#!/usr/bin/env bash
# get.kinra.ai server installer — stage the kin Outpost on a fresh Linux
# docker host:
#   bash <(curl -fsSL https://get.kinra.ai/outpost-install.sh)
#
# What it does: checks docker/compose/git + the GitHub SSH key, clones (or
# updates) ~/kin-textual, stages the five secret files under
# container/secrets/ (empty stubs, mode 600), then EITHER brings the
# container up (all five secrets non-empty) and waits for the compose
# healthcheck, OR stops before `compose up` and prints the manual secrets
# checklist. "Staged, awaiting secrets" is this script's designed success
# state (exit 0) — oauth2-proxy cannot come up healthy on empty stubs.
#
# What it never does: generate or guess secret values; touch Traefik/DNS
# (routing to this box stays a manual, operator-owned step); use sudo.
#
# Same contract as install.sh: bash-3.2-compatible, idempotent, --check
# walks the decision tree mutating nothing, main-at-EOF so a truncated
# curl is a no-op.

set -euo pipefail

REPO_SSH="git@github.com:kinra-ai/kin-textual.git"
CHECKOUT="${HOME:-}/kin-textual"
COMPOSE_FILE="$CHECKOUT/container/compose.yaml"
SECRETS_DIR="$CHECKOUT/container/secrets"
# The five compose file-secrets (see container/compose.yaml's `secrets:` map).
SECRET_FILES="oauth-client-id oauth-client-secret oauth-cookie-secret kin-settings vapid-private-key"
CHECK=0

say()  { printf '%s\n' "$*"; }
ok()   { printf 'ok: %s\n' "$*"; }
would(){ printf 'would: %s\n' "$*"; }
fail() { printf 'error: %s\n' "$*" >&2; }

check_platform_docker() {
  local os
  os="$(uname -s)"
  if [ "$os" != "Linux" ] && [ "$os" != "Darwin" ]; then
    fail "unsupported platform '$os' — the Outpost targets a Linux docker host"
    return 1
  fi
  ok "platform $os"
  if ! command -v docker >/dev/null 2>&1; then
    fail "docker not found — install Docker Engine first (Fedora: https://docs.docker.com/engine/install/fedora/ · Ubuntu: https://docs.docker.com/engine/install/ubuntu/)"
    return 1
  fi
  ok "docker on PATH"
  if ! docker compose version >/dev/null 2>&1; then
    fail "docker compose v2 not available — install the docker-compose-plugin package (the v1 'docker-compose' binary is not supported)"
    return 1
  fi
  ok "docker compose v2 available"
  if ! command -v git >/dev/null 2>&1; then
    fail "git not found — install it first (Fedora: sudo dnf install git · Ubuntu/Debian: sudo apt install git)"
    return 1
  fi
  ok "git on PATH"
}

check_ssh_key() {
  # GitHub's SSH probe exits 1 on SUCCESSFUL auth, 255 when no usable key was
  # offered. accept-new writes known_hosts, so --check only describes it.
  if [ "$CHECK" = 1 ]; then
    would "probe SSH auth to git@github.com (BatchMode; exit 1 = key ok, 255 = no key)"
    return 0
  fi
  local rc=0 out=""
  out="$(ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 \
      -T git@github.com 2>&1)" || rc=$?
  if [ "$rc" = 1 ]; then
    ok "SSH key authenticates to github.com"
    return 0
  fi
  # ssh exits 255 for BOTH auth and network failures — discriminate on the
  # message so a proxied/offline host isn't told its key is bad.
  case "$out" in
    *"Permission denied"*)
      fail "no SSH key with access to github.com (ssh probe exit $rc). kin-textual is a private repo — this host needs an SSH key with access to kinra-ai/kin-textual (https://github.com/settings/keys)."
      ;;
    *)
      fail "could not reach github.com over SSH (exit $rc) — check network/DNS/proxy, then re-run. ssh said: $out"
      ;;
  esac
  return 1
}

ensure_checkout() {
  if [ -d "$CHECKOUT/.git" ]; then
    if [ "$CHECK" = 1 ]; then
      would "update the existing checkout: git -C $CHECKOUT pull --ff-only (warn + continue on divergence)"
      return 0
    fi
    # A half-clone from an earlier network death has .git but no HEAD.
    if ! git -C "$CHECKOUT" rev-parse HEAD >/dev/null 2>&1; then
      fail "$CHECKOUT looks like an incomplete checkout (no HEAD) — move it aside and re-run"
      return 1
    fi
    say "→ updating existing checkout at $CHECKOUT"
    if ! git -C "$CHECKOUT" pull --ff-only; then
      say "⚠ could not fast-forward $CHECKOUT (local changes or a diverged branch) — continuing with the existing checkout as-is" >&2
    fi
  elif [ -e "$CHECKOUT" ]; then
    fail "$CHECKOUT exists but is not a git checkout — move it aside and re-run"
    return 1
  elif [ "$CHECK" = 1 ]; then
    would "git clone $REPO_SSH $CHECKOUT"
  else
    say "→ cloning $REPO_SSH → $CHECKOUT"
    git clone "$REPO_SSH" "$CHECKOUT"
  fi
}

stage_secrets() {
  # Empty stubs, mode 600 — the same five files + chmod discipline as
  # `task outpost:secrets-stub`, but EMPTY (never stub values: this is a
  # real host, and the empty/non-empty state drives the fork below). The
  # values themselves are yours to fill — this script never generates one.
  if [ "$CHECK" = 1 ]; then
    would "mkdir -p $SECRETS_DIR and touch the five secret stubs (mode 600) iff absent: $SECRET_FILES"
    return 0
  fi
  mkdir -p "$SECRETS_DIR"
  local name
  for name in $SECRET_FILES; do
    if [ ! -f "$SECRETS_DIR/$name" ]; then
      # umask 177 closes the create-then-chmod perm window (the stub is
      # empty, so nothing could leak — belt and suspenders).
      (umask 177; : > "$SECRETS_DIR/$name")
      chmod 600 "$SECRETS_DIR/$name"
      say "→ staged empty secret stub: container/secrets/$name"
    else
      ok "secret file exists: container/secrets/$name"
    fi
  done
}

# The three secrets oauth2-proxy cannot boot without. kin-settings and
# vapid-private-key are OPTIONAL (start.sh treats empty as absent for both),
# so they don't gate the compose-up fork.
required_secrets_filled() {
  local name
  for name in oauth-client-id oauth-client-secret oauth-cookie-secret; do
    if [ ! -s "$SECRETS_DIR/$name" ]; then
      return 1
    fi
  done
  return 0
}

print_secrets_checklist() {
  say ""
  say "── the Outpost is STAGED — fill the secrets, then re-run this script ──"
  say ""
  say "1. Create an OIDC client in your Pocket ID instance (auth.kinra.ai for"
  say "   the kinra deployment), callback URL:"
  say "     https://<your-outpost-host>/oauth2/callback"
  say "   then drop the values into $SECRETS_DIR/:"
  say "     oauth-client-id       ← the OIDC client UUID"
  say "     oauth-client-secret   ← the OIDC client secret"
  say "     oauth-cookie-secret   ← 32 hex chars, no trailing newline:"
  say "                             openssl rand -hex 16 | tr -d '\\n' > $SECRETS_DIR/oauth-cookie-secret"
  say "   (optional) kin-settings       ← seeds ~/.kin/settings.toml in the box"
  say "   (optional) vapid-private-key  ← PEM EC key for Web Push; empty = dormant"
  say ""
  say "2. Environment for the compose up (or export before re-running):"
  say "     OUTPOST_PUBLIC_URL     — the public URL (e.g. https://outpost.example.com)"
  say "     OUTPOST_BIND_IP        — where :4180 binds (default 127.0.0.1; use the"
  say "                              WireGuard/tailnet IP your proxy reaches)"
  say "     OUTPOST_ALLOWED_EMAILS — the email allowlist (keep it narrow)"
  say ""
  say "3. Routing stays manual: point your reverse proxy (Traefik/nginx/Caddy)"
  say "   + DNS at <OUTPOST_BIND_IP>:4180. This script never touches them."
  say ""
  say "Docs: https://docs.kinra.ai/guide/outpost/  (or the repo copy:"
  say "      https://github.com/kinra-ai/kin-textual/blob/main/docs/guide/outpost.md)"
}

compose_up_and_wait() {
  if [ "$CHECK" = 1 ]; then
    would "docker compose -f $COMPOSE_FILE up -d --build (NO --profile publish — the get/docs static services are kloud-only), then poll the container healthcheck ≤90s"
    return 0
  fi
  say "→ docker compose up (this builds the image on first run — a few minutes)"
  docker compose -f "$COMPOSE_FILE" up -d --build
  say "→ waiting for the container healthcheck (dual probe; start_period 30s)…"
  local i st="" healthy=""
  for i in $(seq 1 30); do
    st="$(docker inspect -f '{{.State.Health.Status}}' outpost 2>/dev/null || echo unknown)"
    if [ "$st" = "healthy" ]; then healthy=1; break; fi
    sleep 3
  done
  if [ -z "$healthy" ]; then
    fail "the outpost container never went healthy in ~90s (last state: $st) — recent logs:"
    docker compose -f "$COMPOSE_FILE" logs --tail 50 outpost >&2 || true
    return 1
  fi
  say "✓ outpost healthy after ~$((i * 3))s"
  say "  route your reverse proxy + DNS at :4180 if you haven't (see docs.kinra.ai/guide/outpost/)"
}

main() {
  case "${1:-}" in
    --check) CHECK=1 ;;
    "") ;;
    *) fail "unknown flag '$1' (only --check is supported)"; return 1 ;;
  esac
  if [ -z "${HOME:-}" ]; then
    fail "HOME is not set — run this as a normal user with a home directory"
    return 1
  fi
  check_platform_docker
  check_ssh_key
  ensure_checkout
  stage_secrets
  if [ "$CHECK" = 1 ]; then
    would "fork on secrets state: all three oauth secrets non-empty → compose up + health poll; any empty → print the manual secrets checklist and exit 0 (staged is the designed success state)"
    say ""
    say "check walk complete — nothing was changed."
    return 0
  fi
  if required_secrets_filled; then
    compose_up_and_wait
  else
    print_secrets_checklist
  fi
}

# The main-at-EOF wrapper: nothing executes until the full script arrived.
main "$@"
