#!/usr/bin/env bash set -eu set -o pipefail 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) IAM_PROFILE=$(lookup iam_profile) SSH_SECRET_ID=$(lookup ssh_secret_id) AZ=$(lookup availability_zone) DISK_SIZE=$(lookup disk_size) # 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 # https://docs.aws.amazon.com/cli/latest/reference/ec2/run-instances.html # shellcheck disable=SC2206 # We intentionally split $SECURITY_GROUPS EXTRA_ARGS+=( --iam-instance-profile "Name=\"$IAM_PROFILE\"" --image-id "$AMI_ID" --instance-type "$INSTANCE_TYPE" --security-group-ids $SECURITY_GROUPS --monitoring Enabled=true --placement "AvailabilityZone=$AZ" --block-device-mappings "DeviceName=/dev/sda1,Ebs={VolumeSize=$DISK_SIZE,VolumeType=gp3,Throughput=125,Iops=3000,Encrypted=true}" ) # 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"