#!/usr/bin/env bash set -e SERVER=$1 ROLES=$2 BRANCH=$3 if [ -z "$SERVER" ] || [ -z "$ROLES" ]; then echo "USAGE: $0 server roles [branch]" echo echo "Installs an empty Ubuntu server in AWS with a Zulip server role." echo echo " * server is the local part of the hostname (e.g. postgres0)" echo " * roles is a comma-separated list of Puppet rules to be passed to scripts/lib/install" echo " E.g. 'zulip::profile::postgresql'" echo " * branch is used to override the default branch to install from." echo echo "Reads configuration from $HOME/.zulip-install-server.conf, which should look like:" echo echo "[repo]" echo "repo_url=git@github.com:zulip/zulip.git" echo "branch=main" echo "[aws]" echo "zone_id=Z2U988IEXAMPLE" echo "security_groups=sg-01234567" echo "image_id=ami-0dc45e3d9be6ab7b5" echo "instance_type=m4.large" echo "ssh_secret_id=prod/git/deploy" exit 1 fi set -x cd "$(dirname "$0")" source ./bootstrap-awscli.sh zulip_install_config_file="$HOME/.zulip-install-server.conf" if [ ! -f "$zulip_install_config_file" ]; then echo "No configuration file found in $zulip_install_config_file" exit 1 fi REPO_URL=$(crudini --get "$zulip_install_config_file" repo repo_url) if [ -z "$BRANCH" ]; then BRANCH=$(crudini --get "$zulip_install_config_file" repo default_branch) fi function lookup() { KEY="$1" crudini --get "$zulip_install_config_file" "aws-$ROLES" "$KEY" 2>/dev/null \ || crudini --get "$zulip_install_config_file" aws "$KEY" } AWS_ZONE_ID=$(lookup zone_id) SECURITY_GROUPS=$(lookup security_groups) AMI_ID=$(lookup image_id) INSTANCE_TYPE=$(lookup instance_type) SSH_SECRET_ID=$(lookup ssh_secret_id) AUTO_SCALING_GROUP=$(lookup auto_scaling_group || true) # Verify it doesn't exist already ZONE_NAME=$($AWS route53 get-hosted-zone --id "$AWS_ZONE_ID" | jq -r '.HostedZone.Name') HOSTNAME="$SERVER.${ZONE_NAME%?}" # Remove trailing . EXISTING_RECORDS=$($AWS route53 list-resource-record-sets \ --hosted-zone-id "$AWS_ZONE_ID" \ --query "ResourceRecordSets[?Name == '$HOSTNAME.']" \ | jq '. | length') if [ "$EXISTING_RECORDS" != "0" ]; then echo "$HOSTNAME already exists!" exit 1 fi # Find the ASG, if there is one EXTRA_ARGS=() if [ -n "$AUTO_SCALING_GROUP" ]; then ASG_DESC=$(aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names "$AUTO_SCALING_GROUP" | jq '.AutoScalingGroups[0]') LAUNCH_TEMPLATE=$(echo "$ASG_DESC" | jq '.LaunchTemplate | {Version, LaunchTemplateId}') # We have to move things _into_ the ASG after we create them, but # can only do so if they're in one of the AZ's that the ASG is # for. Choose the AZ that is least-full. POSSIBLE_AZS=$(echo "$ASG_DESC" | jq -r ".AvailabilityZones[]") CURRENT_AZS=$(echo "$ASG_DESC" | jq -r '.Instances[] | .AvailabilityZone') CHOSEN_AZ=$(echo -e "$POSSIBLE_AZS\n$CURRENT_AZS" | sort | uniq -c | sort -n | head -n1 | awk '{print $2}') EXTRA_ARGS+=( --launch-template "$LAUNCH_TEMPLATE" --placement "AvailabilityZone=$CHOSEN_AZ" ) else EXTRA_ARGS+=( --iam-instance-profile "Name=\"EC2ProdInstance\"" --image-id "$AMI_ID" --instance-type "$INSTANCE_TYPE" --security-group-ids "$SECURITY_GROUPS" --monitoring Enabled=true ) fi # Build up the provisioning script BOOTDATA=$(mktemp) { echo "#!/bin/bash" echo "SERVER=$SERVER" echo "HOSTNAME=$HOSTNAME" echo "ROLES=$ROLES" echo "REPO_URL=$REPO_URL" echo "BRANCH=$BRANCH" echo "SSH_SECRET_ID=$SSH_SECRET_ID" sed '/^AWS=/ r ./bootstrap-awscli.sh' bootstrap-aws-installer } >>"$BOOTDATA" TAG_ROLE_NAMES=$(echo "$ROLES" | perl -pe 's/\w+::profile::(\w+)/$1/g') TAGS="[{Key=Name,Value=$SERVER},{Key=role,Value=\"$TAG_ROLE_NAMES\"}]" INSTANCE_DATA=$($AWS ec2 run-instances \ --tag-specifications "ResourceType=instance,Tags=$TAGS" \ "${EXTRA_ARGS[@]}" \ --user-data "file://$BOOTDATA") INSTANCEID=$(echo "$INSTANCE_DATA" | jq -r .Instances[0].InstanceId) # Wait for public IP assignment PUBLIC_DNS_NAME="" while [ -z "$PUBLIC_DNS_NAME" ]; do sleep 1 PUBLIC_DNS_NAME=$($AWS ec2 describe-instances --instance-ids "$INSTANCEID" \ | jq -r .Reservations[0].Instances[0].PublicDnsName) done # Add the hostname to the zone ROUTE53_CHANGES=$(mktemp) cat >"$ROUTE53_CHANGES" <>> Install started successfully! Provisioning takes 5-6min." echo " sleep 360 && ssh root@$HOSTNAME"