#!/usr/bin/env bash
# shellcheck shell=bash
#
# skuld-installer — operator VM installer for the Skuld Platform Directory Operator
#
# Usage (copied from the operator UI's "Add Operator" modal — never hand-typed):
#     curl -fsSL https://install.skuld.sh | bash -s <enrollment-token>
#
# Source:   https://github.com/skuld-platform/skuld-installer
# License:  Proprietary — Skuld Platform
# Build:    sha=c5984703 date=2026-05-19T22:26:41Z   # replaced by CI before deploy to CF Pages
#
# ADR-010 (operator VM substrate) and ADR-012 (this repo).

set -euo pipefail

# ── Build-time metadata ──────────────────────────────────────────
SKULD_INSTALLER_BUILD_SHA="${SKULD_INSTALLER_BUILD_SHA:-dev}"
SKULD_INSTALLER_BUILD_DATE="${SKULD_INSTALLER_BUILD_DATE:-dev}"

# ── Configuration (overridable via env for staging tests) ───────
SKULD_API_BASE_URL="${SKULD_API_BASE_URL:-https://control.skuld.sh}"
SKULD_INSTALL_DIR="${SKULD_INSTALL_DIR:-/etc/skuld}"
SKULD_STATE_DIR="${SKULD_STATE_DIR:-/var/lib/skuld}"
SKULD_OPERATOR_USER="${SKULD_OPERATOR_USER:-skuld-operator}"

# ── Pretty output ────────────────────────────────────────────────
RED=$'\033[0;31m'
GREEN=$'\033[0;32m'
YELLOW=$'\033[0;33m'
BOLD=$'\033[1m'
RESET=$'\033[0m'

log()  { printf '%b\n' "${BOLD}»${RESET} $*"; }
ok()   { printf '%b\n' "${GREEN}✔${RESET} $*"; }
warn() { printf '%b\n' "${YELLOW}!${RESET} $*" >&2; }
die()  { printf '%b\n' "${RED}✗${RESET} $*" >&2; exit 1; }

# ── Args ─────────────────────────────────────────────────────────
TOKEN="${1:-}"
if [[ -z "$TOKEN" ]]; then
  die "missing enrollment token. Copy the full command from the 'Add Operator' modal in the Skuld UI."
fi

# ── Banner ───────────────────────────────────────────────────────
cat <<EOF
${BOLD}Skuld Platform — Operator VM Installer${RESET}
build: ${SKULD_INSTALLER_BUILD_SHA} (${SKULD_INSTALLER_BUILD_DATE})
api:   ${SKULD_API_BASE_URL}

EOF

# ── Pre-flight: root ─────────────────────────────────────────────
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
  die "this installer must run as root (\`sudo bash\` or root shell)."
fi

# ── Pre-flight: OS (Kali only, per issue #7) ────────────────────
if [[ -r /etc/os-release ]]; then
  # shellcheck disable=SC1091
  . /etc/os-release
fi
if [[ "${ID:-}" != "kali" ]]; then
  cat <<EOF >&2
${RED}✗${RESET} unsupported OS: ${PRETTY_NAME:-unknown}

The Skuld operator VM currently supports Kali Linux only (rolling channel).
See https://guide.skuld.sh/operator-os-support for the list of supported
distros and the roadmap for additional OS targets.
EOF
  exit 1
fi
ok "OS check: ${PRETTY_NAME}"

# ── Pre-flight: outbound 443 to *.skuld.sh ──────────────────────
if ! curl -fsS --max-time 10 -o /dev/null "${SKULD_API_BASE_URL}/api/v1/health"; then
  die "cannot reach ${SKULD_API_BASE_URL}/api/v1/health — verify outbound 443 to *.skuld.sh is allowed."
fi
ok "control plane reachable"

# ── Pre-flight: required binaries ───────────────────────────────
REQUIRED_BINARIES=(curl jq openssl systemctl sha256sum awk)
MISSING=()
for bin in "${REQUIRED_BINARIES[@]}"; do
  command -v "$bin" >/dev/null 2>&1 || MISSING+=("$bin")
done
if (( ${#MISSING[@]} > 0 )); then
  die "missing required binaries: ${MISSING[*]}. Install via \`apt install ${MISSING[*]}\`."
fi
ok "required binaries present"

# ── Preflight API call: display context to user ─────────────────
log "fetching enrollment context…"
PREFLIGHT_RESPONSE="$(
  curl -fsS --max-time 15 \
    --data-urlencode "token=${TOKEN}" \
    -G "${SKULD_API_BASE_URL}/api/v1/operators/enroll/preflight"
)" || die "preflight request failed. Token may be invalid, expired, or already consumed."

ORG_NAME="$(jq -r '.organizationName' <<<"$PREFLIGHT_RESPONSE")"
PROJECT_NAME="$(jq -r '.projectName' <<<"$PREFLIGHT_RESPONSE")"
PROJECT_CODE="$(jq -r '.projectCode' <<<"$PREFLIGHT_RESPONSE")"
LABEL="$(jq -r '.label' <<<"$PREFLIGHT_RESPONSE")"
EXPIRES_AT="$(jq -r '.expiresAt' <<<"$PREFLIGHT_RESPONSE")"

cat <<EOF

${BOLD}You are about to enroll this VM as a Skuld operator:${RESET}

  Organization: ${ORG_NAME}
  Project:      ${PROJECT_NAME} (${PROJECT_CODE})
  Label:        ${LABEL}
  Token TTL:    until ${EXPIRES_AT}

EOF
# Consent gate. Three paths:
#   1. SKULD_AUTO_CONFIRM=1   — unattended (CI, automation). Explicit env opt-in.
#   2. interactive shell      — read from current stdin as usual.
#   3. curl-pipe (stdin = script body), /dev/tty available — read from terminal.
# If none of those is true (curl-pipe with no controlling tty), refuse.
if [[ "${SKULD_AUTO_CONFIRM:-}" == "1" || "${SKULD_AUTO_CONFIRM:-}" == "yes" ]]; then
  ok "auto-confirm via SKULD_AUTO_CONFIRM"
elif [ -t 0 ]; then
  read -r -p "Continue? [y/N] " CONFIRM
  [[ "${CONFIRM,,}" == "y" || "${CONFIRM,,}" == "yes" ]] || die "aborted by user."
elif [ -r /dev/tty ]; then
  read -r -p "Continue? [y/N] " CONFIRM < /dev/tty
  [[ "${CONFIRM,,}" == "y" || "${CONFIRM,,}" == "yes" ]] || die "aborted by user."
else
  die "no controlling terminal and SKULD_AUTO_CONFIRM not set — refusing to enroll without explicit consent. Pass SKULD_AUTO_CONFIRM=1 for unattended installs."
fi

# ── Side effects start here ─────────────────────────────────────

log "creating install directories"
install -d -m 0700 "$SKULD_INSTALL_DIR"
install -d -m 0700 "$SKULD_STATE_DIR"
ok "directories ready"

# ── Machine fingerprint (ADR-010 §4.1) ──────────────────────────
log "computing machine fingerprint"
if [[ -r /etc/machine-id ]]; then
  FP_SOURCE="$(cat /etc/machine-id)"
elif [[ -r /sys/class/dmi/id/product_uuid ]]; then
  FP_SOURCE="$(cat /sys/class/dmi/id/product_uuid)"
else
  die "no source for machine fingerprint (/etc/machine-id and /sys/class/dmi/id/product_uuid both missing)."
fi
MACHINE_FINGERPRINT="$(printf '%s' "$FP_SOURCE" | sha256sum | awk '{print $1}')"
ok "fingerprint: ${MACHINE_FINGERPRINT:0:12}…"

# ── ed25519 keypair (ADR-010 §4.1) ──────────────────────────────
KEY_PATH="${SKULD_INSTALL_DIR}/operator.key"
PUB_PATH="${KEY_PATH}.pub"
if [[ -e "$KEY_PATH" ]]; then
  warn "keypair already exists at $KEY_PATH — keeping it (re-enroll path)."
else
  log "generating ed25519 keypair"
  ssh-keygen -t ed25519 -N '' -C "skuld-operator-${MACHINE_FINGERPRINT:0:8}" -f "$KEY_PATH" >/dev/null
  chmod 0600 "$KEY_PATH"
  ok "keypair written to $KEY_PATH"
fi
PUBKEY="$(cat "$PUB_PATH")"

# ── Enroll ──────────────────────────────────────────────────────
log "submitting enrollment request"
ENROLL_PAYLOAD="$(
  jq -n \
    --arg token "$TOKEN" \
    --arg fp "$MACHINE_FINGERPRINT" \
    --arg pubkey "$PUBKEY" \
    '{token: $token, machineFingerprint: $fp, publicKey: $pubkey}'
)"
ENROLL_RESPONSE="$(
  curl -fsS --max-time 30 \
    -H 'Content-Type: application/json' \
    -X POST \
    --data "$ENROLL_PAYLOAD" \
    "${SKULD_API_BASE_URL}/api/v1/operators/enroll"
)" || die "enroll request failed."

OPERATOR_ID="$(jq -r '.operatorId' <<<"$ENROLL_RESPONSE")"
GATEWAY_URL="$(jq -r '.gatewayUrl' <<<"$ENROLL_RESPONSE")"
MTLS_P12_B64="$(jq -r '.mtlsP12Base64' <<<"$ENROLL_RESPONSE")"
MTLS_P12_PASS="$(jq -r '.mtlsP12Password' <<<"$ENROLL_RESPONSE")"
CA_BUNDLE="$(jq -r '.caBundlePem' <<<"$ENROLL_RESPONSE")"

# TODO(sprint-1-follow-up): once the API ships real PKI, decode the p12 and
# write it to disk with 0600 perms. The next PR adds: openssl pkcs12 -in
# operator.p12 -out client.pem (cert+chain) + client.key (key) for the WSS
# client to consume. For now we capture the placeholders so the contract
# is exercised end-to-end.
printf '%s' "$MTLS_P12_B64"  > "${SKULD_INSTALL_DIR}/operator.p12.b64"
printf '%s' "$MTLS_P12_PASS" > "${SKULD_INSTALL_DIR}/operator.p12.pass"
printf '%s' "$CA_BUNDLE"     > "${SKULD_INSTALL_DIR}/skuld-ca.pem"
chmod 0600 "${SKULD_INSTALL_DIR}/operator.p12.b64" "${SKULD_INSTALL_DIR}/operator.p12.pass"

cat > "${SKULD_INSTALL_DIR}/operator.env" <<EOF
SKULD_OPERATOR_ID=${OPERATOR_ID}
SKULD_GATEWAY_URL=${GATEWAY_URL}
SKULD_MACHINE_FINGERPRINT=${MACHINE_FINGERPRINT}
EOF
chmod 0600 "${SKULD_INSTALL_DIR}/operator.env"
ok "enrolled — operator id ${OPERATOR_ID}"

# ── systemd unit (placeholder; runtime binary lands in sprint 2) ─
# TODO(sprint-2): replace the placeholder ExecStart with the real
# skuld-operator-directory binary path. Sprint 1's job is end-to-end
# enrollment; the runtime binary itself is sprint 2.
cat > /etc/systemd/system/skuld-operator-directory.service <<'EOF'
[Unit]
Description=Skuld Platform — Directory Operator
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
EnvironmentFile=/etc/skuld/operator.env
# Sprint 1: placeholder so the unit exists and is disabled by default.
# Sprint 2 replaces this with the real ExecStart.
ExecStart=/bin/sh -c 'echo "skuld-operator-directory: runtime binary not yet installed (sprint 2)"; sleep infinity'
Restart=on-failure
RestartSec=15
NoNewPrivileges=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/var/lib/skuld /etc/skuld

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
ok "systemd unit installed (disabled until sprint 2 ships the runtime)"

cat <<EOF

${GREEN}${BOLD}Enrollment complete.${RESET}

  Operator ID:   ${OPERATOR_ID}
  Gateway URL:   ${GATEWAY_URL}
  Config:        ${SKULD_INSTALL_DIR}/operator.env
  State dir:     ${SKULD_STATE_DIR}

The operator runtime binary lands in Sprint 2; the systemd unit is installed
but disabled. You may close this terminal — the next step ("start engagement"
in the Skuld UI) will dispatch work to this operator once the runtime is in
place.

EOF
