#!/usr/bin/env bash
if [ -z "${BASH_VERSION:-}" ]; then
  if command -v bash >/dev/null 2>&1; then
    exec bash -s -- "$@"
  fi
  echo "error: bash is required to run this installer"
  exit 1
fi
set -euo pipefail

export DEBIAN_FRONTEND="${DEBIAN_FRONTEND:-noninteractive}"
export PYTHONWARNINGS="${PYTHONWARNINGS:-ignore::SyntaxWarning}"

COORDINATOR_URL=""
ENROLL_TOKEN=""
NODE_NAME=""
EXIT_NODE="false"
REPO_URL="https://repo.rizomarl.com/mesh"
CHANNEL="stable"
PROFILE="mesh"
RUNTIME_MODE="host"
ENABLE_FIREWALL="false"
MIN_ROUTER_PROFILE_VERSION="0.1.77"
IDENTITY_PATH="/etc/rizoma/agent_identity.json"
COMPATIBILITY_REPORT_PATH="/var/lib/rizoma/agent/install_compatibility.json"
REUSE_EXISTING_IDENTITY="false"
FORCE_REENROLL="false"
AGENT_HTTP_LISTEN="${RIZOMA_AGENT_LISTEN:-127.0.0.1:8081}"
# 0.9.250.0 Track B: 3-layer enrollment handshake.
# --session-id is the implicit auth on /v1/install/* endpoints
# (a 128-bit UUID generated by the dashboard). The auth_key is
# NOT accepted as a CLI arg (would defeat the 60s-TTL "operator
# presence" guarantee) — the script prompts for it after
# signalling /v1/install/ready, and the operator pastes the
# value from the dashboard's Add Peer modal.
INSTALL_SESSION_ID=""
INSTALL_AUTH_KEY=""
INSTALL_FINAL_NODE_ID=""
INSTALL_LIFECYCLE_FAILED="false"
INSTALL_LIFECYCLE_DONE="false"
COMPATIBILITY_CONTRACT_VERSION="agent-install-compatibility.v1"
COMPATIBILITY_CHECKED_AT=""
COMPATIBILITY_STATUS="supported"
COMPATIBILITY_WARNINGS=()
COMPATIBILITY_ERRORS=()
COMPAT_OS_ID=""
COMPAT_OS_ID_LIKE=""
COMPAT_OS_VERSION_ID=""
COMPAT_OS_PRETTY_NAME=""
COMPAT_KERNEL=""
COMPAT_ARCHITECTURE=""
COMPAT_PACKAGE_MANAGER=""
COMPAT_OS_PROFILE_ID=""
COMPAT_CERTIFICATION="untested"
COMPAT_SYSTEMD="false"
COMPAT_SYSTEMD_VERSION=""
COMPAT_TUN_AVAILABLE="false"
COMPAT_NFTABLES_AVAILABLE="false"
COMPAT_NFTABLES_USABLE="false"
COMPAT_RUNTIME_PATHS_JSON=()
COMPAT_FEATURES_JSON=()
ROUTER_PREFLIGHT_OK="false"
ROUTER_PREFLIGHT_PRODUCT=""
ROUTER_PREFLIGHT_VERSION=""
ROUTER_PREFLIGHT_INSTANCE_ID=""
ROUTER_PREFLIGHT_SERVICE=""
ROUTER_PREFLIGHT_INTEGRATION_VERSION=""
ROUTER_PREFLIGHT_SUPPORTS_GATEWAY="false"
ROUTER_PREFLIGHT_BUILD_COMMIT=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    --coordinator-url)
      COORDINATOR_URL="$2"
      shift 2
      ;;
    --token)
      ENROLL_TOKEN="$2"
      shift 2
      ;;
    --name)
      NODE_NAME="$2"
      shift 2
      ;;
    --exit-node)
      EXIT_NODE="true"
      shift
      ;;
    --profile)
      PROFILE="$2"
      shift 2
      ;;
    --runtime-mode|--runtime)
      RUNTIME_MODE="$2"
      shift 2
      ;;
    --enable-firewall)
      ENABLE_FIREWALL="true"
      shift
      ;;
    --repo-url)
      REPO_URL="$2"
      shift 2
      ;;
    --channel)
      CHANNEL="$2"
      shift 2
      ;;
    --identity)
      IDENTITY_PATH="$2"
      shift 2
      ;;
    --compatibility-report)
      COMPATIBILITY_REPORT_PATH="$2"
      shift 2
      ;;
    --reuse-existing-identity)
      REUSE_EXISTING_IDENTITY="true"
      shift
      ;;
    --force-reenroll)
      FORCE_REENROLL="true"
      shift
      ;;
    --session-id)
      INSTALL_SESSION_ID="$2"
      shift 2
      ;;
    --auth-key)
      # 0.9.250.0 Track B: the auth_key is normally
      # prompted interactively. Accepting it as a CLI
      # arg is supported for unattended / scripted
      # installs only (the operator is then responsible
      # for the TTL window themselves).
      INSTALL_AUTH_KEY="$2"
      shift 2
      ;;
    *)
      shift
      ;;
  esac
done

if [[ -z "$COORDINATOR_URL" ]]; then
  echo "error: --coordinator-url is required"
  exit 1
fi

if [[ -z "$ENROLL_TOKEN" ]]; then
  echo "error: --token is required"
  exit 1
fi

if [[ -z "$IDENTITY_PATH" ]]; then
  echo "error: --identity cannot be empty"
  exit 1
fi

if [[ -z "$COMPATIBILITY_REPORT_PATH" ]]; then
  echo "error: --compatibility-report cannot be empty"
  exit 1
fi

# 0.9.250.0 Track B: lifecycle helper functions for the
# install-script <-> dashboard handshake. All helpers
# silently no-op when INSTALL_SESSION_ID is empty (e.g.,
# when the operator ran the script outside the Add Peer
# flow, the legacy pre-0.9.250.0 behavior).
install_lifecycle_call() {
  local endpoint="$1"
  local body="$2"
  if [[ -z "$INSTALL_SESSION_ID" ]]; then
    return 0
  fi
  if ! command -v curl >/dev/null 2>&1; then
    echo "warning: install_lifecycle_${endpoint}: curl not found, skipping dashboard handshake" >&2
    return 0
  fi
  local url="${COORDINATOR_URL%/}/v1/install/${endpoint}"
  local tmp_response
  tmp_response="$(mktemp /tmp/rizoma-install.XXXXXX 2>/dev/null || echo /dev/null)"
  local http_code=0
  local curl_rc=0
  # 0.9.250.1: -f removed (it would silently fail on 4xx/5xx).
  # We capture the HTTP status code via -w and log a warning
  # if the call failed so the operator can see what went wrong.
  # The install continues regardless — the dashboard signal is
  # informational; the actual enrollment uses a separate path.
  http_code="$(curl -sS --max-time 10 \
    -H "Content-Type: application/json" \
    -X POST \
    -w '%{http_code}' \
    --data "$body" \
    "$url" \
    -o "$tmp_response" 2>&1)" || curl_rc=$?
  if [[ -n "$tmp_response" && "$tmp_response" != "/dev/null" ]]; then
    rm -f "$tmp_response" 2>/dev/null || true
  fi
  if [[ "$curl_rc" -ne 0 || "$http_code" -ge 400 ]]; then
    echo "warning: install_lifecycle_${endpoint} failed" >&2
    echo "  url: $url" >&2
    echo "  http_code: $http_code" >&2
    echo "  curl_rc: $curl_rc" >&2
    if [[ -n "${INSTALL_AUTH_KEY:-}" || "$endpoint" == "ready" || "$endpoint" == "start" ]]; then
      echo "  hint: the dashboard's Add Peer modal may stay in 'waiting' state" >&2
      echo "  hint: check that the ingress routes /v1/install/* correctly" >&2
    fi
    # We continue — the install will still try to enroll, the
    # agent will just be missing the dashboard's "ready" signal.
  fi
  return 0
}

install_lifecycle_start() {
  if [[ -z "$INSTALL_SESSION_ID" ]]; then
    return 0
  fi
  install_lifecycle_call "start" "$(cat <<EOF
{"session_id":"$(json_escape "$INSTALL_SESSION_ID")","hostname":"$(json_escape "$NODE_NAME")","created_via":"agent_sh"}
EOF
)"
}

install_lifecycle_ready() {
  if [[ -z "$INSTALL_SESSION_ID" ]]; then
    return 0
  fi
  install_lifecycle_call "ready" "$(cat <<EOF
{"session_id":"$(json_escape "$INSTALL_SESSION_ID")"}
EOF
)"
}

install_lifecycle_complete() {
  local node_id="$1"
  if [[ -z "$INSTALL_SESSION_ID" ]]; then
    return 0
  fi
  INSTALL_FINAL_NODE_ID="$node_id"
  install_lifecycle_call "complete" "$(cat <<EOF
{"session_id":"$(json_escape "$INSTALL_SESSION_ID")","node_id":"$(json_escape "$node_id")"}
EOF
)"
  INSTALL_LIFECYCLE_DONE="true"
}

install_lifecycle_failed() {
  local reason="$1"
  if [[ -z "$INSTALL_SESSION_ID" ]]; then
    return 0
  fi
  if [[ "$INSTALL_LIFECYCLE_DONE" == "true" ]]; then
    return 0
  fi
  if [[ "$INSTALL_LIFECYCLE_FAILED" == "true" ]]; then
    return 0
  fi
  INSTALL_LIFECYCLE_FAILED="true"
  install_lifecycle_call "failed" "$(cat <<EOF
{"session_id":"$(json_escape "$INSTALL_SESSION_ID")","reason":"$(json_escape "$reason")"}
EOF
)"
}

# 0.9.250.0 Track B: trap any unexpected exit to clean
# up the pending_installs row (the "no zombies" rule).
# ERR/EXIT fires for `set -e` failures and explicit exit.
# We swallow the helper's curl failures inside the
# helper itself so the trap never aborts the actual
# error path the operator is on.
trap_install_lifecycle_failure() {
  trap 'install_lifecycle_failed "script_exit"' EXIT
}

# 0.9.250.0 Track B: prompt for the auth_key if not
# already provided. Reads from RIZOMA_AGENT_AUTH_KEY env
# var first (so an unattended install can pre-set it),
# then falls back to an interactive read.
#
# 0.9.250.2 (Track B hotfix): when run via
# `curl URL | bash -s -- args`, stdin is the pipe from
# curl (not a TTY), but /dev/tty IS the operator's
# terminal. The previous check `[[ ! -t 0 && ! -r
# /dev/stdin ]]` was wrong — /dev/stdin is readable
# (from the pipe), so the check passed and `read`
# blocked forever waiting on the pipe. Fix: when
# stdin is not a TTY, read from /dev/tty instead.
# If /dev/tty is also not available (non-interactive
# context like cron), error out with a clear message
# telling the operator to use --auth-key or the env
# var. Added a 180s timeout so a forgotten/abandoned
# install fails loud instead of hanging silently.
prompt_for_auth_key() {
  if [[ -n "$INSTALL_AUTH_KEY" ]]; then
    return 0
  fi
  if [[ -n "${RIZOMA_AGENT_AUTH_KEY:-}" ]]; then
    INSTALL_AUTH_KEY="$RIZOMA_AGENT_AUTH_KEY"
    return 0
  fi

  # Pick the right fd for the interactive read.
  # - stdin is a TTY (e.g. `bash agent.sh` directly):
  #     read from fd 0 (normal)
  # - stdin is a pipe (e.g. `curl ... | bash`):
  #     read from /dev/tty (the operator's terminal)
  # - neither (e.g. cron, systemd ExecStart=):
  #     error out — operator must pre-set the key
  local read_source="stdin"
  if [[ ! -t 0 ]]; then
    # -r /dev/tty succeeds only if the process has a
    # controlling terminal. -t 1 is NOT checked (the
    # operator may have redirected stdout to a file
    # while still being at a terminal).
    if [[ -r /dev/tty ]]; then
      read_source="/dev/tty"
    else
      echo "error: --auth-key (or RIZOMA_AGENT_AUTH_KEY env) is required when stdin is not a TTY and no controlling terminal is available" >&2
      echo "  hint: pass --auth-key 'ak_XXXXXXXXXXXXXXXX' to the install command" >&2
      echo "  hint: or set RIZOMA_AGENT_AUTH_KEY before running" >&2
      return 1
    fi
  fi

  echo ""
  echo "  ====================================================================="
  echo "  ONE-TIME AUTH KEY REQUIRED"
  echo "  ====================================================================="
  echo "  1. Switch to the dashboard (in your browser) — leave this SSH"
  echo "     session open and switch back here AFTER step 4."
  echo "  2. The Add Peer modal should now show 'Installer is ready'."
  echo "  3. Click the 'Generate key' button — the key appears with a 60s"
  echo "     countdown. Copy it (the copy button is next to the key)."
  echo "  4. Switch back here and paste the key at the prompt below."
  echo "  ====================================================================="
  echo ""
  echo "  The key is 19 characters, starts with 'ak_'."
  echo "  If the dashboard doesn't show 'Generate', wait a few seconds"
  echo "  and check the modal state — the install state may not have"
  echo "  synced yet (poll runs every 1-2s)."
  echo ""

  local raw=""
  # 0.9.250.2: -t 180 timeout. If the operator walks away
  # for >3 min, the script fails loud instead of hanging
  # silently forever. The trap then calls
  # install_lifecycle_failed "auth_key_prompt_timeout" so
  # the dashboard marks the session as expired.
  if [[ "$read_source" == "/dev/tty" ]]; then
    read -r -t 180 -p "  auth_key> " raw < /dev/tty || {
      local rc=$?
      echo "" >&2
      if [[ $rc -eq 142 ]]; then
        echo "error: auth_key prompt timed out after 180s — the dashboard session has expired" >&2
        echo "  hint: restart the install from the dashboard's Add Peer modal" >&2
      else
        echo "error: could not read auth_key from /dev/tty (rc=$rc)" >&2
      fi
      return 1
    }
  else
    read -r -t 180 -p "  auth_key> " raw || {
      local rc=$?
      echo "" >&2
      if [[ $rc -eq 142 ]]; then
        echo "error: auth_key prompt timed out after 180s — the dashboard session has expired" >&2
        echo "  hint: restart the install from the dashboard's Add Peer modal" >&2
      else
        echo "error: could not read auth_key (rc=$rc)" >&2
      fi
      return 1
    }
  fi
  raw="${raw// /}"
  if [[ ! "$raw" =~ ^ak_[A-Z0-9]{16}$ ]]; then
    echo "" >&2
    echo "error: auth_key format is invalid (expected 'ak_' + 16 chars from [A-Z0-9])" >&2
    echo "  hint: open the dashboard's Add Peer modal, click Generate," >&2
    echo "  copy the key, and paste it here. The key auto-copies when" >&2
    echo "  you click the copy button next to it." >&2
    return 1
  fi
  INSTALL_AUTH_KEY="$raw"
  return 0
}

if [[ "$REUSE_EXISTING_IDENTITY" == "true" && "$FORCE_REENROLL" == "true" ]]; then
  echo "error: choose either --reuse-existing-identity or --force-reenroll, not both"
  exit 1
fi

if [[ -z "$NODE_NAME" ]]; then
  NODE_NAME="$(hostname)"
fi

case "$PROFILE" in
  mesh)
    ;;
  firewall)
    ENABLE_FIREWALL="true"
    ;;
  security)
    ENABLE_FIREWALL="true"
    ;;
  router)
    ENABLE_FIREWALL="false"
    ;;
  *)
    echo "error: unsupported profile '$PROFILE' (expected: mesh, firewall, security, router)"
    exit 1
    ;;
esac

case "$RUNTIME_MODE" in
  host)
    ;;
  *)
    echo "error: unsupported runtime mode '$RUNTIME_MODE' (expected: host)"
    exit 1
    ;;
esac

if [[ "$(id -u)" -ne 0 ]]; then
  if command -v sudo >/dev/null 2>&1; then
    SUDO="sudo"
  elif command -v doas >/dev/null 2>&1; then
    SUDO="doas"
  else
    echo "error: run as root or install sudo/doas"
    exit 1
  fi
else
  SUDO=""
fi

run_root() {
  if [[ -n "${SUDO:-}" ]]; then
    "$SUDO" "$@"
  else
    "$@"
  fi
}

write_root_file() {
  local path="$1"
  local mode="$2"
  local tmp
  tmp="$(mktemp /tmp/rizoma-write.XXXXXX)"
  cat > "$tmp"
  run_root install -m "$mode" "$tmp" "$path"
  rm -f "$tmp"
}

json_escape() {
  local value="${1:-}"
  value="${value//\\/\\\\}"
  value="${value//\"/\\\"}"
  value="${value//$'\n'/\\n}"
  value="${value//$'\r'/\\r}"
  value="${value//$'\t'/\\t}"
  printf '%s' "$value"
}

json_string_array() {
  local array_name="$1"
  local first="true"
  local item
  declare -n values="$array_name"
  printf '['
  for item in "${values[@]}"; do
    item="$(printf '%s' "$item" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
    if [[ -z "$item" ]]; then
      continue
    fi
    if [[ "$first" != "true" ]]; then
      printf ','
    fi
    printf '"%s"' "$(json_escape "$item")"
    first="false"
  done
  printf ']'
}

json_object_array() {
  local array_name="$1"
  local first="true"
  local item
  declare -n values="$array_name"
  printf '['
  for item in "${values[@]}"; do
    if [[ -z "$item" ]]; then
      continue
    fi
    if [[ "$first" != "true" ]]; then
      printf ','
    fi
    printf '%s' "$item"
    first="false"
  done
  printf ']'
}

compat_warning() {
  local message
  local existing
  message="$(printf '%s' "$*" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
  if [[ -n "$message" ]]; then
    for existing in "${COMPATIBILITY_WARNINGS[@]}"; do
      if [[ "$existing" == "$message" ]]; then
        return
      fi
    done
    COMPATIBILITY_WARNINGS+=("$message")
  fi
}

compat_error() {
  local message
  local existing
  message="$(printf '%s' "$*" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
  if [[ -n "$message" ]]; then
    for existing in "${COMPATIBILITY_ERRORS[@]}"; do
      if [[ "$existing" == "$message" ]]; then
        return
      fi
    done
    COMPATIBILITY_ERRORS+=("$message")
  fi
}

compatibility_status() {
  if [[ "${#COMPATIBILITY_ERRORS[@]}" -gt 0 ]]; then
    printf 'ineligible'
  elif [[ "${#COMPATIBILITY_WARNINGS[@]}" -gt 0 ]]; then
    printf 'degraded'
  else
    printf 'supported'
  fi
}

json_bool() {
  if [[ "${1:-}" == "true" ]]; then
    printf 'true'
  else
    printf 'false'
  fi
}

compat_runtime_path() {
  local path="$1"
  local required="$2"
  local purpose="$3"
  local exists="false"
  local status
  if [[ -e "$path" ]]; then
    exists="true"
    status="present"
  elif [[ "$required" == "true" ]]; then
    status="missing_required"
  else
    status="missing_optional"
  fi
  COMPAT_RUNTIME_PATHS_JSON+=("$(cat <<EOF
{"path":"$(json_escape "$path")","required":$(json_bool "$required"),"exists":$(json_bool "$exists"),"status":"$status","purpose":"$(json_escape "$purpose")"}
EOF
)")
}

compat_feature() {
  local id="$1"
  local label="$2"
  local status="$3"
  local reason="${4:-}"
  COMPAT_FEATURES_JSON+=("$(cat <<EOF
{"id":"$(json_escape "$id")","label":"$(json_escape "$label")","status":"$(json_escape "$status")","reason":"$(json_escape "$reason")"}
EOF
)")
}

detect_package_manager() {
  if command -v apt-get >/dev/null 2>&1; then
    printf 'apt'
  elif command -v dnf >/dev/null 2>&1; then
    printf 'dnf'
  elif command -v yum >/dev/null 2>&1; then
    printf 'yum'
  else
    printf 'unknown'
  fi
}

normalize_architecture() {
  case "$(uname -m 2>/dev/null || echo unknown)" in
    x86_64|amd64)
      printf 'amd64'
      ;;
    aarch64|arm64)
      printf 'arm64'
      ;;
    armv7l|armv7hl)
      printf 'armv7'
      ;;
    *)
      uname -m 2>/dev/null || echo unknown
      ;;
  esac
}

detect_os_release() {
  if [[ -r /etc/os-release ]]; then
    # /etc/os-release is defined as shell-compatible assignment syntax.
    # shellcheck disable=SC1091
    . /etc/os-release
    COMPAT_OS_ID="${ID:-}"
    COMPAT_OS_ID_LIKE="${ID_LIKE:-}"
    COMPAT_OS_VERSION_ID="${VERSION_ID:-}"
    COMPAT_OS_PRETTY_NAME="${PRETTY_NAME:-}"
  fi
}

resolve_os_profile() {
  local os_id os_like os_version
  os_id="$(printf '%s' "$COMPAT_OS_ID" | tr '[:upper:]' '[:lower:]')"
  os_like="$(printf '%s' "$COMPAT_OS_ID_LIKE" | tr '[:upper:]' '[:lower:]')"
  os_version="$COMPAT_OS_VERSION_ID"
  COMPAT_CERTIFICATION="untested"

  case "$os_id:$os_version" in
    ubuntu:22.04)
      COMPAT_OS_PROFILE_ID="ubuntu-22.04"
      COMPAT_CERTIFICATION="certified"
      ;;
    ubuntu:24.04)
      COMPAT_OS_PROFILE_ID="ubuntu-24.04"
      COMPAT_CERTIFICATION="certified"
      ;;
    ubuntu:26.04)
      COMPAT_OS_PROFILE_ID="ubuntu-26.04"
      COMPAT_CERTIFICATION="compatible"
      ;;
    debian:12)
      COMPAT_OS_PROFILE_ID="debian-12"
      COMPAT_CERTIFICATION="compatible"
      ;;
    redos:*|rhel:*|rocky:*|almalinux:*|fedora:*)
      COMPAT_OS_PROFILE_ID="${os_id}-${os_version%%.*}"
      COMPAT_CERTIFICATION="compatible"
      ;;
    *)
      if [[ "$os_like" == *debian* || "$os_like" == *ubuntu* || "$os_id" == "ubuntu" || "$os_id" == "debian" ]]; then
        COMPAT_OS_PROFILE_ID="unknown-debian-like"
      elif [[ "$os_like" == *rhel* || "$os_like" == *fedora* || "$os_id" == "rhel" || "$os_id" == "fedora" ]]; then
        COMPAT_OS_PROFILE_ID="unknown-rpm-like"
      else
        COMPAT_OS_PROFILE_ID="unknown-linux"
      fi
      COMPAT_CERTIFICATION="untested"
      compat_warning "untested OS profile '$COMPAT_OS_PROFILE_ID'; installer will use conservative compatibility checks"
      ;;
  esac
}

detect_systemd_version() {
  local first
  first="$(systemctl --version 2>/dev/null | head -n 1 || true)"
  COMPAT_SYSTEMD_VERSION="$first"
}

collect_base_compatibility() {
  COMPATIBILITY_CHECKED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
  COMPAT_KERNEL="$(uname -r 2>/dev/null || echo unknown)"
  COMPAT_ARCHITECTURE="$(normalize_architecture)"
  COMPAT_PACKAGE_MANAGER="$(detect_package_manager)"
  if command -v systemctl >/dev/null 2>&1; then
    COMPAT_SYSTEMD="true"
    detect_systemd_version
  fi
  detect_os_release
  resolve_os_profile

  if [[ "$(uname -s 2>/dev/null || echo unknown)" != "Linux" ]]; then
    compat_error "linux host required"
  fi

  case "$COMPAT_ARCHITECTURE" in
    amd64|arm64|armv7)
      ;;
    *)
      compat_error "unsupported CPU architecture: $COMPAT_ARCHITECTURE"
      ;;
  esac

  if [[ "$COMPAT_PACKAGE_MANAGER" == "unknown" ]]; then
    compat_error "no supported package manager found; apt, dnf, or yum is required"
  fi

  if [[ "$RUNTIME_MODE" == "host" && "$COMPAT_SYSTEMD" != "true" ]]; then
    compat_error "systemd host runtime required"
  fi

  if [[ ! -c /dev/net/tun ]] && command -v modprobe >/dev/null 2>&1; then
    run_root modprobe tun >/dev/null 2>&1 || true
  fi
  if [[ -c /dev/net/tun ]]; then
    COMPAT_TUN_AVAILABLE="true"
  else
    compat_error "/dev/net/tun is missing; install on a Linux host or privileged container with TUN and CAP_NET_ADMIN"
  fi
}

compat_probe_nftables_runtime() {
  if ! command -v nft >/dev/null 2>&1; then
    return 1
  fi
  local tmp
  tmp="$(mktemp /tmp/rizoma-nft-compat.XXXXXX)"
  cat > "$tmp" <<'EOF'
destroy table inet rizoma_compat_preflight
table inet rizoma_compat_preflight {
  chain input {
    type filter hook input priority 0; policy accept;
  }
}
EOF
  if run_root nft -c -f "$tmp" >/dev/null 2>&1; then
    rm -f "$tmp"
    return 0
  fi
  rm -f "$tmp"
  return 1
}

build_derived_compatibility() {
  COMPAT_RUNTIME_PATHS_JSON=()
  COMPAT_FEATURES_JSON=()

  compat_runtime_path "/etc/fail2ban" "false" "Fail2ban integration"
  compat_runtime_path "/run/fail2ban" "false" "Fail2ban runtime socket/state"
  compat_runtime_path "/var/run/fail2ban" "false" "Fail2ban runtime compatibility path"
  compat_runtime_path "/run/utmp" "false" "Remote SSH login accounting"
  compat_runtime_path "/var/log/wtmp" "false" "Remote SSH login history accounting"
  compat_runtime_path "/var/log/btmp" "false" "Remote SSH failed-login accounting"
  compat_runtime_path "/var/log/lastlog" "false" "Remote SSH last-login accounting"

  if command -v nft >/dev/null 2>&1; then
    COMPAT_NFTABLES_AVAILABLE="true"
  fi
  if compat_probe_nftables_runtime; then
    COMPAT_NFTABLES_AVAILABLE="true"
    COMPAT_NFTABLES_USABLE="true"
  fi

  if [[ "$COMPAT_TUN_AVAILABLE" == "true" ]]; then
    compat_feature "mesh_tunnel" "Mesh tunnel" "supported" ""
    compat_feature "exit_node" "Exit node" "supported" ""
  else
    compat_feature "mesh_tunnel" "Mesh tunnel" "blocked" "/dev/net/tun is missing"
    compat_feature "exit_node" "Exit node" "blocked" "TUN device is required for peer routing"
  fi

  if [[ "$(uname -s 2>/dev/null || echo unknown)" == "Linux" && "$COMPAT_SYSTEMD" == "true" ]]; then
    if [[ -e /run/utmp ]]; then
      compat_feature "remote_ssh" "Remote SSH" "supported" ""
    else
      compat_feature "remote_ssh" "Remote SSH" "degraded" "Remote SSH shell is supported, but /run/utmp login accounting is unavailable on this OS"
      compat_warning "Remote SSH login accounting degraded: /run/utmp is not present on this OS"
    fi
  else
    compat_feature "remote_ssh" "Remote SSH" "blocked" "Linux systemd host runtime is required"
  fi

  if [[ "$ENABLE_FIREWALL" == "true" ]]; then
    if [[ "$COMPAT_NFTABLES_USABLE" == "true" ]]; then
      compat_feature "mesh_firewall" "Mesh firewall" "supported" ""
    else
      compat_feature "mesh_firewall" "Mesh firewall" "blocked" "nftables is missing or the kernel netlink interface is unavailable"
    fi
  else
    compat_feature "mesh_firewall" "Mesh firewall" "not_enabled" "Install profile does not enable the mesh firewall"
  fi

  if [[ "$ENABLE_FIREWALL" == "true" && "$COMPAT_NFTABLES_USABLE" == "true" ]]; then
    compat_feature "exposure_lockdown" "Exposure lockdown" "supported" ""
  elif [[ "$ENABLE_FIREWALL" == "true" ]]; then
    compat_feature "exposure_lockdown" "Exposure lockdown" "blocked" "nftables firewall support is required for host exposure lockdown"
  else
    compat_feature "exposure_lockdown" "Exposure lockdown" "not_enabled" "Security/firewall profile is required"
  fi

  if [[ "$PROFILE" == "router" ]]; then
    if [[ "$ROUTER_PREFLIGHT_OK" == "true" ]]; then
      compat_feature "router_integration" "Router integration" "supported" ""
    else
      compat_feature "router_integration" "Router integration" "blocked" "Rizoma Router preflight did not pass"
    fi
  else
    compat_feature "router_integration" "Router integration" "not_applicable" "Only --profile router enables router integration"
  fi
}

write_compatibility_report() {
  local report_dir warnings_json errors_json paths_json features_json status router_json
  build_derived_compatibility
  report_dir="$(dirname "$COMPATIBILITY_REPORT_PATH")"
  run_root install -d -m 700 "$report_dir"
  warnings_json="$(json_string_array COMPATIBILITY_WARNINGS)"
  errors_json="$(json_string_array COMPATIBILITY_ERRORS)"
  paths_json="$(json_object_array COMPAT_RUNTIME_PATHS_JSON)"
  features_json="$(json_object_array COMPAT_FEATURES_JSON)"
  status="$(compatibility_status)"
  router_json="null"
  if [[ "$PROFILE" == "router" ]]; then
    router_json="$(cat <<EOF
{
    "ok": $(json_bool "$ROUTER_PREFLIGHT_OK"),
    "product": "$(json_escape "$ROUTER_PREFLIGHT_PRODUCT")",
    "router_version": "$(json_escape "$ROUTER_PREFLIGHT_VERSION")",
    "router_instance_id": "$(json_escape "$ROUTER_PREFLIGHT_INSTANCE_ID")",
    "service": "$(json_escape "$ROUTER_PREFLIGHT_SERVICE")",
    "mesh_integration_version": "$(json_escape "$ROUTER_PREFLIGHT_INTEGRATION_VERSION")",
    "supports_site_gateway": $(json_bool "$ROUTER_PREFLIGHT_SUPPORTS_GATEWAY"),
    "build_commit": "$(json_escape "$ROUTER_PREFLIGHT_BUILD_COMMIT")"
  }
EOF
)"
  fi
  write_root_file "$COMPATIBILITY_REPORT_PATH" 0600 <<EOF
{
  "contract_version": "$COMPATIBILITY_CONTRACT_VERSION",
  "checked_at": "$COMPATIBILITY_CHECKED_AT",
  "status": "$status",
  "install_profile": "$(json_escape "$PROFILE")",
  "runtime_mode": "$(json_escape "$RUNTIME_MODE")",
  "os_profile_id": "$(json_escape "$COMPAT_OS_PROFILE_ID")",
  "certification": "$(json_escape "$COMPAT_CERTIFICATION")",
  "package_manager": "$(json_escape "$COMPAT_PACKAGE_MANAGER")",
  "os_id": "$(json_escape "$COMPAT_OS_ID")",
  "os_id_like": "$(json_escape "$COMPAT_OS_ID_LIKE")",
  "os_version_id": "$(json_escape "$COMPAT_OS_VERSION_ID")",
  "os_pretty_name": "$(json_escape "$COMPAT_OS_PRETTY_NAME")",
  "kernel": "$(json_escape "$COMPAT_KERNEL")",
  "architecture": "$(json_escape "$COMPAT_ARCHITECTURE")",
  "systemd": $COMPAT_SYSTEMD,
  "systemd_version": "$(json_escape "$COMPAT_SYSTEMD_VERSION")",
  "tun_available": $COMPAT_TUN_AVAILABLE,
  "nftables_available": $COMPAT_NFTABLES_AVAILABLE,
  "nftables_usable": $COMPAT_NFTABLES_USABLE,
  "router": $router_json,
  "runtime_paths": $paths_json,
  "features": $features_json,
  "warnings": $warnings_json,
  "ineligible_reasons": $errors_json
}
EOF
}

fail_if_incompatible() {
  if [[ "${#COMPATIBILITY_ERRORS[@]}" -eq 0 ]]; then
    return
  fi
  write_compatibility_report || true
  echo "error: host is not compatible with Rizoma Agent profile '$PROFILE':"
  local reason
  for reason in "${COMPATIBILITY_ERRORS[@]}"; do
    echo "error: - $reason"
  done
  echo "error: compatibility report: $COMPATIBILITY_REPORT_PATH"
  exit 1
}

find_router_preflight_command() {
  local candidate
  for candidate in rizoma-routerctl routerctl /usr/bin/rizoma-routerctl /usr/sbin/rizoma-routerctl /usr/local/bin/rizoma-routerctl /usr/local/sbin/rizoma-routerctl /usr/bin/routerctl /usr/sbin/routerctl /usr/local/bin/routerctl /usr/local/sbin/routerctl; do
    if command -v "$candidate" >/dev/null 2>&1; then
      command -v "$candidate"
      return 0
    fi
    if [[ "$candidate" == /* && -x "$candidate" ]]; then
      echo "$candidate"
      return 0
    fi
  done
  return 1
}

json_field() {
  local json="$1"
  local key="$2"
  if command -v jq >/dev/null 2>&1; then
    printf '%s' "$json" | jq -r --arg key "$key" '.[$key] // empty' 2>/dev/null
    return
  fi
  printf '%s' "$json" |
    tr ',{}' '\n' |
    sed -nE 's/^[[:space:]]*"'"$key"'"[[:space:]]*:[[:space:]]*"?([^"]*)"?[[:space:]]*$/\1/p' |
    head -n 1
}

version_at_least() {
  local actual="${1%%-*}"
  local required="${2%%-*}"
  local actual_major actual_minor actual_patch required_major required_minor required_patch
  IFS=. read -r actual_major actual_minor actual_patch <<< "$actual"
  IFS=. read -r required_major required_minor required_patch <<< "$required"
  actual_major="${actual_major:-0}"
  actual_minor="${actual_minor:-0}"
  actual_patch="${actual_patch:-0}"
  required_major="${required_major:-0}"
  required_minor="${required_minor:-0}"
  required_patch="${required_patch:-0}"
  case "$actual_major$actual_minor$actual_patch$required_major$required_minor$required_patch" in
    *[!0-9]*)
      return 1
      ;;
  esac
  if (( actual_major != required_major )); then
    (( actual_major > required_major ))
    return
  fi
  if (( actual_minor != required_minor )); then
    (( actual_minor > required_minor ))
    return
  fi
  (( actual_patch >= required_patch ))
}

require_rizoma_router_preflight() {
  if [[ "$PROFILE" != "router" ]]; then
    return
  fi

  local routerctl
  if ! routerctl="$(find_router_preflight_command)"; then
    compat_error "--profile router requires Rizoma Router $MIN_ROUTER_PROFILE_VERSION or newer on this host"
    compat_error "rizoma-routerctl mesh-preflight --json was not found"
    fail_if_incompatible
  fi

  local preflight_json
  if ! preflight_json="$(run_root "$routerctl" mesh-preflight --json 2>&1)"; then
    compat_error "Rizoma Router preflight failed; refusing router-profile enrollment: $preflight_json"
    fail_if_incompatible
  fi

  local ok product router_version service mesh_supported integration_version supports_gateway router_instance_id build_commit
  ok="$(json_field "$preflight_json" "ok")"
  product="$(json_field "$preflight_json" "product")"
  router_version="$(json_field "$preflight_json" "router_version")"
  service="$(json_field "$preflight_json" "service")"
  mesh_supported="$(json_field "$preflight_json" "mesh_integration_supported")"
  integration_version="$(json_field "$preflight_json" "mesh_integration_version")"
  supports_gateway="$(json_field "$preflight_json" "supports_site_gateway")"
  router_instance_id="$(json_field "$preflight_json" "router_instance_id")"
  build_commit="$(json_field "$preflight_json" "build_commit")"

  ROUTER_PREFLIGHT_OK="$ok"
  ROUTER_PREFLIGHT_PRODUCT="$product"
  ROUTER_PREFLIGHT_VERSION="$router_version"
  ROUTER_PREFLIGHT_INSTANCE_ID="$router_instance_id"
  ROUTER_PREFLIGHT_SERVICE="$service"
  ROUTER_PREFLIGHT_INTEGRATION_VERSION="$integration_version"
  ROUTER_PREFLIGHT_SUPPORTS_GATEWAY="$supports_gateway"
  ROUTER_PREFLIGHT_BUILD_COMMIT="$build_commit"

  if [[ "$ok" != "true" || "$product" != "rizoma-router" || "$service" != "active" || "$mesh_supported" != "true" || "$supports_gateway" != "true" || -z "$router_instance_id" ]]; then
    compat_error "Rizoma Router preflight did not confirm a healthy site-gateway capable router: product=$product service=$service mesh_supported=$mesh_supported supports_site_gateway=$supports_gateway router_instance_id=${router_instance_id:-missing}"
    fail_if_incompatible
  fi

  if [[ -z "$integration_version" ]]; then
    compat_error "Rizoma Router preflight did not report mesh_integration_version"
    fail_if_incompatible
  fi

  if ! version_at_least "$router_version" "$MIN_ROUTER_PROFILE_VERSION"; then
    compat_error "Rizoma Router $MIN_ROUTER_PROFILE_VERSION or newer is required for --profile router (found: ${router_version:-unknown})"
    fail_if_incompatible
  fi

  echo "Rizoma Router preflight passed: version $router_version, instance $router_instance_id."
}

print_existing_identity_error() {
  local identity="$1"
  echo "error: existing agent identity found at $identity"
  echo "This installer creates a fresh peer by default and will not reuse an existing node ID, Mesh IP, or MagicDNS name."
  echo "For repair or reinstall of this same peer, rerun with --reuse-existing-identity."
  echo "For deliberate fresh enrollment on this host, rerun with --force-reenroll to back up the existing identity first."
}

reject_ambiguous_existing_identity() {
  local identity="$1"
  if ! run_root test -f "$identity"; then
    return
  fi
  if [[ "$REUSE_EXISTING_IDENTITY" == "true" || "$FORCE_REENROLL" == "true" ]]; then
    return
  fi

  print_existing_identity_error "$identity"
  exit 1
}

handle_existing_identity() {
  local identity="$1"
  if ! run_root test -f "$identity"; then
    return
  fi

  if [[ "$FORCE_REENROLL" == "true" ]]; then
    local backup
    backup="${identity}.backup.$(date -u +%Y%m%dT%H%M%SZ)"
    echo "Existing agent identity found at $identity; backing it up for fresh enrollment."
    if command -v systemctl >/dev/null 2>&1; then
      run_root systemctl stop rizoma-agent >/dev/null 2>&1 || true
    fi
    run_root mv "$identity" "$backup"
    echo "Previous agent identity backup: $backup"
    return
  fi

  if [[ "$REUSE_EXISTING_IDENTITY" == "true" ]]; then
    echo "Existing agent identity found at $identity; reusing it because --reuse-existing-identity was set."
    return
  fi

  print_existing_identity_error "$identity"
  exit 1
}

collect_base_compatibility
require_rizoma_router_preflight
fail_if_incompatible

reject_ambiguous_existing_identity "$IDENTITY_PATH"

# 0.9.250.0 Track B: register the install with the
# dashboard and arm the cleanup trap. Done AFTER the
# compatibility check so we don't burn a session_id
# on a host that's about to fail compatibility.
trap_install_lifecycle_failure
install_lifecycle_start

prepare_firewall_runtime() {
  if command -v systemctl >/dev/null 2>&1; then
    if systemctl list-unit-files | grep -q '^ufw\.service'; then
      if systemctl is-active --quiet ufw 2>/dev/null; then
        echo "Disabling active UFW firewall..."
        run_root ufw --force disable >/dev/null 2>&1 || run_root systemctl disable --now ufw >/dev/null 2>&1 || true
      fi
      run_root systemctl disable ufw >/dev/null 2>&1 || true
    fi

    if systemctl list-unit-files | grep -q '^firewalld\.service'; then
      if systemctl is-active --quiet firewalld 2>/dev/null; then
        echo "Disabling active firewalld firewall..."
        run_root systemctl disable --now firewalld >/dev/null 2>&1 || true
      fi
      run_root systemctl disable firewalld >/dev/null 2>&1 || true
    fi
  fi
}

install_packages() {
  if [[ "$#" -eq 0 ]]; then
    return
  fi
  if command -v apt-get >/dev/null 2>&1; then
    run_root apt-get update -qq
    run_root apt-get install -y "$@"
  elif command -v dnf >/dev/null 2>&1; then
    run_root dnf -y makecache
    run_root dnf -y install "$@"
  elif command -v yum >/dev/null 2>&1; then
    run_root yum -y makecache
    run_root yum -y install "$@"
  else
    echo "error: no supported package manager (apt/dnf/yum)"
    exit 1
  fi
}

install_apt_keyring() {
  local tmp_key
  local tmp_out
  tmp_key="$(mktemp /tmp/rizoma-keyring.XXXXXX)"
  curl -fsSL "$REPO_URL/keys/rizoma-archive-keyring.gpg" -o "$tmp_key"
  if grep -q "BEGIN PGP PUBLIC KEY BLOCK" "$tmp_key"; then
    tmp_out="$(mktemp /tmp/rizoma-keyring-out.XXXXXX)"
    gpg --batch --yes --dearmor -o "$tmp_out" "$tmp_key"
    run_root install -m 0644 "$tmp_out" /usr/share/keyrings/rizoma-archive-keyring.gpg
    rm -f "$tmp_out"
  else
    run_root install -m 0644 "$tmp_key" /usr/share/keyrings/rizoma-archive-keyring.gpg
  fi
  rm -f "$tmp_key"
}

nftables_runtime_available() {
  if ! command -v nft >/dev/null 2>&1; then
    return 1
  fi
  local tmp
  tmp="$(mktemp /tmp/rizoma-nft-preflight.XXXXXX)"
  cat > "$tmp" <<'EOF'
destroy table inet rizoma_preflight
table inet rizoma_preflight {
  chain input {
    type filter hook input priority 0; policy accept;
  }
}
EOF
  if run_root nft -c -f "$tmp" >/dev/null 2>&1; then
    rm -f "$tmp"
    return 0
  fi
  rm -f "$tmp"
  return 1
}

FIREWALL_MODE="off"
AV_ENABLED="false"
UPDATE_COMMAND="use apt, dnf, or yum to upgrade rizoma-agent"
APT_FEATURE_PACKAGES=()
RPM_FEATURE_PACKAGES=()

if [[ "$ENABLE_FIREWALL" == "true" ]]; then
  FIREWALL_MODE="nftables"
  APT_FEATURE_PACKAGES+=(nftables fail2ban)
  RPM_FEATURE_PACKAGES+=(nftables fail2ban)
fi

# Install rizoma-agent via package manager
if command -v apt-get >/dev/null 2>&1; then
  UPDATE_COMMAND="apt update && apt upgrade"
  # 0.9.250.3: combine the dep install + rizoma-agent install into ONE
  # apt-get update + install. Previous flow did 2 separate apt-get
  # update calls (deps first, then rizoma-agent), wasting ~25s on a
  # second full repo metadata refresh. The rizoma repo + keyring are
  # added FIRST so the single apt-get update picks up both standard
  # Ubuntu repos AND the new rizoma repo, and the single install call
  # resolves all packages (deps + rizoma-agent + nftables + fail2ban)
  # with fresh metadata. Verified on the otp peer: 62s -> ~37s.
  run_root mkdir -p /usr/share/keyrings
  install_apt_keyring
  write_root_file /etc/apt/sources.list.d/rizoma.list 0644 <<EOF
deb [signed-by=/usr/share/keyrings/rizoma-archive-keyring.gpg] $REPO_URL/apt/$CHANNEL ./
EOF
  install_packages curl ca-certificates gnupg jq rizoma-agent "${APT_FEATURE_PACKAGES[@]}"
elif command -v dnf >/dev/null 2>&1; then
  UPDATE_COMMAND="dnf upgrade"
  install_packages curl ca-certificates jq
  run_root mkdir -p /etc/pki/rpm-gpg
  run_root mkdir -p /etc/yum.repos.d
  tmp_key="$(mktemp /tmp/rizoma-rpm-key.XXXXXX)"
  curl -fsSL "$REPO_URL/keys/RPM-GPG-KEY-rizoma" -o "$tmp_key"
  run_root install -m 0644 "$tmp_key" /etc/pki/rpm-gpg/RPM-GPG-KEY-rizoma
  rm -f "$tmp_key"
  write_root_file /etc/yum.repos.d/rizoma.repo 0644 <<REPOEOF
[rizoma]
name=Rizoma
baseurl=$REPO_URL/dnf/$CHANNEL/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rizoma
REPOEOF
  install_packages rizoma-agent "${RPM_FEATURE_PACKAGES[@]}"
elif command -v yum >/dev/null 2>&1; then
  UPDATE_COMMAND="yum update"
  install_packages curl ca-certificates jq
  run_root mkdir -p /etc/pki/rpm-gpg
  run_root mkdir -p /etc/yum.repos.d
  tmp_key="$(mktemp /tmp/rizoma-rpm-key.XXXXXX)"
  curl -fsSL "$REPO_URL/keys/RPM-GPG-KEY-rizoma" -o "$tmp_key"
  run_root install -m 0644 "$tmp_key" /etc/pki/rpm-gpg/RPM-GPG-KEY-rizoma
  rm -f "$tmp_key"
  write_root_file /etc/yum.repos.d/rizoma.repo 0644 <<REPOEOF
[rizoma]
name=Rizoma
baseurl=$REPO_URL/dnf/$CHANNEL/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rizoma
REPOEOF
  install_packages rizoma-agent "${RPM_FEATURE_PACKAGES[@]}"
else
  echo "error: no supported package manager (apt/dnf/yum)"
  exit 1
fi

if [[ "$ENABLE_FIREWALL" == "true" ]]; then
  if command -v nft >/dev/null 2>&1; then
    COMPAT_NFTABLES_AVAILABLE="true"
  fi
  if nftables_runtime_available; then
    COMPAT_NFTABLES_AVAILABLE="true"
    COMPAT_NFTABLES_USABLE="true"
    prepare_firewall_runtime
  else
    compat_error "nftables is required for profile '$PROFILE', but nftables is missing or the kernel netlink interface is unavailable"
    fail_if_incompatible
  fi
fi

# Configure agent
# 0.9.250.3: batch the 5 separate `run_root install -d` calls into
# 2 calls (one sudo spawn per call, not per directory). install -d
# accepts multiple paths in a single invocation. Saves ~1-2s.
run_root install -d -m 700 /etc/rizoma /var/lib/rizoma/agent
run_root install -d -m 755 /var/lib/rizoma /var/log/rizoma /run/fail2ban || true
run_root install -d -m 750 /var/log/rizoma/agent
if command -v systemd-tmpfiles >/dev/null 2>&1; then
  run_root systemd-tmpfiles --create /usr/lib/tmpfiles.d/rizoma-agent.conf >/dev/null 2>&1 || true
fi

write_compatibility_report

handle_existing_identity "$IDENTITY_PATH"

# 0.9.250.0 Track B: prompt for the one-time auth_key
# BEFORE writing /etc/rizoma/agent.env (the agent reads
# RIZOMA_AGENT_AUTH_KEY from there at startup). We
# signal /v1/install/ready FIRST so the dashboard
# knows the script is at the prompt — that's what
# unlocks the Generate button on the dashboard side.
# If the operator doesn't have a key yet, they can
# type something invalid; the script will then fail
# at the validation step and the dashboard will see
# "expired" via the trap.
install_lifecycle_ready
if ! prompt_for_auth_key; then
  install_lifecycle_failed "auth_key_prompt_failed"
  exit 1
fi

write_root_file /etc/rizoma/agent.env 0600 <<EOF
RIZOMA_AGENT_LISTEN=$AGENT_HTTP_LISTEN
RIZOMA_AGENT_COORDINATOR_URL=$COORDINATOR_URL
RIZOMA_AGENT_TOKEN=$ENROLL_TOKEN
RIZOMA_AGENT_NAME=$NODE_NAME
RIZOMA_AGENT_EXIT_NODE=$EXIT_NODE
RIZOMA_AGENT_RUNTIME_MODE=$RUNTIME_MODE
RIZOMA_AGENT_COMPATIBILITY_REPORT=$COMPATIBILITY_REPORT_PATH
RIZOMA_AGENT_IDENTITY=$IDENTITY_PATH
RIZOMA_AGENT_STORE=/var/lib/rizoma/agent
# The agent API remains HTTP until mesh mTLS is wired; bind it to loopback.
RIZOMA_AGENT_INSECURE_HTTP=true
RIZOMA_AGENT_TUN=rizoma0
RIZOMA_AGENT_QUIC_LISTEN=${RIZOMA_AGENT_QUIC_LISTEN:-0.0.0.0:4242}
RIZOMA_AGENT_QUIC_PUBLIC_ADDR=${RIZOMA_AGENT_QUIC_PUBLIC_ADDR:-}
RIZOMA_AGENT_FIREWALL_MODE=$FIREWALL_MODE
# 0.9.250.0 Track B: one-time auth_key, consumed by
# the coordinator's /v1/enroll validation. 60s TTL on
# the dashboard side; the agent does NOT re-validate
# the TTL locally (the coordinator is the trust root).
RIZOMA_AGENT_AUTH_KEY=$INSTALL_AUTH_KEY
EOF

if command -v systemctl >/dev/null 2>&1; then
  run_root systemctl daemon-reload
  run_root systemctl reset-failed rizoma-agent >/dev/null 2>&1 || true
  if [[ "$FIREWALL_MODE" == "nftables" ]]; then
    run_root systemctl enable --now nftables || true
    run_root systemctl enable --now fail2ban || true
  fi
  run_root systemctl enable rizoma-agent >/dev/null 2>&1 || true
  run_root systemctl restart rizoma-agent
fi

# Wait for enrollment
echo "Waiting for agent enrollment..."
identity="$IDENTITY_PATH"
enrolled=false
for i in $(seq 1 30); do
  if run_root test -f "$identity"; then
    if command -v systemctl >/dev/null 2>&1; then
      if run_root systemctl is-active --quiet rizoma-agent; then
        enrolled=true
        break
      fi
    else
      enrolled=true
      break
    fi
  fi
  sleep 2
done

if [ "$enrolled" = true ]; then
  node_id="$(run_root cat "$identity" 2>/dev/null | jq -r '.id' 2>/dev/null || echo "")"
  mesh_ip="$(run_root cat "$identity" 2>/dev/null | jq -r '.mesh_ip' 2>/dev/null || echo "")"
  echo ""
  echo "  Agent enrolled successfully!"
  echo "  Node ID:  $node_id"
  echo "  Mesh IP:  $mesh_ip"
  echo "  Name:     $NODE_NAME"
  echo "  Profile:  $PROFILE"
  echo "  Runtime:  $RUNTIME_MODE"
  echo ""
  echo "  Coordinator: $COORDINATOR_URL"
  echo "  Updates: $UPDATE_COMMAND"
  echo ""
  # 0.9.250.0 Track B: tell the dashboard the
  # enrollment completed. The pending_installs row
  # is kept for 1h for audit, then GC'd by
  # CleanupOldInstalls. This transitions the modal
  # from 'generated' (or 'idle') to 'completed'.
  install_lifecycle_complete "$node_id"
else
  echo ""
  echo "  Agent enrollment failed or timed out."
  echo "  Check logs: journalctl -u rizoma-agent --no-pager -n 20"
  echo ""
  run_root journalctl -u rizoma-agent --no-pager -n 5 2>/dev/null || true
  # 0.9.250.0 Track B: the trap would also fire on
  # exit, but we mark it explicitly so the
  # reason field is meaningful. The handler DELETEs
  # the row (no zombies rule).
  install_lifecycle_failed "agent_enrollment_timeout"
fi
