zulip/tools/setup/install-aws-server

160 lines
5.2 KiB
Bash
Executable File

#!/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" <<EOF
{
"Comment": "Add the $HOSTNAME CNAME record",
"Changes": [
{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "$HOSTNAME",
"Type": "CNAME",
"TTL": 300,
"ResourceRecords": [{"Value": "$PUBLIC_DNS_NAME"}]
}
}
]
}
EOF
$AWS route53 change-resource-record-sets --hosted-zone-id "$AWS_ZONE_ID" --change-batch "file://$ROUTE53_CHANGES"
rm "$ROUTE53_CHANGES"
# Attach to the ASG
if [ -n "$AUTO_SCALING_GROUP" ]; then
# We need to wait for it to be "running" first
aws ec2 wait instance-running --instance-ids "$INSTANCEID"
aws autoscaling attach-instances --instance-ids "$INSTANCEID" --auto-scaling-group-name "$AUTO_SCALING_GROUP"
fi
set +x
echo
echo
echo ">>> Install started successfully! Provisioning takes 5-6min."
echo " sleep 360 && ssh root@$HOSTNAME"