#!/usr/bin/env bash
# Idempotent staged installation for q4.service. Public routing is deliberately
# separate from the local service start so full browser E2E can run first.
set -euo pipefail

ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
MODE=install
WATCHDOG_CRON=/etc/cron.d/q4-caddy-watchdog
HEALTH_URL=http://127.0.0.1:3280/healthz

if (($# > 1)); then
  echo "usage: $0 [--start-local|--publish]" >&2
  exit 64
fi
case "${1:-}" in
  '') ;;
  --start-local) MODE=start-local ;;
  --publish) MODE=publish ;;
  *) echo "usage: $0 [--start-local|--publish]" >&2; exit 64 ;;
esac

if [[ $EUID -ne 0 ]]; then
  echo "install: run as root" >&2
  exit 1
fi
if [[ "$ROOT" != /srv/q4 ]]; then
  echo "install: expected checkout at /srv/q4, got $ROOT" >&2
  exit 1
fi

test -x "$ROOT/runtime/native/openQ4-ded_x64"
test -x "$ROOT/runtime/native/openQ4-client_x64"
test -r "$ROOT/runtime/native/baseoq4/game-mp_x64.so"
test -r "$ROOT/runtime/retail-botnav-manifest.json"
test -d "$ROOT/assets-src/gog/q4base"
test -r "$ROOT/tools/caddy-route.json"
test -x "$ROOT/tools/caddy_route_remove.sh"

# Bind deployment to a fully re-hashed copy of the explicit retail allowlist.
node "$ROOT/tools/stage-retail-manifest.mjs" --verify
test -r "$ROOT/runtime/retail-manifest.json"
python3 "$ROOT/tools/stage-retail-bundles.py" --verify
test -r "$ROOT/runtime/retail-bundle-manifest.json"
test -d "$ROOT/runtime/retail-bundles"
node "$ROOT/tools/stage-retail-botnav.mjs" --verify

if ! getent group q4 >/dev/null; then
  groupadd --system q4
fi
if ! getent passwd q4 >/dev/null; then
  useradd --system --gid q4 --home-dir /srv/q4/runtime/native/home \
    --no-create-home --shell /usr/sbin/nologin q4
fi
if [[ $(id -gn q4) != q4 ]]; then
  echo "install: existing q4 user does not have q4 as its primary group" >&2
  exit 1
fi
q4_passwd=$(getent passwd q4)
q4_uid=$(cut -d: -f3 <<<"$q4_passwd")
q4_home=$(cut -d: -f6 <<<"$q4_passwd")
q4_shell=$(cut -d: -f7 <<<"$q4_passwd")
if ((q4_uid >= 1000)) || [[ "$q4_home" != /srv/q4/runtime/native/home ]] \
    || [[ "$q4_shell" != /usr/sbin/nologin ]]; then
  echo "install: existing q4 account is not the expected locked system identity" >&2
  exit 1
fi

install -d -o q4 -g q4 -m 0750 \
  /srv/q4/replays \
  /srv/q4/runtime/native/home \
  /srv/q4/runtime/replay-instances
chown -R q4:q4 \
  /srv/q4/replays \
  /srv/q4/runtime/native/home \
  /srv/q4/runtime/replay-instances

# Fail before installing the unit if the service identity cannot traverse its
# read-only runtime or read the owner's private PK4 directory.
runuser -u q4 -- test -x /srv/q4/runtime/native/openQ4-ded_x64
runuser -u q4 -- test -x /srv/q4/runtime/native/openQ4-client_x64
runuser -u q4 -- test -r /srv/q4/runtime/native/baseoq4/game-mp_x64.so
runuser -u q4 -- test -r /srv/q4/assets-src/gog/q4base/pak001.pk4
runuser -u q4 -- test -r /srv/q4/runtime/retail-manifest.json
runuser -u q4 -- test -r /srv/q4/runtime/retail-bundle-manifest.json
runuser -u q4 -- test -x /srv/q4/runtime/retail-bundles
runuser -u q4 -- test -r /srv/q4/runtime/retail-botnav-manifest.json
for retail_name in pak{001..022}.pk4 zpak_english.pk4; do
  runuser -u q4 -- test -r "/srv/q4/assets-src/gog/q4base/$retail_name"
done
# Repeat the exact PK4/AAS and generator-identity validation as the locked
# service account so publication cannot silently fall back to botless maps.
runuser -u q4 -- node /srv/q4/tools/stage-retail-botnav.mjs --verify
runuser -u q4 -- python3 /srv/q4/tools/stage-retail-bundles.py --verify
runuser -u q4 -- test -w /srv/q4/replays
runuser -u q4 -- test -w /srv/q4/runtime/native/home
runuser -u q4 -- test -w /srv/q4/runtime/replay-instances
if runuser -u q4 -- test -w /srv/q4/public; then
  echo "install: q4 must not be able to write the public tree" >&2
  exit 1
fi

install -o root -g root -m 0644 "$ROOT/deploy/q4.service" /etc/systemd/system/q4.service
systemctl daemon-reload

case "$MODE" in
  install)
    echo "install: q4.service unit installed; service and publication state unchanged"
    ;;
  start-local)
    # An older installation may already have enabled publication. Remove it
    # before starting so the new local E2E target cannot be reached publicly.
    rm -f "$WATCHDOG_CRON"
    "$ROOT/tools/caddy_route_remove.sh"
    systemctl enable --now q4
    echo "install: q4.service started locally; public Caddy route and watchdog disabled"
    ;;
  publish)
    # The public source archive must describe this exact frozen engine and native
    # source chain, including exact patch members, then pass all artifact checksums.
    python3 "$ROOT/tools/source-offer-identity.py" verify --root "$ROOT"
    node "$ROOT/tools/verify-e2e-evidence.mjs"
    node "$ROOT/tools/verify-retail-bundle-cache-evidence.mjs"
    node "$ROOT/tools/verify-retail-bundle-maps-evidence.mjs"

    health=$(curl -fsS --max-time 5 "$HEALTH_URL") || {
      echo "install: local q4 health is unavailable; refusing publication" >&2
      exit 1
    }
    if ! grep -Eq '"status"[[:space:]]*:[[:space:]]*"ok"' <<<"$health" \
        || ! grep -Eq '"dedicated"[[:space:]]*:[[:space:]]*true' <<<"$health" \
        || ! grep -Eq '"retail"[[:space:]]*:[[:space:]]*true' <<<"$health" \
        || ! grep -Eq '"retailBundles"[[:space:]]*:[[:space:]]*true' <<<"$health"; then
      echo "install: local q4 gateway/dedicated is not ready; refusing publication" >&2
      exit 1
    fi

    # Keep the immediate route update single-threaded with cron installation.
    # The watchdog checks for an existing host route before it ever POSTs.
    rm -f "$WATCHDOG_CRON"
    Q4_CADDY_WATCHDOG_FORCE=1 "$ROOT/tools/caddy_route_watchdog.sh"
    install -o root -g root -m 0644 \
      "$ROOT/deploy/q4-caddy-watchdog.cron" "$WATCHDOG_CRON"
    echo "install: local q4 is ready; Caddy route checked and watchdog enabled"
    ;;
esac
