#!/bin/bash
# Site server installer. Served by the installer service; the service fills in its own address.
#
#   curl -fsSL https://install.remote.lcmaccess.com -o install-site.sh
#   sudo bash install-site.sh
#
# Turns a fresh Ubuntu Server 24.04 into a supertool site server: Docker, the
# jump account, the NetBird agent, the role script, then the role itself. The
# machine then waits for supertool to take it over.
#
# Environment overrides (all optional):
#   ROLE=backend|frontend   CODE=XXXX-XXXX   SERVER_USER=<account>
#   NIC=, GATEWAY=, DNS=    passed on to site-setup
#   REBOOT=yes              reboot at the end without asking
set -euo pipefail

INSTALLER_URL="${INSTALLER_URL:-https://install.remote.lcmaccess.com}"
INSTALLER_VERSION="0.1.0"
LOG=/var/log/site-installer.log
STATE_DIR=/var/lib/site-installer

say()  { printf '\n==> %s\n' "$*"; }
fail() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }

[ "$(id -u)" = 0 ] || fail "run this with sudo"
[ -r /etc/os-release ] && . /etc/os-release
[ "${ID:-}" = ubuntu ] || fail "this installer is for Ubuntu Server 24.04 (found ${PRETTY_NAME:-unknown})"
case "${VERSION_ID:-}" in 24.*) ;; *) echo "WARNING: built for Ubuntu 24.04, found ${VERSION_ID:-?}. Continuing." ;; esac

echo "Site server installer ${INSTALLER_VERSION} (${INSTALLER_URL})"

# ---------------------------------------------------------------- inputs
ask_inputs() {
  role="${ROLE:-}"
  while [ "$role" != backend ] && [ "$role" != frontend ]; do
    read -r -p "Role of this machine [backend/frontend]: " role
  done

  server_user="${SERVER_USER:-${SUDO_USER:-}}"
  if [ -z "$server_user" ] || [ "$server_user" = root ]; then
    read -r -p "Service account supertool signs in as: " server_user
  fi
  id "$server_user" >/dev/null 2>&1 || fail "no such account: ${server_user}"
  [ -n "${SERVER_USER:-}" ] || {
    read -r -p "Supertool will sign in as '${server_user}'. Correct? [Y/n] " yn
    case "${yn:-Y}" in [Yy]*) ;; *) fail "set SERVER_USER=<account> and run again" ;; esac
  }

  code="${CODE:-}"
  [ -n "$code" ] || read -r -p "Enrollment code from supertool (XXXX-XXXX): " code
}

# ---------------------------------------------------------------- enroll
enroll() {
  say "Checking the code with ${INSTALLER_URL}"
  command -v curl >/dev/null || { apt-get update -q && apt-get install -y -q curl ca-certificates; }
  command -v jq >/dev/null || { apt-get update -q && apt-get install -y -q jq; }

  payload="$(jq -nc --arg code "$code" --arg role "$role" --arg hostname "$(hostname)" '{code:$code, role:$role, hostname:$hostname}')"
  tmp="$(mktemp)"; trap 'rm -f "$tmp"' EXIT
  status="$(curl -sS -o "$tmp" -w '%{http_code}' -H 'content-type: application/json' -d "$payload" "${INSTALLER_URL}/enroll" || echo 000)"
  if [ "$status" != 200 ]; then
    message="$(jq -r '.message // empty' "$tmp" 2>/dev/null || true)"
    fail "enrollment refused (HTTP ${status}): ${message:-no answer from the installer service}"
  fi
  site="$(jq -r .site "$tmp")"
  management_url="$(jq -r .managementUrl "$tmp")"
  jump_pubkey="$(jq -r .jumpPubkey "$tmp")"
  supertool_mesh_ip="$(jq -r .supertoolMeshIp "$tmp")"
  setup_key="$(jq -r '.setupKey // empty' "$tmp")"
  report_token="$(jq -r .reportToken "$tmp")"
  rm -f "$tmp"
  [ -n "$site" ] && [ -n "$management_url" ] && [ -n "$report_token" ] || fail "the enrollment answer is incomplete"
  [ "$role" = frontend ] || [ -n "$setup_key" ] || fail "no NetBird setup key in the enrollment answer"
  echo "Enrolled: site ${site}, role ${role}, hostname will be ${site}-${role:0:2}"
}

# ---------------------------------------------------------------- the long part
# The role step changes this machine's address, and an SSH session from the old
# address dies there. The work runs detached and is logged, so it finishes either way.
run_detached() {
  mkdir -p "$STATE_DIR"
  umask 077
  {
    printf '%s=%q\n' ROLE "$role" SITE "$site" SERVER_USER "$server_user"
    printf '%s=%q\n' MANAGEMENT_URL "$management_url" SUPERTOOL_MESH_IP "$supertool_mesh_ip"
    printf '%s=%q\n' JUMP_PUBKEY "$jump_pubkey" SETUP_KEY "$setup_key" REPORT_TOKEN "$report_token"
    printf '%s=%q\n' INSTALLER_URL "$INSTALLER_URL" NIC "${NIC:-}" GATEWAY "${GATEWAY:-}" DNS "${DNS:-}"
  } > "$STATE_DIR/env"
  cp "$0" "$STATE_DIR/install.sh"
  echo "The install now runs in the background and logs to ${LOG}."
  if [ -n "${SSH_CONNECTION:-}" ]; then
    echo "You are connected over SSH: this session will drop when the address changes to 192.168.2.x."
    echo "Reconnect to 192.168.2.$([ "$role" = backend ] && echo 2 || echo 3) and read: sudo tail -f ${LOG}"
  fi
  setsid nohup bash "$STATE_DIR/install.sh" --phase2 >"$LOG" 2>&1 < /dev/null &
  tail -f "$LOG" --pid "$!" 2>/dev/null || true
  wait "$!" 2>/dev/null || true
  grep -q '^INSTALL DONE' "$LOG" || { echo "The install did not finish. Read ${LOG}."; exit 1; }
  echo
  if [ "${REBOOT:-}" = yes ]; then reboot; fi
  read -r -p "Reboot now to confirm that everything comes back? [y/N] " yn
  case "$yn" in [Yy]*) reboot ;; *) echo "Reboot later with: sudo reboot" ;; esac
}

phase2() {
  set -euo pipefail
  # shellcheck source=/dev/null
  . "$STATE_DIR/env"
  export DEBIAN_FRONTEND=noninteractive

  say "Phase 1: base tools"
  apt-get update -q
  apt-get install -y -q curl ca-certificates ufw jq

  say "Phase 2: Docker and the service account"
  if ! command -v docker >/dev/null; then
    curl -fsSL https://get.docker.com | sh
  fi
  usermod -aG docker "$SERVER_USER"
  docker info --format '{{.ServerVersion}}' >/dev/null
  echo "Docker $(docker info --format '{{.ServerVersion}}'), compose $(docker compose version --short)"

  if [ "$ROLE" = backend ]; then
    say "Phase 3: the jump account"
    id stjump >/dev/null 2>&1 || adduser --disabled-password --gecos "" --shell /usr/sbin/nologin stjump
    usermod -p '*' stjump
    install -d -m 700 -o stjump -g stjump /home/stjump/.ssh
    echo "restrict,port-forwarding,permitopen=\"192.168.2.2:22\",permitopen=\"192.168.2.3:22\" ${JUMP_PUBKEY}" > /home/stjump/.ssh/authorized_keys
    chown stjump:stjump /home/stjump/.ssh/authorized_keys
    chmod 600 /home/stjump/.ssh/authorized_keys
    cat > /etc/ssh/sshd_config.d/50-stjump.conf <<'SSHD'
Match User stjump
    PasswordAuthentication no
    KbdInteractiveAuthentication no
    AllowTcpForwarding local
    PermitOpen 192.168.2.2:22 192.168.2.3:22
    AllowAgentForwarding no
    X11Forwarding no
    PermitTTY no
    ForceCommand /usr/sbin/nologin
SSHD
    sshd -t
    systemctl reload ssh
    echo "Jump key: $(ssh-keygen -lf /home/stjump/.ssh/authorized_keys)"

    say "Phase 4: NetBird agent, installed but not joined"
    if ! command -v netbird >/dev/null; then
      curl -fsSL https://pkgs.netbird.io/install.sh | sh
    fi
    systemctl disable --now netbird >/dev/null 2>&1 || true
  fi

  say "Phase 5: the role script"
  install -m 755 "$STATE_DIR/site-setup" /usr/local/sbin/site-setup
  cat > /etc/site-setup.conf <<CONF
MANAGEMENT_URL=${MANAGEMENT_URL}
SUPERTOOL_MESH_IP=${SUPERTOOL_MESH_IP}
CONF
  chmod 644 /etc/site-setup.conf
  cat > /etc/systemd/system/supertool-mesh-drop.service <<'UNIT'
[Unit]
Description=Drop mesh traffic to the Docker containers
After=docker.service
Wants=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c 'iptables -C DOCKER-USER -i wt0 -j DROP 2>/dev/null || iptables -I DOCKER-USER -i wt0 -j DROP'

[Install]
WantedBy=multi-user.target
UNIT
  systemctl daemon-reload

  say "Phase 8: the role"
  SETUP_KEY="$SETUP_KEY" NIC="$NIC" GATEWAY="$GATEWAY" DNS="$DNS" /usr/local/sbin/site-setup "$ROLE" "$SITE"

  say "Reporting to supertool"
  host_key="$(ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub | awk '{print $2}')"
  mesh_ip=null
  if [ "$ROLE" = backend ]; then
    mesh_ip="$(netbird status | grep 'NetBird IP' | grep -oE '100\.[0-9]+\.[0-9]+\.[0-9]+' | head -1)"
    [ -n "$mesh_ip" ] || { echo "WARNING: no mesh address yet; report it from supertool by hand" ; mesh_ip=null; }
    [ "$mesh_ip" = null ] || mesh_ip="\"$mesh_ip\""
  fi
  lan_address="192.168.2.$([ "$ROLE" = backend ] && echo 2 || echo 3)"
  report="$(jq -nc --arg token "$REPORT_TOKEN" --arg key "$host_key" --arg hostname "$(hostname)" --arg lan "$lan_address" --argjson mesh "$mesh_ip" '{reportToken:$token, meshIp:$mesh, lanAddress:$lan, hostKey:$key, hostname:$hostname}')"
  for attempt in 1 2 3 4 5 6; do
    if curl -fsS -o /dev/null -H 'content-type: application/json' -d "$report" "${INSTALLER_URL}/report"; then
      echo "Reported: host key ${host_key}${mesh_ip:+, mesh ${mesh_ip}}"
      break
    fi
    echo "Report attempt ${attempt} failed; waiting"
    sleep 10
  done

  rm -f "$STATE_DIR/env"
  echo
  echo "Host key fingerprint: ${host_key}"
  [ "$ROLE" = frontend ] || echo "Mesh address: ${mesh_ip}"
  echo "INSTALL DONE. Supertool can now finish the enrollment of site ${SITE}."
}

if [ "${1:-}" = --phase2 ]; then
  phase2
  exit 0
fi

ask_inputs
enroll
mkdir -p "$STATE_DIR"
curl -fsSL "${INSTALLER_URL}/site-setup" -o "$STATE_DIR/site-setup"
bash -n "$STATE_DIR/site-setup" || fail "the role script did not download correctly"
run_detached
