#!/usr/bin/env bash
set -eu
set -o pipefail

usage() {
    cat <<EOF
USAGE: $0 [--roles=roles] [--branch=main] [--debug-key=username] server

Installs an empty Ubuntu server in AWS with a Zulip server role.

 * server is the local part of the hostname (e.g. postgres0)

 * roles is a comma-separated list of Puppet profile names; these
   will get 'kandra::profile::' prepended to them, and passed
   to scripts/lib/install -- e.g. 'postgresql'
 * branch is used to override the default branch to install from.
 * username is the name of the AWS SSH key pair to allow logins as
   'ubuntu' with during initial boot; this is purely for debugging the
   bootstrapping process.

Reads configuration from $HOME/.zulip-install-server.conf, which should look like:

[repo]
repo_url=git@github.com:zulip/zulip.git
[aws]
zone_id=Z2U988IEXAMPLE
security_groups=sg-01234567
instance_type=m4.large
EOF
}

args="$(getopt -o '' --long help,branch:,roles:,debug-key: -n "$0" -- "$@")" || {
    usage >&2
    exit 1
}
eval "set -- $args"

BRANCH="production"
ROLES="base"
DEBUG_KEY=""
while true; do
    case "$1" in
        --help)
            usage
            exit 0
            ;;
        --roles)
            shift
            ROLES="$1"
            shift
            ;;
        --branch)
            shift
            BRANCH="$1"
            shift
            ;;
        --debug-key)
            shift
            DEBUG_KEY="$1"
            shift
            ;;
        --)
            shift
            break
            ;;
    esac
done

if [ $# -ne 1 ]; then
    usage >&2
    exit 1
fi

SERVER="$1"

set -x

cd "$(dirname "$0")/../.."

./puppet/kandra/files/install-aws-cli
AWS=/srv/zulip-aws-tools/bin/aws

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)

for role in ${ROLES//,/ }; do
    if ! [ -f "./puppet/kandra/manifests/profile/$role.pp" ]; then
        echo "No such role kandra::profile::$role !"
        exit 1
    fi
done
FULL_ROLES=$(echo "$ROLES" | perl -pe '$_=join(",",map{"kandra::profile::$_"} split ",")')

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)
INSTANCE_TYPE=$(lookup instance_type)
IAM_PROFILE=$(lookup iam_profile)
AZ=$(lookup availability_zone)
DISK_SIZE=$(lookup disk_size)

# Determine the architecture
ARCH=$($AWS ec2 describe-instance-types --instance-types "$INSTANCE_TYPE" --query 'InstanceTypes[*].ProcessorInfo.SupportedArchitectures[0]' --output text)

# Lookup the latest 22.04 image
AMI_ID=$($AWS ec2 describe-images --owners 099720109477 --filters 'Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-*-22.04*' "Name=architecture,Values=$ARCH" --query 'sort_by(Images, &CreationDate)[-1].ImageId' --output text)

# 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}"
    --metadata-options "InstanceMetadataTags=enabled"
)

if [ -n "$DEBUG_KEY" ]; then
    EXTRA_ARGS+=(
        --key-name "$DEBUG_KEY"
    )
fi

# Build up the provisioning script
BOOTDATA=$(mktemp)
{
    echo "#!/bin/bash"
    echo "SERVER=$SERVER"
    echo "HOSTNAME=$HOSTNAME"
    echo "FULL_ROLES=$FULL_ROLES"
    echo "REPO_URL=$REPO_URL"
    echo "BRANCH=$BRANCH"
    # Replace anything which looks like FOO="inline!bar/baz" with the
    # output of pack-local-script, which will make "$FOO" inside the
    # $BOOTDATA be the path to that script (smuggled inline and
    # unpacked before use).
    perl -ple 's|^(\w+)="inline!([^"]+)"|qx(./tools/setup/pack-local-script $1 $2)|e' ./tools/setup/bootstrap-aws-installer
} >>"$BOOTDATA"

TAG_ROLE_NAMES="$ROLES"
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"

set +x
echo
echo
echo ">>> Install started successfully!  Provisioning takes 5-6min."
echo "    sleep 360 && ssh root@$HOSTNAME"
