#!/usr/bin/env bash
# =============================================================================
# provision-vm.sh — create & configure the free-tier GCP VM that relays phones.
#
# Idempotent-ish and argument driven. Handles: gcloud install, sign-in, project
# create/select, billing link, Compute API, the free-tier e2-micro VM, the
# firewall port range, sshd tweaks (GatewayPorts + dead-tunnel reaping), and the
# list-phones helper.
#
# Usage (minimum):
#   bash provision-vm.sh --project personal-projects-hub
#
# Common options (defaults shown):
#   --project NAME             (required) GCP project id to use/create
#   --create-project           create the project if it does not exist
#   --billing-account ID       link this billing account (needed to create a VM)
#   --account EMAIL            gcloud account to sign in as
#   --zone us-west1-b          MUST be us-west1/us-central1/us-east1 for free tier
#   --machine-type e2-micro    free-tier machine type
#   --vm-name hub-server
#   --relay-user karthik       the SSH user phones log in to the VM as
#   --disk-size 30             GB, standard persistent disk (free tier <=30)
#   --image-family ubuntu-2404-lts-amd64
#   --port-range 2222-2240     public relay ports to open in the firewall
#   --skip-auth                assume gcloud is already authenticated
#   --deploy-site              also deploy the Django portal (opens tcp:80/443)
#   --domain NAME              TLS hostname for the portal (e.g. tyrostir.duckdns.org)
#   --email ADDR               Let's Encrypt email for HTTPS
#   --ddns-token TOKEN         DuckDNS token: point/refresh --domain at this VM
#   -h | --help
# =============================================================================
set -euo pipefail

PROJECT=""; CREATE_PROJECT=0; BILLING=""; ACCOUNT=""
ZONE="us-west1-b"; MACHINE="e2-micro"; VM="hub-server"; RELAY_USER="karthik"
DISK="30"; IMAGE_FAMILY="ubuntu-2404-lts-amd64"; IMAGE_PROJECT="ubuntu-os-cloud"
PORT_RANGE="2222-2240"; SKIP_AUTH=0
DEPLOY_SITE=0; DOMAIN=""; EMAIL=""; DDNS_TOKEN=""
die(){ echo "ERROR: $*" >&2; exit 1; }
usage(){ sed -n '2,42p' "$0"; }

while [ $# -gt 0 ]; do case "$1" in
  --deploy-site) DEPLOY_SITE=1; shift;;
  --domain) DOMAIN="${2:-}"; shift 2;;
  --email) EMAIL="${2:-}"; shift 2;;
  --ddns-token) DDNS_TOKEN="${2:-}"; shift 2;;
  --project) PROJECT="${2:-}"; shift 2;;
  --create-project) CREATE_PROJECT=1; shift;;
  --billing-account) BILLING="${2:-}"; shift 2;;
  --account) ACCOUNT="${2:-}"; shift 2;;
  --zone) ZONE="${2:-}"; shift 2;;
  --machine-type) MACHINE="${2:-}"; shift 2;;
  --vm-name) VM="${2:-}"; shift 2;;
  --relay-user) RELAY_USER="${2:-}"; shift 2;;
  --disk-size) DISK="${2:-}"; shift 2;;
  --image-family) IMAGE_FAMILY="${2:-}"; shift 2;;
  --port-range) PORT_RANGE="${2:-}"; shift 2;;
  --skip-auth) SKIP_AUTH=1; shift;;
  -h|--help) usage; exit 0;;
  *) die "unknown argument: $1 (use --help)";;
esac; done
[ -n "$PROJECT" ] || die "--project is required"
REGION="${ZONE%-*}"

echo "==> [1/8] ensuring gcloud is installed"
if ! command -v gcloud >/dev/null 2>&1; then
    echo "    installing google-cloud-cli ..."
    sudo apt-get install -y apt-transport-https ca-certificates gnupg curl >/dev/null
    curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg \
        | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
    echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \
        | sudo tee /etc/apt/sources.list.d/google-cloud-sdk.list >/dev/null
    sudo apt-get update >/dev/null && sudo apt-get install -y google-cloud-cli >/dev/null
fi
gcloud --version | head -1

echo "==> [2/8] authentication"
if [ "$SKIP_AUTH" = 0 ] && ! gcloud auth list --filter=status:ACTIVE --format='value(account)' | grep -q .; then
    echo "    no active account — launching sign-in (paste the code it asks for)"
    if [ -n "$ACCOUNT" ]; then gcloud auth login "$ACCOUNT" --no-launch-browser
    else gcloud auth login --no-launch-browser; fi
fi
[ -n "$ACCOUNT" ] && gcloud config set account "$ACCOUNT" >/dev/null 2>&1 || true
echo "    active: $(gcloud auth list --filter=status:ACTIVE --format='value(account)' | head -1)"

echo "==> [3/8] project"
if ! gcloud projects describe "$PROJECT" >/dev/null 2>&1; then
    [ "$CREATE_PROJECT" = 1 ] || die "project '$PROJECT' not found (add --create-project to create it)"
    gcloud projects create "$PROJECT" --name="$PROJECT"
fi
gcloud config set project "$PROJECT" >/dev/null

echo "==> [4/8] billing"
if [ -n "$BILLING" ]; then
    gcloud billing projects link "$PROJECT" --billing-account="$BILLING" >/dev/null \
        && echo "    linked $BILLING"
else
    gcloud billing projects describe "$PROJECT" >/dev/null 2>&1 \
        && grep -q "billingEnabled: true" < <(gcloud billing projects describe "$PROJECT") \
        || echo "    WARNING: billing not linked; VM creation may fail. Pass --billing-account."
fi

echo "==> [5/8] enabling Compute Engine API"
gcloud services enable compute.googleapis.com >/dev/null

echo "==> [6/8] creating VM $VM ($MACHINE, $ZONE)"
if gcloud compute instances describe "$VM" --zone="$ZONE" >/dev/null 2>&1; then
    echo "    already exists"
else
    gcloud compute instances create "$VM" \
        --zone="$ZONE" --machine-type="$MACHINE" \
        --image-family="$IMAGE_FAMILY" --image-project="$IMAGE_PROJECT" \
        --boot-disk-size="${DISK}GB" --boot-disk-type=pd-standard \
        --tags=ssh-relay >/dev/null
fi
IP=$(gcloud compute instances describe "$VM" --zone="$ZONE" \
       --format='value(networkInterfaces[0].accessConfigs[0].natIP)')
echo "    external IP: $IP"

echo "==> [7/8] firewall (tcp:$PORT_RANGE)"
gcloud compute firewall-rules describe allow-relay-range >/dev/null 2>&1 || \
gcloud compute firewall-rules create allow-relay-range \
    --allow="tcp:${PORT_RANGE}" --target-tags=ssh-relay --direction=INGRESS >/dev/null
echo "    allow-relay-range ok"
if [ "$DEPLOY_SITE" = 1 ]; then
    for r in "allow-http:tcp:80" "allow-https:tcp:443"; do
        name="${r%%:*}"; ports="${r#*:}"
        gcloud compute firewall-rules describe "$name" >/dev/null 2>&1 || \
        gcloud compute firewall-rules create "$name" --allow="$ports" \
            --target-tags=ssh-relay --direction=INGRESS >/dev/null
    done
    echo "    allow-http / allow-https ok"
fi

echo "==> [8/8] configuring VM sshd + list-phones helper"
REMOTE_SETUP=$(cat <<'REMOTE'
set -e
# GatewayPorts so phones can bind 0.0.0.0:<port>; reap dead tunnels fast so
# ports free themselves. 00- sorts before the cloud image's 50- default.
echo 'GatewayPorts clientspecified' | sudo tee /etc/ssh/sshd_config.d/99-relay.conf >/dev/null
printf 'ClientAliveInterval 30\nClientAliveCountMax 3\n' | sudo tee /etc/ssh/sshd_config.d/00-reap.conf >/dev/null
sudo sshd -t && sudo systemctl reload ssh
sudo tee /usr/local/bin/list-phones >/dev/null <<'LP'
#!/bin/bash
printf 'Relay phones online — %s  (%s)\n' "$(hostname)" "$(date '+%F %T')"
printf '%-6s  %-21s  %s\n' PORT PHONE_PUBLIC_IP STATE
printf '%s\n' "-------------------------------------------------"
found=0
while read -r l; do
  port=$(printf '%s' "$l" | grep -oE '0\.0\.0\.0:[0-9]+' | head -1 | cut -d: -f2)
  pid=$(printf '%s' "$l" | grep -oP 'pid=\K[0-9]+' | head -1)
  [ -z "$port" ] && continue
  peer=$(sudo ss -tnpH state established 2>/dev/null | grep "pid=$pid," \
          | awk '{print $4}' | grep -vE '^127\.|^0\.0\.0\.0' | head -1)
  printf '%-6s  %-21s  %s\n' "$port" "${peer:-<unknown>}" ONLINE; found=1
done < <(sudo ss -tlnpH 2>/dev/null | grep -E '0.0.0.0:(222[2-9]|223[0-9]|2240)')
[ "$found" = 0 ] && printf '  (no phones connected)\n'
LP
sudo chmod +x /usr/local/bin/list-phones
echo "VM configured"
REMOTE
)
gcloud compute ssh "$RELAY_USER@$VM" --zone="$ZONE" --command="$REMOTE_SETUP" 2>&1 | tail -3

if [ "$DEPLOY_SITE" = 1 ]; then
    echo "==> [9/9] deploying the portal (gunicorn + nginx${DOMAIN:+ + HTTPS})"
    REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
    [ -f "$REPO_ROOT/manage.py" ] || die "--deploy-site must run from inside the repo (found no manage.py at $REPO_ROOT)"
    TARBALL="/tmp/gcprelay-deploy.tar.gz"
    tar -C "$REPO_ROOT" --exclude=.git --exclude=.venv --exclude=otp_codes \
        --exclude=db.sqlite3 --exclude=staticfiles -czf "$TARBALL" .
    gcloud compute scp "$TARBALL" "$RELAY_USER@$VM:/tmp/gcprelay-deploy.tar.gz" --zone="$ZONE" >/dev/null
    [ -n "$DOMAIN" ] || die "--deploy-site requires --domain (e.g. tyrostir.duckdns.org)"
    HTTPS_ARGS="--https --domain $DOMAIN"
    [ -n "$EMAIL" ] && HTTPS_ARGS="$HTTPS_ARGS --email $EMAIL"
    [ -n "$DDNS_TOKEN" ] && HTTPS_ARGS="$HTTPS_ARGS --ddns-token $DDNS_TOKEN"
    gcloud compute ssh "$RELAY_USER@$VM" --zone="$ZONE" --command="\
        rm -rf ~/gcprelay-src && mkdir -p ~/gcprelay-src && \
        tar xzf /tmp/gcprelay-deploy.tar.gz -C ~/gcprelay-src && \
        cd ~/gcprelay-src && sudo bash scripts/deploy-site.sh --host $IP $HTTPS_ARGS" 2>&1 | tail -8
fi

cat <<DONE

============================================================
 VM READY — $VM @ $IP  (project $PROJECT, zone $ZONE)
============================================================
 Phones connect out to this IP on ports $PORT_RANGE.
 The phone's VM key is generated by gcloud at:
     ~/.ssh/google_compute_engine   (reuse across phones)
 See who is online:
     gcloud compute ssh $RELAY_USER@$VM --zone=$ZONE --command=list-phones
 Next: run the phone setup script with --vm-ip $IP, and deploy-site.sh to host
 the portal at http://$IP/gcprelay
============================================================
DONE
