#!/bin/sh
# SigmaShake ssg installer — no Node.js or npm required.
# Downloads the native ssg binary directly from the SigmaShake download CDN
# (download.sigmashake.com/cli/* → the canonical release artifact).
#
# Usage:
#   curl -fsSL https://install.sigmashake.com | sh
#   curl -fsSL https://install.sigmashake.com/install.sh | sh
#   curl -fsSL https://install.sigmashake.com | sh -s -- --no-setup
#   curl -fsSL https://install.sigmashake.com | sh -s -- --install-dir=/usr/local/bin
#   curl -fsSL https://install.sigmashake.com | sh -s -- -y
#   curl -fsSL https://install.sigmashake.com | sh -s -- --uninstall
#   # Non-interactive install + account link in one shot (magic-link token):
#   curl -fsSL https://install.sigmashake.com | SSG_INSTALL_TOKEN=<token> sh -s -- -y
#
# After install (when -y/SSG_INSTALL_YES consent is present, or the script is
# run interactively), this installer links the account first (SSG_INSTALL_TOKEN,
# or an interactive `ssg auth login` when a TTY is available and --no-auth was
# not passed). An unlicensed ssg blocks every check, so the license comes
# before `ssg init`. Then it runs `ssg init`, starts the local governance
# daemon, and runs `ssg status` — then prints one `ssg-install-timing: ...`
# line to stderr and writes the same fields to $SIGMASHAKE_HOME/install-timing.json.
#
# --uninstall reverses a prior curl-pipe install: it invokes
# `ssg uninstall --package-remove` on the currently-installed binary (strips
# hooks, disables autostart, stops any running local daemon) and then removes
# the ssg binary + adjacent public/ dashboard assets from the install
# directory. It does NOT touch ~/.sigmashake (license, rules, audit logs) —
# that is per-user state, preserved by design so a reinstall keeps config.
# Pass --install-dir=<dir> to target a non-default install; otherwise it
# checks $SSG_INSTALL_DIR, then ~/.local/bin, then /usr/local/bin, then PATH.
# Pass --install-path=<absolute path of ssg, or its directory> to name one exact
# install. It wins over $SSG_INSTALL_DIR; with --install-dir it is an error
# (exit 1). It does not search, and it keeps an adjacent public/ directory.
# Before it runs the file, uninstall checks that `ssg --version` prints a bare
# version number (an "ssg " prefix is allowed). If not, it exits 1, removes
# nothing and prints how to remove the file by hand. With HOME unset,
# uninstall needs SIGMASHAKE_HOME and a given dir (--install-path, --install-dir
# or $SSG_INSTALL_DIR). It searches nowhere else. Without them it exits 1.
#
# Non-interactive installs (no tty on stdin) require -y/--yes as explicit
# Terms of Service consent; without it the script fails closed. -y also
# records the consent (version, URL, timestamp) to consent.json in the
# ssg home directory.
# Existing exact-target updates may instead pass --preserve-consent together
# with --install-path and --no-setup. That mode validates current recorded
# consent and leaves the consent file byte-for-byte untouched.
#
# Environment overrides:
#   SSG_INSTALL_DIR=<path>       Override install location (default: ~/.local/bin)
#   SSG_DRY_RUN=1                Print actions without writing files
#   SSG_DASHBOARD_URL=<url>      Dashboard URL to probe and print (default: http://127.0.0.1:5599)
#   SSG_INSTALL_BASE_URL=<url>   Override CDN base URL (default: https://download.sigmashake.com)
#                                Used by the hermetic install test suite; do not set in production.
#   SSG_INSTALL_YES=1            Exactly equivalent to -y/--yes (same ToS consent recording,
#                                distinguished in consent.json as method "env:SSG_INSTALL_YES").
#   SSG_INSTALL_TOKEN=<token>    Magic-link install token; after setup this installer runs
#                                `ssg auth login --no-browser`, which reads the token from this
#                                env var (never pass it as an argv flag — it would leak via ps/proc),
#                                then unsets it once that command returns. Single-use, ~15-minute
#                                TTL. Pasting it inline lands in shell history — prefer
#                                `read -s SSG_INSTALL_TOKEN && export SSG_INSTALL_TOKEN` first, or
#                                copy it fresh from the accounts page's copy button right before use.
#   SSG_FLEET_CODE=<code>        One-time fleet enrollment code (32 hex characters) from
#                                https://fleet.sigmashake.com/welcome. The installer checks
#                                the format before it downloads anything (exit 4 if invalid).
#                                The code implies Terms of Service consent, recorded as method
#                                "env:SSG_FLEET_CODE". After install, the installer runs
#                                `ssg fleet enroll` with the code in the environment only
#                                (never argv), unsets it, runs `ssg init`, restarts or starts
#                                the daemon, and skips the account-link step unless
#                                SSG_INSTALL_TOKEN is also set. Exit 5 if enrollment fails.
#                                `ssg fleet enroll` exit 6 means the machine IS enrolled but
#                                the license did not activate. The installer then goes on,
#                                prints "Next step: ssg auth login", and exits 3 (partial)
#                                unless SSG_INSTALL_TOKEN links the license.
#   --no-auth                    Skip the interactive `ssg auth login` prompt after setup.
#                                Does not affect a non-interactive SSG_INSTALL_TOKEN login.
#   --no-modify-path             Do not add the install directory to your shell rc file.
#                                Without this flag, -y (or a yes answer at the prompt) appends
#                                one marked line to the rc file of your shell (zsh, bash, or
#                                the generic profile) when the install directory is not on
#                                PATH. To undo it, delete the line that ends with
#                                "# added by sigmashake installer".
#   --no-serve                   Do not offer to start the local dashboard (`ssg serve`) after
#                                setup. Without this flag, -y starts it detached (log:
#                                ~/.sigmashake/serve-install.log) and an interactive run asks
#                                "Start the dashboard now? [Y/n]". The URL is
#                                http://127.0.0.1:5599. ssg gates the dashboard by license.
#
# SHA256 allowlist is baked at release time.
# Regenerate with: bash sigmashake-dist/scripts/checksums.sh
set -eu

# ── Timing (SSG-INSTALL-TIMING) ───────────────────────────────────────────────
# Millisecond epoch. Prefers `date +%s%N` (GNU coreutils / most Linux); a
# ns-epoch is 19 digits, so requiring >=13 digits accepts it while rejecting
# a literal unexpanded "%N" (stock macOS BSD date has no %N) or any other
# non-numeric output, which falls back to whole-second resolution (×1000).
# Never fails the install: a timing hiccup must not take down the installer.
_now_ms() {
  _T="$(date +%s%N 2>/dev/null || true)"
  case "$_T" in
    ''|*[!0-9]*) _T='' ;;
  esac
  if [ -n "$_T" ] && [ "${#_T}" -ge 13 ]; then
    echo $((_T / 1000000))
  else
    echo $(($(date +%s) * 1000))
  fi
}
# Renders a timing value as a bare JSON number when purely numeric, else as a
# quoted JSON string (covers the "skipped" / "interactive" sentinel values).
_json_val() {
  case "$1" in
    ''|*[!0-9]*) printf '"%s"' "$1" ;;
    *) printf '%s' "$1" ;;
  esac
}
_T_SCRIPT_START="$(_now_ms)"
_AUTH_EXCLUDE_MS=0
_DOWNLOAD_MS=0
_EXTRACT_MS=0
_VERIFY_MS=0

VERSION=""
NO_SETUP=0
YES=0
NO_AUTH=0
NO_MODIFY_PATH=0
NO_SERVE=0
DRY_RUN="${SSG_DRY_RUN:-0}"
INSTALL_DIR="${SSG_INSTALL_DIR:-}"
INSTALL_PATH=""
INSTALL_PATH_SET=0
INSTALL_DIR_FLAG=0
PRESERVE_CONSENT=0
UNINSTALL=0
# Testability override: point the installer at a local fixture CDN in tests.
# Default is the production CDN. When set, ALL download.sigmashake.com URLs
# are replaced with this base (no trailing slash). Default behavior is
# byte-identical when the variable is absent.
BASE="${SSG_INSTALL_BASE_URL:-https://download.sigmashake.com}"
TERMS_VERSION="2026-06-13"
TERMS_URL="https://sigmashake.com/terms"

# Tracks HOW -y consent was given so consent.json can distinguish the two
# non-interactive paths (set alongside YES=1 below; never both at once — the
# explicit flag always wins when both happen to be present).
_YES_METHOD=""

for _arg in "$@"; do
  case "$_arg" in
    --no-setup)       NO_SETUP=1 ;;
    -y|--yes)        YES=1; _YES_METHOD="-y" ;;
    --no-auth)        NO_AUTH=1 ;;
    --no-modify-path) NO_MODIFY_PATH=1 ;;
    --no-serve)       NO_SERVE=1 ;;
    --dry-run)        DRY_RUN=1 ;;
    --version=*)      VERSION="${_arg#--version=}" ;;
    --install-dir=*)  INSTALL_DIR="${_arg#--install-dir=}"; INSTALL_DIR_FLAG=1 ;;
    --install-path=*) INSTALL_PATH="${_arg#--install-path=}"; INSTALL_PATH_SET=1 ;;
    --preserve-consent) PRESERVE_CONSENT=1 ;;
    --uninstall)      UNINSTALL=1 ;;
  esac
done

# SSG_FLEET_CODE: validate the fleet code before any download. A valid code
# means the user accepts the ToS (same as SSG_INSTALL_YES=1). Record it under
# its own method marker. The explicit -y flag still wins. The code moves into
# a private variable and leaves the environment now. Curl, tar and init never
# see it. Only the `ssg fleet enroll` call receives it, through the environment.
_FLEET_CODE=""
if [ -n "${SSG_FLEET_CODE:-}" ] && [ "$UNINSTALL" = "0" ]; then
  # Check the raw value first. `$(...)` strips trailing newlines, so a code
  # with a trailing newline would pass after the lowercase step. The PowerShell
  # installer rejects it too (`\z`). Any byte that is not a hex digit is bad.
  case "$SSG_FLEET_CODE" in
    *[!0-9a-fA-F]*) _FLEET_CODE_BAD=1 ;;
    *) _FLEET_CODE_BAD=0 ;;
  esac
  _FLEET_CODE="$(printf '%s' "$SSG_FLEET_CODE" | tr '[:upper:]' '[:lower:]')"
  if [ "$_FLEET_CODE_BAD" = "0" ] && [ "${#_FLEET_CODE}" -ne 32 ]; then _FLEET_CODE_BAD=1; fi
  if [ "$_FLEET_CODE_BAD" = "1" ]; then
    printf 'Error: SSG_FLEET_CODE is not a valid fleet code (expected 32 hex characters). Get a new command at https://fleet.sigmashake.com/welcome\n' >&2
    exit 4
  fi
  if [ -z "$_YES_METHOD" ]; then
    YES=1
    _YES_METHOD="env:SSG_FLEET_CODE"
  fi
fi
unset SSG_FLEET_CODE

# SSG_INSTALL_YES=1 is exactly equivalent to -y/--yes (same consent recording
# below) — lets a piped `sh -s --` invocation carry consent via env alone.
# Recorded under a distinct method marker so consent.json can tell the two
# paths apart; only applies when -y/--yes wasn't already the explicit source.
if [ "${SSG_INSTALL_YES:-0}" = "1" ] && [ -z "$_YES_METHOD" ]; then
  YES=1
  _YES_METHOD="env:SSG_INSTALL_YES"
fi

# Per-platform default version. Platforms are released on independent cadences,
# so the right default is "highest version actually released for THIS platform"
# rather than a single global pin. checksums.sh updates these (and _sha256_for
# below) from the built binaries at release time.
_default_version_for() {
  case "$1" in
    linux-x64)    echo "1.1.8" ;;
    linux-arm64)  echo "1.1.8" ;;
    darwin-x64)   echo "1.1.8" ;;
    darwin-arm64) echo "1.1.8" ;;
    *)            echo "" ;;
  esac
}

# Color output only when stderr is a terminal
if [ -t 2 ] && command -v tput >/dev/null 2>&1; then
  _GREEN="$(tput setaf 2 2>/dev/null || true)"
  _YELLOW="$(tput setaf 3 2>/dev/null || true)"
  _RED="$(tput setaf 1 2>/dev/null || true)"
  _BOLD="$(tput bold 2>/dev/null || true)"
  _RESET="$(tput sgr0 2>/dev/null || true)"
else
  _GREEN=''; _YELLOW=''; _RED=''; _BOLD=''; _RESET=''
fi

_info() { printf '%s✓%s  %s\n' "$_GREEN" "$_RESET" "$1" >&2; }
_warn() { printf '%s!%s  %s\n' "$_YELLOW" "$_RESET" "$1" >&2; }
_err()  { printf '%s✗%s  %s\n' "$_RED" "$_RESET" "$1" >&2; }

# HOME names the default install dir and the consent file. Without HOME, the
# installer needs SIGMASHAKE_HOME and an explicit install dir (--install-dir,
# --install-path, or SSG_INSTALL_DIR). Runs after flag parsing, so the flags
# count. Stop here, before any download, with one clear message.
_exit_home_unset() {
  _err "HOME is not set. Set HOME, or set SSG_INSTALL_DIR and SIGMASHAKE_HOME, then run the installer again."
  exit 1
}
if [ -z "${HOME:-}" ] && { [ -z "${SIGMASHAKE_HOME:-}" ] || { [ -z "$INSTALL_DIR" ] && [ "$INSTALL_PATH_SET" != 1 ]; }; }; then
  _exit_home_unset
fi
_consent_path() {
  printf '%s/consent.json\n' "${SIGMASHAKE_HOME:-$HOME/.sigmashake}"
}
_write_yes_consent() {
  _SSG_HOME="${SIGMASHAKE_HOME:-$HOME/.sigmashake}"
  _CONSENT_PATH="$(_consent_path)"
  _ACCEPTED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date +"%Y-%m-%dT%H:%M:%SZ")"
  mkdir -p "$_SSG_HOME"
  (
    umask 077
    cat > "$_CONSENT_PATH" <<EOF
{
  "tosVersion": "$TERMS_VERSION",
  "tosUrl": "$TERMS_URL",
  "acceptedAt": "$_ACCEPTED_AT",
  "method": "$_YES_METHOD"
}
EOF
  )
  _info "accepted ToS $TERMS_VERSION ($TERMS_URL) via $_YES_METHOD"
}

# ── Uninstall ─────────────────────────────────────────────────────────────────
# Runs before the ToS-consent gate and platform detection below — uninstall
# needs neither. Resolution order mirrors the install-target resolution used
# by the default (no --install-dir/--install-path) install path.
# The --version line of the SigmaShake ssg is a version: 1.2.3, 1.2.3-dev.abc,
# or the same with a leading "ssg " or "ssg v". Other tools named ssg (static
# site generators) print other text. Uninstall runs the file, so it checks this
# first. The probe writes to a temp file, so a child that holds stdout cannot
# stall it. A hang stops after SSG_UNINSTALL_PROBE_SECS seconds (default 5, a
# test seam). Without timeout(1) (stock macOS) a background watchdog stops it.
# Result in _PROBE_KIND: ok, foreign, unrunnable, or notemp.
# A signal during the probe stops the probe and the watchdog and removes the
# temp file. The installer cleanup trap is set later, so this one is local.
# shellcheck disable=SC2329  # Called by the traps in _ssg_probe.
_probe_abort() {
  if [ -n "${_PROBE_WD:-}" ]; then kill "$_PROBE_WD" 2>/dev/null || true; fi
  if [ -n "${_PROBE_PID:-}" ]; then kill "$_PROBE_PID" 2>/dev/null || true; fi
  rm -f "${_PROBE_TMP:-}"
}
_ssg_probe() {
  _PROBE_KIND="notemp"
  _PROBE_PID=""
  _PROBE_WD=""
  case "${SSG_UNINSTALL_PROBE_SECS:-5}" in
    ''|*[!0-9]*) _PROBE_SECS=5 ;;
    *) _PROBE_SECS="${SSG_UNINSTALL_PROBE_SECS:-5}" ;;
  esac
  _PROBE_TMP="$(mktemp "${TMPDIR:-/tmp}/ssg-probe.XXXXXX" 2>/dev/null || true)"
  [ -n "$_PROBE_TMP" ] || return 0
  trap '_probe_abort' EXIT
  trap '_probe_abort; exit 1' INT TERM HUP
  _PROBE_RC=0
  if command -v timeout >/dev/null 2>&1; then
    timeout -k 2 "$_PROBE_SECS" "$1" --version </dev/null >"$_PROBE_TMP" 2>/dev/null &
    _PROBE_PID=$!
    wait "$_PROBE_PID" 2>/dev/null || _PROBE_RC=$?
  else
    "$1" --version </dev/null >"$_PROBE_TMP" 2>/dev/null &
    _PROBE_PID=$!
    # The watchdog kills the probe only when its own sleep ends. Main stops it
    # with TERM, and the trap kills the sleep, so no sleep stays behind.
    ( _s=""; trap 'if [ -n "$_s" ]; then kill "$_s" 2>/dev/null; fi; exit 0' TERM
      sleep "$_PROBE_SECS" & _s=$!
      wait "$_s"
      kill -0 "$_PROBE_PID" 2>/dev/null && kill -9 "$_PROBE_PID" 2>/dev/null ) >/dev/null 2>&1 </dev/null &
    _PROBE_WD=$!
    wait "$_PROBE_PID" 2>/dev/null || _PROBE_RC=$?
    kill "$_PROBE_WD" 2>/dev/null || true
    wait "$_PROBE_WD" 2>/dev/null || true
    _PROBE_WD=""
  fi
  _PROBE_PID=""
  _PROBE_LINE="$(awk 'NR == 1 { gsub(/\r/, ""); print; exit }' "$_PROBE_TMP" 2>/dev/null || true)"
  rm -f "$_PROBE_TMP"
  trap - EXIT INT TERM HUP
  if printf '%s\n' "$_PROBE_LINE" | grep -Eq '^(ssg v?)?[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.+-]+)?$'; then
    _PROBE_KIND="ok"
  elif [ -z "$_PROBE_LINE" ] && [ "$_PROBE_RC" != "0" ]; then
    _PROBE_KIND="unrunnable"
  else
    _PROBE_KIND="foreign"
  fi
}

if [ "$UNINSTALL" = "1" ]; then
  _UNINSTALL_DIR=""
  if [ "$INSTALL_PATH_SET" = "1" ]; then
    # An exact --install-path wins over SSG_INSTALL_DIR. Two flags conflict.
    if [ "$INSTALL_DIR_FLAG" = "1" ]; then
      _err "Use --install-path or --install-dir, not both."
      exit 1
    fi
    # Same checks as install mode, for the file form. The dir form must exist.
    case "$INSTALL_PATH" in
      /*) ;;
      *) _err "--install-path must be an absolute path (the ssg binary or its directory): $INSTALL_PATH"; exit 1 ;;
    esac
    if [ -d "$INSTALL_PATH" ]; then
      while :; do
        case "$INSTALL_PATH" in
          */) INSTALL_PATH="${INSTALL_PATH%/}" ;;
          *) break ;;
        esac
      done
      if [ -z "$INSTALL_PATH" ] || [ "$(basename -- "$INSTALL_PATH")" = "ssg" ]; then
        _err "--install-path names a directory called ssg or the root directory. Give the ssg binary or the directory that holds it."
        exit 1
      fi
      _UNINSTALL_DIR="$INSTALL_PATH"
    else
      case "$INSTALL_PATH" in
        */) _err "--install-path must not end with a directory separator"; exit 1 ;;
      esac
      if [ "$(basename -- "$INSTALL_PATH")" != "ssg" ] || [ ! -f "$INSTALL_PATH" ]; then
        _err "--install-path must be an existing ssg binary or the directory that holds it: $INSTALL_PATH"
        exit 1
      fi
      _UNINSTALL_DIR="$(dirname -- "$INSTALL_PATH")"
    fi
  else
    _UNINSTALL_DIR="$INSTALL_DIR"
  fi
  # Without HOME the search below has no safe start. Only a given dir is used.
  if [ -z "$_UNINSTALL_DIR" ] && [ -z "${HOME:-}" ]; then
    _exit_home_unset
  fi
  if [ -z "$_UNINSTALL_DIR" ]; then
    if [ -f "$HOME/.local/bin/ssg" ]; then
      _UNINSTALL_DIR="$HOME/.local/bin"
    elif [ -f "/usr/local/bin/ssg" ]; then
      _UNINSTALL_DIR="/usr/local/bin"
    else
      _PATH_SSG_UNINSTALL="$(command -v ssg 2>/dev/null || true)"
      if [ -n "$_PATH_SSG_UNINSTALL" ]; then
        _UNINSTALL_DIR="$(dirname -- "$_PATH_SSG_UNINSTALL")"
      fi
    fi
  fi

  if [ -z "$_UNINSTALL_DIR" ] || [ ! -f "$_UNINSTALL_DIR/ssg" ]; then
    _err "No ssg installation found (checked --install-path, \$SSG_INSTALL_DIR / --install-dir, ~/.local/bin, /usr/local/bin, PATH)."
    _err "Nothing to uninstall. Pass --install-path=<absolute path of ssg> to name one."
    exit 1
  fi

  _ssg_probe "$_UNINSTALL_DIR/ssg"
  case "$_PROBE_KIND" in
    ok) ;;
    notemp)
      _err "Could not make a temp file in ${TMPDIR:-/tmp} to check $_UNINSTALL_DIR/ssg. Nothing was removed."
      _err "Set TMPDIR to a writable directory and run the uninstall again."
      exit 1 ;;
    unrunnable)
      _err "could not run $_UNINSTALL_DIR/ssg; if it is SigmaShake ssg, remove it by hand: rm $_UNINSTALL_DIR/ssg"
      _err "Nothing was removed."
      exit 1 ;;
    *)
      _err "$_UNINSTALL_DIR/ssg is not the SigmaShake ssg: its --version is not a version number."
      _err "Nothing was removed. If it is SigmaShake ssg, remove it by hand: rm $_UNINSTALL_DIR/ssg"
      _err "Otherwise pass --install-path=<absolute path of the SigmaShake ssg> to name the right one."
      exit 1 ;;
  esac

  _UNINSTALL_TARGET="$_UNINSTALL_DIR/ssg"
  _info "Found ssg at $_UNINSTALL_TARGET"
  _info "Running package-remove cleanup (hooks, autostart, local daemon)..."
  if ! "$_UNINSTALL_TARGET" uninstall --package-remove; then
    _warn "'ssg uninstall --package-remove' exited non-zero — continuing with binary removal anyway."
  fi

  rm -f "$_UNINSTALL_TARGET"
  # Same removal style used for the public/ refresh in the install path below
  # (find -delete, not a recursive remove) — mirrors the existing convention
  # already used in this file for clearing the public/ dashboard assets dir.
  # An --install-path install never wrote public/, so that mode keeps it.
  if [ "$INSTALL_PATH_SET" != "1" ] && [ -d "$_UNINSTALL_DIR/public" ]; then
    find "$_UNINSTALL_DIR/public" -depth -delete 2>/dev/null || true
  fi

  _info "Removed ssg from $_UNINSTALL_DIR."
  _info "Per-user state (license, rules, audit logs) is left untouched at:"
  _info "  ${SIGMASHAKE_HOME:-$HOME/.sigmashake}"
  _info "Remove that directory manually for a full wipe."
  exit 0
fi

if [ "$PRESERVE_CONSENT" = "1" ]; then
  if [ "$YES" = "1" ]; then
    _err "--preserve-consent cannot be combined with -y/--yes"
    exit 1
  fi
  if [ "$INSTALL_PATH_SET" != "1" ] || [ "$NO_SETUP" != "1" ]; then
    _err "--preserve-consent requires --install-path=<existing-ssg> and --no-setup"
    exit 1
  fi
  _PRESERVED_CONSENT_PATH="$(_consent_path)"
  if [ ! -f "$_PRESERVED_CONSENT_PATH" ] || [ ! -r "$_PRESERVED_CONSENT_PATH" ]; then
    _err "--preserve-consent requires current recorded consent at $_PRESERVED_CONSENT_PATH"
    exit 1
  fi
  _PRESERVED_TERMS_VERSION="$(sed -n 's/.*"tosVersion"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$_PRESERVED_CONSENT_PATH")"
  _PRESERVED_TERMS_URL="$(sed -n 's/.*"tosUrl"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$_PRESERVED_CONSENT_PATH")"
  _PRESERVED_ACCEPTED_AT="$(sed -n 's/.*"acceptedAt"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$_PRESERVED_CONSENT_PATH")"
  case "$_PRESERVED_ACCEPTED_AT" in
    [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z) ;;
    *) _PRESERVED_ACCEPTED_AT="" ;;
  esac
  if [ "$_PRESERVED_TERMS_VERSION" != "$TERMS_VERSION" ] || \
     [ "$_PRESERVED_TERMS_URL" != "$TERMS_URL" ] || \
     [ -z "$_PRESERVED_ACCEPTED_AT" ]; then
    _err "--preserve-consent requires current recorded consent for $TERMS_VERSION at $TERMS_URL"
    exit 1
  fi
elif [ "$YES" != "1" ] && [ ! -t 0 ]; then
  _err "Terms of Service consent is required for non-interactive install."
  _err "Read: $TERMS_URL"
  _err "Then rerun: curl -fsSL https://install.sigmashake.com | sh -s -- -y"
  exit 1
fi

# !! SHA256 ALLOWLIST — regenerated by: bash sigmashake-dist/scripts/checksums.sh !!
# Last updated: 2026-09-05 (linux entries restored from 32546889f — lost release
# bump; SHAs re-derived from the live linux-x64/linux-arm64 CDN tarballs).
# Keep version numbers out of this comment: the install-coherence test fails
# when a comment names a version that no platform default uses.
# SHAs match _default_version_for above; bumping either without the other will
# fail SHA verification at install time.
_sha256_for() {
  case "$1" in
    linux-x64)    echo "d41371769692a1f79a6faf2af91736339765f51e197a2ac4614523b94885641b" ;;
    linux-arm64)  echo "b3ddfcddee3abaf2985724e9db5eb713db9aed2f8ca0f3946a0d66990308ffa0" ;;
    darwin-x64)   echo "f7249e069d15d8e0d2a46505afe34353329e4b59185ee0dab4c71f835e6d8dfb" ;;
    darwin-arm64) echo "0a679c6d6846ff249584517a175d742f55ad7278d68a98dae97adf1e23e8fee1" ;;
    *)            echo "" ;;
  esac
}
# !! END SHA256 ALLOWLIST !!

# ── Platform detection ────────────────────────────────────────────────────────
_OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
_ARCH="$(uname -m)"

case "$_ARCH" in
  x86_64|amd64)  _ARCH="x64" ;;
  aarch64|arm64) _ARCH="arm64" ;;
  *)
    _err "Unsupported CPU architecture: $_ARCH"
    _err "Supported: x86_64 / amd64, aarch64 / arm64"
    exit 1 ;;
esac

case "$_OS" in
  linux)  _PLATFORM="linux-$_ARCH" ;;
  darwin) _PLATFORM="darwin-$_ARCH" ;;
  *)
    _err "Unsupported OS: $_OS"
    _err "Windows users: run install.ps1"
    _err "  iwr -useb https://install.sigmashake.com/install.ps1 | iex"
    exit 1 ;;
esac

# Resolve VERSION: explicit --version=X wins; otherwise pick the per-platform
# default so darwin doesn't get pinned to a linux version that was never
# published for it.
_DEFAULT_VERSION="$(_default_version_for "$_PLATFORM")"
if [ -z "$VERSION" ]; then
  VERSION="$_DEFAULT_VERSION"
  if [ -z "$_DEFAULT_VERSION" ]; then
    _err "No default version for platform: $_PLATFORM"
    _err "See https://sigmashake.com/pricing to subscribe, then re-run this installer."
    exit 1
  fi
elif [ -z "$_DEFAULT_VERSION" ] || [ "$VERSION" != "$_DEFAULT_VERSION" ]; then
  _err "no baked SHA256 for ${_PLATFORM}@${VERSION} (available: ${_DEFAULT_VERSION:-none})"
  _err "The requested release must be published and baked into this installer first."
  exit 1
fi

_EXPECTED_SHA="$(_sha256_for "$_PLATFORM")"
if [ -z "$_EXPECTED_SHA" ]; then
  _err "No SHA256 on record for platform: $_PLATFORM"
  _err "The platform release must be published and baked into this installer first."
  exit 1
fi

# ── Install target ────────────────────────────────────────────────────────────
# --install-path is the orchestrator-only exact-active-target mode. It replaces
# one already-probed `ssg` directory entry and never touches sibling assets.
if [ "$INSTALL_PATH_SET" = "1" ]; then
  case "$INSTALL_PATH" in
    */) _err "--install-path must not end with a directory separator"; exit 1 ;;
    /*) ;;
    *) _err "--install-path must be an absolute path"; exit 1 ;;
  esac
  if [ "$(basename "$INSTALL_PATH")" != "ssg" ] || [ ! -f "$INSTALL_PATH" ]; then
    _err "--install-path must name an existing ssg executable"
    exit 1
  fi
  INSTALL_DIR="$(dirname "$INSTALL_PATH")"
  if [ ! -w "$INSTALL_DIR" ]; then
    _err "active ssg directory is not writable: $INSTALL_DIR"
    exit 1
  fi
elif [ -z "$INSTALL_DIR" ]; then
  _LOCAL_BIN="$HOME/.local/bin"
  if mkdir -p "$_LOCAL_BIN" 2>/dev/null && [ -w "$_LOCAL_BIN" ]; then
    INSTALL_DIR="$_LOCAL_BIN"
  elif [ -w "/usr/local/bin" ]; then
    INSTALL_DIR="/usr/local/bin"
  else
    mkdir -p "$_LOCAL_BIN"
    INSTALL_DIR="$_LOCAL_BIN"
  fi
fi

# ── Prerequisites ─────────────────────────────────────────────────────────────
for _tool in curl tar; do
  if ! command -v "$_tool" >/dev/null 2>&1; then
    _err "Required tool not found: $_tool"
    exit 1
  fi
done

if ! command -v sha256sum >/dev/null 2>&1 && ! command -v shasum >/dev/null 2>&1; then
  _err "SHA256 verification requires sha256sum (Linux) or shasum (macOS) — neither found"
  exit 1
fi

_checksum() {
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" | awk '{print $1}'
  else
    shasum -a 256 "$1" | awk '{print $1}'
  fi
}

_canonical_path() {
  _CANON_PATH="$1"
  if command -v realpath >/dev/null 2>&1; then
    realpath "$_CANON_PATH" 2>/dev/null && return
  fi
  if command -v readlink >/dev/null 2>&1; then
    readlink -f "$_CANON_PATH" 2>/dev/null && return
  fi
  _CANON_DIR="$(dirname "$_CANON_PATH")"
  _CANON_BASE="$(basename "$_CANON_PATH")"
  (cd -P "$_CANON_DIR" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$_CANON_BASE") \
    || printf '%s\n' "$_CANON_PATH"
}

_ssg_version_of() {
  # Bounded probe ONLY: without a timeout command, a broken/hostile shadowed
  # binary could hang the installer — skip the probe (path compare still runs).
  command -v timeout >/dev/null 2>&1 || return 0
  timeout 5 "$1" --version 2>/dev/null | awk 'NR == 1 { gsub(/\r/, ""); print; exit }'
}

# ── Header ────────────────────────────────────────────────────────────────────
printf '\n%sSigmaShake Installer%s  ssg %s · %s → %s\n\n' \
  "$_BOLD" "$_RESET" "$VERSION" "$_PLATFORM" "$INSTALL_DIR" >&2

# ── License notice ────────────────────────────────────────────────────────────
# A valid fleet code means the buyer has paid. Fleet enroll activates the
# license, so the banner is not shown. The server stays the authority.
if [ -z "$_FLEET_CODE" ]; then
  printf '%sIMPORTANT — Paid subscription required%s\n' "$_BOLD" "$_RESET" >&2
  printf 'ssg requires an active paid SigmaShake subscription to operate.\n' >&2
  printf 'The download and install are free. There is no free tier and no trial of the value;\n' >&2
  printf '%s\n' "a \$5/month subscription unlocks it." >&2
  printf '\n' >&2
  printf '  Start a subscription: https://billing.sigmashake.com/start-checkout\n' >&2
  printf '  Pricing details:      https://sigmashake.com/pricing\n' >&2
  printf '\n' >&2
  printf 'The binary will be installed now. The install is free. Without an active\n' >&2
  printf 'subscription, ssg blocks every agent tool call (fail-closed)\n' >&2
  printf 'until you run  ssg auth login.\n' >&2
  printf '\n' >&2
fi

# ── Download ──────────────────────────────────────────────────────────────────
_TMP="$(mktemp -d)"
_EXACT_STAGE=""
# shellcheck disable=SC2329  # Called by the trap on the next line.
_cleanup() {
  if [ -n "$_EXACT_STAGE" ]; then rm -f "$_EXACT_STAGE"; fi
  rm -rf "$_TMP"
}
trap _cleanup EXIT INT TERM

_ARCHIVE="$_TMP/ssg.tgz"
_EXTRACT="$_TMP/extract"
# The CLI ships through the paid-funnel download CDN: /cli/<version>/<file>
# 302-redirects to the canonical GitHub Releases asset (the same artifact the
# Homebrew tap pulls). The tarball root holds the `ssg` binary + `public/`.
_CDN_URL="${BASE}/cli/$VERSION/ssg-$_PLATFORM.tar.gz"

_info "Downloading from the SigmaShake CDN..."
_T_PHASE="$(_now_ms)"
if [ "$DRY_RUN" = "1" ]; then
  _warn "[dry-run] would fetch: $_CDN_URL"
else
  if ! curl -fsSL --connect-timeout 30 --max-time 120 -o "$_ARCHIVE" "$_CDN_URL"; then
    _err "Download failed."
    _err "URL: $_CDN_URL"
    _err "Check your connection, or see https://sigmashake.com/pricing"
    exit 1
  fi
  _DOWNLOAD_MS=$(($(_now_ms) - _T_PHASE))
fi

# ── Extract ───────────────────────────────────────────────────────────────────
_info "Extracting binaries..."
_T_PHASE="$(_now_ms)"
if [ "$DRY_RUN" != "1" ]; then
  mkdir -p "$_EXTRACT"
  tar xzf "$_ARCHIVE" -C "$_EXTRACT"
  _SSG_BIN="$_EXTRACT/ssg"
  if [ ! -f "$_SSG_BIN" ]; then
    _err "Binary not found in archive (expected: ssg at the archive root)"
    exit 1
  fi
  _EXTRACT_MS=$(($(_now_ms) - _T_PHASE))
fi

# ── SHA256 integrity check ────────────────────────────────────────────────────
_info "Verifying integrity..."
_T_PHASE="$(_now_ms)"
if [ "$DRY_RUN" != "1" ]; then
  _ACTUAL="$(_checksum "$_SSG_BIN")"
  if [ "$_ACTUAL" != "$_EXPECTED_SHA" ]; then
    _err "SHA256 mismatch — refusing to install"
    _err "  expected: $_EXPECTED_SHA"
    _err "  got:      $_ACTUAL"
    _err "Report issues at: https://sigmashake.com/security"
    exit 1
  fi
  _VERIFY_MS=$(($(_now_ms) - _T_PHASE))
  _info "Integrity verified."
fi

# ── Install ───────────────────────────────────────────────────────────────────
if [ "$DRY_RUN" = "1" ]; then
  if [ "$INSTALL_PATH_SET" = "1" ]; then
    _warn "[dry-run] would atomically replace active binary: $INSTALL_PATH"
    _warn "[dry-run] sibling files and public/ would remain untouched"
  else
    _warn "[dry-run] would install to: $INSTALL_DIR"
    _warn "[dry-run] files: ssg  (+ any archive-root sidecars)"
    _warn "[dry-run] would also install: public/ (dashboard React SPA assets)"
  fi
  echo "" >&2
  _info "Dry-run complete — no files written."
  exit 0
fi

# Install every top-level file in the archive co-located so ssg auto-discovers
# its siblings. The CDN tarball ships `ssg` (+ `public/`, handled below); any
# future archive-root sidecars install the same way.
# Resolution order in ssg: SSG_HOOK_FAST_BIN env var → sibling of execPath → PATH.
_INSTALLED=0
if [ "$INSTALL_PATH_SET" = "1" ]; then
  # Stage on the target filesystem so the final rename is atomic. Exact-target
  # mode intentionally installs only the verified binary: a generic Bun/npm
  # bin directory may contain an unrelated `public/` tree or other sidecars.
  _EXACT_STAGE="$INSTALL_DIR/.ssg.new.$$"
  if command -v install >/dev/null 2>&1; then
    install -m 755 "$_SSG_BIN" "$_EXACT_STAGE" 2>/dev/null || {
      _err "Failed to stage active ssg replacement"
      exit 1
    }
  else
    if ! cp "$_SSG_BIN" "$_EXACT_STAGE" || ! chmod 755 "$_EXACT_STAGE"; then
      _err "Failed to stage active ssg replacement"
      exit 1
    fi
  fi
  if ! mv -f "$_EXACT_STAGE" "$INSTALL_PATH"; then
    _err "Failed to atomically replace active ssg binary"
    exit 1
  fi
  _EXACT_STAGE=""
  _INSTALLED=1
else
  mkdir -p "$INSTALL_DIR" 2>/dev/null || {
    _err "Cannot create install directory: $INSTALL_DIR"
    _err "Pick a writable location and rerun:"
    _err "  curl -fsSL https://install.sigmashake.com | sh -s -- --install-dir=<dir>"
    exit 1
  }
  for _f in "$_EXTRACT"/*; do
    [ -f "$_f" ] || continue   # skip public/ (a directory)
    _NAME="$(basename "$_f")"
    _COPY_OK=0
    if command -v install >/dev/null 2>&1; then
      if install -m 755 "$_f" "$INSTALL_DIR/$_NAME" 2>/dev/null; then
        _COPY_OK=1
      elif cp "$_f" "$INSTALL_DIR/$_NAME" && chmod 755 "$INSTALL_DIR/$_NAME"; then
        _COPY_OK=1
      fi
    else
      if cp "$_f" "$INSTALL_DIR/$_NAME" && chmod 755 "$INSTALL_DIR/$_NAME"; then
        _COPY_OK=1
      fi
    fi
    if [ "$_COPY_OK" != "1" ]; then
      _err "Failed to install $_NAME to $INSTALL_DIR (permissions? disk full?)"
      _err "Rerun with a writable location:"
      _err "  curl -fsSL https://install.sigmashake.com | sh -s -- --install-dir=<dir>"
      exit 1
    fi
    _INSTALLED=$((_INSTALLED + 1))
  done
fi

if [ "$_INSTALLED" -eq 0 ]; then
  _err "No binaries extracted from the archive — download may be corrupt"
  exit 1
fi

# Install the React SPA assets adjacent to the binary. `ssg serve` resolves
# its public dir via candidate #3 (`<execDir>/public/`) when run outside a
# checkout — without this step a stale public/ from a previous install would
# silently serve the old dashboard UI after a binary-only refresh.
if [ "$INSTALL_PATH_SET" != "1" ] && [ -d "$_EXTRACT/public" ]; then
  _PUBLIC_DST="$INSTALL_DIR/public"
  mkdir -p "$_PUBLIC_DST"
  find "$_PUBLIC_DST" -mindepth 1 -delete 2>/dev/null || true
  cp -R "$_EXTRACT/public/." "$_PUBLIC_DST/"
  _info "Installed dashboard assets to $_PUBLIC_DST"
fi

_info "Installed $_INSTALLED files to $INSTALL_DIR"

if [ "$YES" = "1" ]; then
  _write_yes_consent
fi

# ── PATH hint ─────────────────────────────────────────────────────────────────
hash -r 2>/dev/null || true
_PATH_SSG="$(command -v ssg 2>/dev/null || true)"
_SHADOW_WARN=0
_SHADOW_VERSION=""
_SHADOW_PATH_REAL=""
_INSTALLED_SSG_REAL="$(_canonical_path "$INSTALL_DIR/ssg")"
_SHADOW_SEEN=""

_shadow_mark_seen() {
  # "|"-delimited (not space-delimited): real paths can contain spaces.
  _SHADOW_SEEN="$_SHADOW_SEEN|$1|"
}

_shadow_is_seen() {
  case "$_SHADOW_SEEN" in
    *"|$1|"*) return 0 ;;
    *) return 1 ;;
  esac
}

# Case-matched next-step advice for a shadowing/stale `ssg` found at $1 —
# shared by the primary `command -v` hit below and the full-PATH walk
# (OI-044) so every stale copy gets the same actionable guidance, not just
# the first.
_shadow_advice() {
  case "$1" in
    *pnpm*)
      _warn "  Remove the old pnpm install:  pnpm remove -g @sigmashake/ssg" ;;
    *npm*|*/node_modules/.bin/ssg|*/.nvm/*)
      _warn "  Remove the old npm install:   npm uninstall -g @sigmashake/ssg" ;;
    *bun*)
      _warn "  Remove the old Bun install:   bun remove -g @sigmashake/ssg" ;;
    /usr/local/bin/ssg)
      _warn "  Remove the old copy:          sudo rm -f \"$1\"" ;;
    */.sigmashake/bin/ssg)
      # This path is created + symlinked by SigmaShake Desktop
      # (PathRegistration.swift) and prepended to PATH via ~/.zprofile — it is
      # also the governance hook binary on a machine with Desktop installed.
      # Never tell the user to delete or overwrite it in place: that breaks
      # the running Desktop app and can wedge every governed tool call.
      _warn "  This is SigmaShake Desktop's managed ssg — do not delete it."
      _warn "  Desktop's bundled CLI is taking precedence over this fresh install."
      _warn "  Preferred:       update SigmaShake Desktop so its bundled ssg matches."
      _warn "  Or, for this shell only:  export PATH=\"$INSTALL_DIR:\$PATH\" && hash -r" ;;
    *)
      _warn "  Remove or upgrade the old install at the path above." ;;
  esac
}

# Returns 0 when the user agrees: -y, or a yes answer on the terminal.
# Without -y and without a terminal, the answer is no.
_confirm() {
  if [ "$YES" = "1" ]; then return 0; fi
  if [ -t 1 ] && [ -r /dev/tty ]; then
    _T_ASK="$(_now_ms)"
    printf '%s [Y/n] ' "$1" >&2
    if read -r _ANSWER </dev/tty; then :; else _ANSWER="n"; fi
    _AUTH_EXCLUDE_MS=$((_AUTH_EXCLUDE_MS + $(_now_ms) - _T_ASK))
    case "$_ANSWER" in
      ''|[yY]*) return 0 ;;
    esac
  fi
  return 1
}

_PATH_MARK="# added by sigmashake installer"

# The rc line must stay valid inside double quotes. Skip an unusual install dir.
_path_dir_plain() {
  case "$INSTALL_DIR" in
    *'"'*|*'$'*|*'`'*|*\\*) return 1 ;;
  esac
}

# Prints the exact rc line for the install directory.
_path_line() {
  case "$INSTALL_DIR" in
    "${HOME:-}/.local/bin") _LINE_DIR="\$HOME/.local/bin" ;;
    *) _LINE_DIR="$INSTALL_DIR" ;;
  esac
  printf 'export PATH="%s:%s" %s\n' "$_LINE_DIR" "\$PATH" "$_PATH_MARK"
}

# Appends the PATH line to the rc file $1 once. A rerun adds nothing and sets
# _PATH_PRESENT=1 so the caller can say so.
_PATH_PRESENT=0
_add_path_line() {
  _RC_LINE="$(_path_line)"
  if [ -f "$1" ] && grep -qxF "$_RC_LINE" "$1" 2>/dev/null; then
    _PATH_PRESENT=1
    return 0
  fi
  printf '\n%s\n' "$_RC_LINE" >> "$1" 2>/dev/null
}

if [ -z "$_PATH_SSG" ]; then
  printf '\n' >&2
  _warn "ssg is not yet on your PATH."
  _SHELL_RC=""
  _RC_FILE=""
  # The "~" below is display text for the user. It is never used as a path.
  # shellcheck disable=SC2088
  case "$(basename "${SHELL:-sh}")" in
    zsh)  _SHELL_RC="~/.zshrc"; _RC_FILE="${HOME:-}/.zshrc" ;;
    fish) _warn "  Run once:  fish_add_path $INSTALL_DIR" ;;
    bash) _SHELL_RC="~/.bashrc"; _RC_FILE="${HOME:-}/.bashrc" ;;
    *)    _SHELL_RC="~/.profile"; _RC_FILE="${HOME:-}/.profile" ;;
  esac
  # No HOME means no rc file to edit: keep the printed hint only.
  if [ -z "${HOME:-}" ]; then _RC_FILE=""; fi
  # F2 (X163F): add the PATH line for the user when they agree (-y or a yes
  # answer). This removes one manual step from the one-command install.
  _PATH_ADDED=0
  if [ -n "$_RC_FILE" ] && [ "$NO_MODIFY_PATH" = "0" ] && _path_dir_plain \
     && _confirm "Add $INSTALL_DIR to your PATH in $_SHELL_RC?"; then
    if _add_path_line "$_RC_FILE"; then
      _PATH_ADDED=1
      if [ "$_PATH_PRESENT" = "1" ]; then
        _info "The PATH line is already present in $_SHELL_RC."
      else
        _info "Added the PATH line to $_SHELL_RC (marked: $_PATH_MARK)."
      fi
      _info "  To undo: delete the line that ends with \"$_PATH_MARK\" from $_SHELL_RC"
    else
      _warn "Could not write $_SHELL_RC."
    fi
  fi
  if [ "$_PATH_ADDED" = "0" ] && [ -n "$_SHELL_RC" ]; then
    # shellcheck disable=SC2016  # The literal text $PATH is shown to the user.
    _PATH_EX='$PATH'
    _warn "  Add to $_SHELL_RC:  export PATH=\"$INSTALL_DIR:$_PATH_EX\""
  fi
  _warn "  Then open a new terminal or run:  export PATH=\"$INSTALL_DIR:\$PATH\""
  printf '\n' >&2
else
  _SHADOW_PATH_REAL="$(_canonical_path "$_PATH_SSG")"
  _SHADOW_VERSION="$(_ssg_version_of "$_PATH_SSG" || true)"
  # Warn on a different resolved path, or (when the bounded probe ran) a
  # different version at the same path. An empty probe result is "unknown",
  # not a mismatch — never false-positive on probe unavailability.
  if [ "$_SHADOW_PATH_REAL" != "$_INSTALLED_SSG_REAL" ]; then
    _SHADOW_WARN=1
  elif [ -n "$_SHADOW_VERSION" ] && [ "$_SHADOW_VERSION" != "$VERSION" ]; then
    _SHADOW_WARN=1
  fi
  _shadow_mark_seen "$_SHADOW_PATH_REAL"
fi

printf '\n' >&2
_info "ssg ${VERSION} installed to $INSTALL_DIR."
if [ "$_SHADOW_WARN" = "1" ]; then
  printf '\n' >&2
  _warn "PATH SHADOWING DETECTED: running 'ssg' still resolves to:"
  _warn "  $_PATH_SSG"
  _warn "  resolved path: $_SHADOW_PATH_REAL"
  _warn "  version there: ${_SHADOW_VERSION:-<unreadable>} (expected: $VERSION)"
  _warn "  installed binary: $INSTALL_DIR/ssg"
  _shadow_advice "$_PATH_SSG"
  _warn "  Or put this install first:    export PATH=\"$INSTALL_DIR:\$PATH\""
  _warn "  Then clear the shell cache:   hash -r"
  _warn "  If it still resolves stale, open a new shell and retry:  ssg --version"
fi

# Walk the REST of PATH beyond the first `command -v` hit — a stale copy
# further down PATH is invisible to the check above and can silently take
# over again after any future PATH reorder (OI-044, ledger: `which -a ssg`
# on a real host surfaced 4 distinct copies, only the first of which the old
# check ever saw). Warn once per unique resolved path; never re-report
# $_PATH_SSG (already handled above) or a copy that IS the binary we just
# installed (e.g. a duplicate PATH entry pointing at $INSTALL_DIR).
if [ -n "$_PATH_SSG" ]; then
  _SHADOW_OLD_IFS="$IFS"
  IFS=':'
  for _SHADOW_DIR in $PATH; do
    IFS="$_SHADOW_OLD_IFS"
    [ -z "$_SHADOW_DIR" ] && _SHADOW_DIR="."
    _SHADOW_CAND="$_SHADOW_DIR/ssg"
    if [ -f "$_SHADOW_CAND" ] && [ -x "$_SHADOW_CAND" ]; then
      _SHADOW_CAND_REAL="$(_canonical_path "$_SHADOW_CAND")"
      if ! _shadow_is_seen "$_SHADOW_CAND_REAL"; then
        _shadow_mark_seen "$_SHADOW_CAND_REAL"
        if [ "$_SHADOW_CAND_REAL" != "$_INSTALLED_SSG_REAL" ]; then
          _SHADOW_CAND_VERSION="$(_ssg_version_of "$_SHADOW_CAND" || true)"
          printf '\n' >&2
          _warn "ADDITIONAL STALE COPY ON PATH: $_SHADOW_CAND"
          _warn "  resolved path: $_SHADOW_CAND_REAL"
          _warn "  version there: ${_SHADOW_CAND_VERSION:-<unreadable>} (expected: $VERSION)"
          _shadow_advice "$_SHADOW_CAND"
        fi
      fi
    fi
    IFS=':'
  done
  IFS="$_SHADOW_OLD_IFS"
fi
printf '\n' >&2

# ── Subscription reminder ─────────────────────────────────────────────────────
# The fleet path enrolls with a code and activates the license itself.
if [ -z "$_FLEET_CODE" ]; then
printf '%sSubscription required to activate%s\n' "$_BOLD" "$_RESET" >&2
printf 'ssg is installed. First-time setup (ssg init) wires your agent hooks.\n' >&2
printf 'Until ssg auth login activates a paid subscription, every agent tool\n' >&2
printf 'call is blocked (fail-closed).\n' >&2
printf '\n' >&2
printf '  1. Start a subscription: https://billing.sigmashake.com/start-checkout\n' >&2
printf '  2. Activate your license: ssg auth login\n' >&2
printf '  3. Verify governance:     ssg status\n' >&2
printf '\n' >&2
fi

# ── First-time setup + daemon + account link + status (all timed) ────────────
_INIT_MS="skipped"
_DAEMON_MS="skipped"
_AUTH_MS="skipped"
_STATUS_MS="skipped"
_FLEET_MS="skipped"
_FLEET_ENROLLED=0
_FLEET_LICENSE_FAILED=0
_AUTH_NEXT_STEPS=0
_RESULT="ok"
# Exit code of `ssg fleet enroll` when the machine is enrolled but the license
# did not activate. The enrollment stands. Only the license step failed.
_FLEET_EXIT_LICENSE=6

_ssg_daemon_running() {
  "$INSTALL_DIR/ssg" daemon --status >/dev/null 2>&1
}

# Starts `ssg <args>` fully detached, with its output in the log file $1.
# Fully detach stdio from the installer's own descriptors (never /dev/null
# alone — a bare `&` still inherits stdin/stdout, which on a `curl | sh` or
# `$(...)`-captured invocation keeps that pipe's write end open and the
# outer command/capture hanging long after this script exits).
# setsid additionally detaches from the controlling session when present;
# nohup (always present on linux/darwin) is the fallback.
_spawn_detached() {
  _SPAWN_LOG="$1"
  shift
  if command -v setsid >/dev/null 2>&1; then
    setsid "$INSTALL_DIR/ssg" "$@" </dev/null >"$_SPAWN_LOG" 2>&1 &
  else
    nohup "$INSTALL_DIR/ssg" "$@" </dev/null >"$_SPAWN_LOG" 2>&1 &
  fi
}

# F5 (X163F): offer the local dashboard. ssg gates it by license. The
# installer adds no bypass. It only starts `ssg serve` and reports the result.
_DASH_URL="${SSG_DASHBOARD_URL:-http://127.0.0.1:5599}"

# True when something already answers at the dashboard URL (a rerun or an
# upgrade). Any HTTP status counts. Without curl the answer is no.
_dashboard_running() {
  # The probe targets loopback: proxy env vars must not answer for it.
  command -v curl >/dev/null 2>&1 \
    && curl --noproxy '*' --proto '=http,https' -s -o /dev/null -m 1 "$_DASH_URL"
}

_start_dashboard() {
  if [ "$NO_SERVE" = "0" ] && _dashboard_running; then
    _info "Dashboard already running: $_DASH_URL"
    return 0
  fi
  if [ "$NO_SERVE" = "1" ] || ! _confirm "Start the dashboard now?"; then
    _info "Dashboard: run  ssg serve  and open $_DASH_URL"
    return 0
  fi
  _SERVE_HOME="${SIGMASHAKE_HOME:-$HOME/.sigmashake}"
  mkdir -p "$_SERVE_HOME" 2>/dev/null || true
  _SERVE_LOG="$_SERVE_HOME/serve-install.log"
  _spawn_detached "$_SERVE_LOG" serve
  _SERVE_PID=$!
  sleep 1
  if kill -0 "$_SERVE_PID" 2>/dev/null; then
    _info "Dashboard started: $_DASH_URL"
  elif grep -q "Pro license required" "$_SERVE_LOG" 2>/dev/null; then
    # The license gate closed serve. This is the expected state before
    # `ssg auth login`, so it is news, not a failure.
    _info "Dashboard: after  ssg auth login , run  ssg serve  and open $_DASH_URL"
  else
    _warn "Dashboard did not start (log: $_SERVE_LOG)."
    _warn "  After  ssg auth login  with an active subscription, run:  ssg serve"
  fi
}

# `ssg init` writes .sigmashake/ into the current directory.
_cwd_writable() {
  [ -w . ]
}

# Runs `ssg init` and sets _INIT_RC.
_run_init() {
  if [ "$YES" = "1" ]; then
    # Installer -y (flag or SSG_INSTALL_YES=1) is explicit ToS consent:
    # recorded above via _write_yes_consent, so --accept-terms keeps init
    # non-interactive.
    if "$INSTALL_DIR/ssg" init -y --accept-terms; then _INIT_RC=0; else _INIT_RC=$?; fi
  else
    # Interactive-TTY consent path (no -y): `ssg init` scaffolds .sigmashake/
    # and wires the governance hook for every detected AI client. The -y flag
    # here only accepts THIS plan prompt so we don't stall mid-script waiting
    # on a keypress — it is not ToS consent (no consent.json write above).
    if "$INSTALL_DIR/ssg" init -y; then _INIT_RC=0; else _INIT_RC=$?; fi
  fi
}

_RUN_SETUP=0
if [ "$NO_SETUP" = "0" ]; then
  if [ "$YES" = "1" ] || { [ -t 0 ] && [ -t 1 ]; }; then
    _RUN_SETUP=1
  fi
fi

if [ "$_RUN_SETUP" = "1" ]; then
  printf '%sRunning first-time setup...%s\n\n' "$_BOLD" "$_RESET" >&2

  # ── Fleet enrollment ─────────────────────────────────────────────────────
  # Runs before init. Enrollment also activates the license, and an unlicensed
  # ssg blocks every check. The code goes to the child by environment only.
  if [ -n "$_FLEET_CODE" ]; then
    _T_PHASE="$(_now_ms)"
    if SSG_FLEET_CODE="$_FLEET_CODE" "$INSTALL_DIR/ssg" fleet enroll; then _FLEET_RC=0; else _FLEET_RC=$?; fi
    _FLEET_MS=$(($(_now_ms) - _T_PHASE))
    _FLEET_CODE=""
    if [ "$_FLEET_RC" = "0" ]; then
      _FLEET_ENROLLED=1
    elif [ "$_FLEET_RC" = "$_FLEET_EXIT_LICENSE" ]; then
      # The machine is enrolled. The agent credentials are saved. Only the
      # license did not activate. Do not blame the code. Go on with setup.
      _FLEET_ENROLLED=1
      _FLEET_LICENSE_FAILED=1
      _warn "This machine is enrolled in Fleet, but the license did not activate."
      _warn "Next step: ssg auth login"
    else
      printf 'Error: fleet enrollment failed (exit %s). The code may be expired, used, or revoked. Get a new command at https://fleet.sigmashake.com/welcome and run it again.\n' "$_FLEET_RC" >&2
      exit 5
    fi
  fi

  # ── Account link ─────────────────────────────────────────────────────────
  # Runs before init. An unlicensed ssg blocks every check, so the license
  # must be active before init wires the agent hooks.
  if [ -n "${SSG_INSTALL_TOKEN:-}" ]; then
    _T_PHASE="$(_now_ms)"
    if "$INSTALL_DIR/ssg" auth login --no-browser; then
      _AUTH_MS=$(($(_now_ms) - _T_PHASE))
      _FLEET_LICENSE_FAILED=0
    else
      _AUTH_RC=$?
      _AUTH_MS=$(($(_now_ms) - _T_PHASE))
      _warn "Account link failed (exit $_AUTH_RC). Rerun: ssg auth login"
      _RESULT="partial"
    fi
    # Token hygiene: single-use / short-TTL — drop it from this process's
    # environment the moment the child that consumes it has returned.
    unset SSG_INSTALL_TOKEN
  elif [ "$_FLEET_ENROLLED" = "1" ]; then
    # Fleet enrollment already activated the license, or printed the next
    # step above. Skip the account link unless SSG_INSTALL_TOKEN is set.
    :
  elif [ "$NO_AUTH" = "0" ] && [ -t 1 ] && [ -r /dev/tty ]; then
    _T_BEFORE_AUTH="$(_now_ms)"
    if "$INSTALL_DIR/ssg" auth login </dev/tty; then
      :
    else
      _warn "Account link failed. Rerun: ssg auth login"
      _RESULT="partial"
    fi
    _AUTH_EXCLUDE_MS=$((_AUTH_EXCLUDE_MS + $(_now_ms) - _T_BEFORE_AUTH))
    _AUTH_MS="interactive"
  else
    _AUTH_NEXT_STEPS=1
  fi

  # F1 (X163F): init is a per-project step. The binary is already installed.
  # A cwd that is not writable (for example `cd /`) is not an install failure.
  # The installer defers init, prints the next step, and exits 0. A real init
  # failure in a writable cwd still exits with the init exit code.
  _INIT_RC=0
  _INIT_DEFERRED=0
  _T_PHASE="$(_now_ms)"
  if _cwd_writable; then
    _run_init
    _INIT_MS=$(($(_now_ms) - _T_PHASE))
  else
    _INIT_DEFERRED=1
  fi
  if [ "$_INIT_RC" != "0" ]; then
    _err "First-time setup failed (exit $_INIT_RC). Rerun: ssg init -y --accept-terms"
    _err "  Run it inside your project:  cd <your project> && ssg init -y --accept-terms"
    exit "$_INIT_RC"
  fi

  # ── Daemon ───────────────────────────────────────────────────────────────
  _T_PHASE="$(_now_ms)"
  if [ "$_FLEET_ENROLLED" = "1" ] && _ssg_daemon_running; then
    # A daemon that started before enrollment has old credentials. Restart it
    # so it reads the new agent credentials. Use `ssg daemon --stop` to stop it.
    "$INSTALL_DIR/ssg" daemon --stop >/dev/null 2>&1 || true
    _DAEMON_WAITED=0
    while [ "$_DAEMON_WAITED" -lt 10000 ] && _ssg_daemon_running; do
      sleep 0.25
      _DAEMON_WAITED=$((_DAEMON_WAITED + 250))
    done
    if _ssg_daemon_running; then
      # The daemon reads agent.jwt again at every fleet heartbeat (every 5
      # minutes). See sigmashake-gov/go/internal/daemon/fleet_heartbeat.go.
      # So the old daemon still picks up the new enrollment.
      _warn "The old governance daemon did not stop within 10s. It reads agent.jwt at every heartbeat, so the new enrollment still takes effect."
      _warn "  To restart it by hand:  ssg daemon --stop && ssg daemon"
    else
      _info "Restarting governance daemon after fleet enrollment."
    fi
  fi
  if _ssg_daemon_running; then
    _info "Governance daemon already running."
  else
    _SSG_HOME="${SIGMASHAKE_HOME:-$HOME/.sigmashake}"
    mkdir -p "$_SSG_HOME" 2>/dev/null || true
    _DAEMON_LOG="$_SSG_HOME/daemon-install.log"
    _spawn_detached "$_DAEMON_LOG" daemon
    _DAEMON_UP=0
    _DAEMON_WAITED=0
    while [ "$_DAEMON_WAITED" -lt 10000 ]; do
      if _ssg_daemon_running; then
        _DAEMON_UP=1
        break
      fi
      sleep 0.25
      _DAEMON_WAITED=$((_DAEMON_WAITED + 250))
    done
    if [ "$_DAEMON_UP" = "1" ]; then
      _info "Governance daemon started."
    else
      _warn "Governance daemon did not come up within 10s. Start it manually: ssg daemon"
      _RESULT="partial"
    fi
  fi
  _DAEMON_MS=$(($(_now_ms) - _T_PHASE))

  # ── Status ───────────────────────────────────────────────────────────────
  if [ "$_INIT_DEFERRED" = "1" ]; then
    _warn "ssg init was skipped: $(pwd) is not writable."
    _warn "  Next step, in your project:  cd <your project> && ssg init -y --accept-terms"
  else
    _T_PHASE="$(_now_ms)"
    if "$INSTALL_DIR/ssg" status; then
      :
    else
      _STATUS_RC=$?
      _warn "ssg status reported a problem (exit $_STATUS_RC). Check: ssg status"
      _RESULT="partial"
    fi
    _STATUS_MS=$(($(_now_ms) - _T_PHASE))
  fi
  if [ "$_AUTH_NEXT_STEPS" = "1" ]; then
    printf '%sNext steps:%s  ssg auth login  →  ssg status\n\n' "$_BOLD" "$_RESET" >&2
  fi
  if [ "$_FLEET_ENROLLED" = "1" ]; then
    printf 'Fleet: this machine is enrolled. Watch it connect at https://fleet.sigmashake.com/welcome\n' >&2
    if [ "$_FLEET_LICENSE_FAILED" = "1" ]; then
      _warn "License: not active. Run:  ssg auth login"
      _RESULT="partial"
    else
      _info "License: if 'ssg status' shows no active Pro license, run:  ssg auth login"
    fi
  fi
  if [ "$_INIT_DEFERRED" = "0" ]; then
    _start_dashboard
  fi
else
  if [ -n "$_FLEET_CODE" ]; then
    _warn "SSG_FLEET_CODE was set, but setup did not run (--no-setup). This machine is not enrolled."
    _warn "  To enroll later, run the install command from https://fleet.sigmashake.com/welcome again without --no-setup,"
    _warn "  or run:  SSG_FLEET_CODE=<your code> ssg fleet enroll"
  fi
  printf '%sNext steps:%s  ssg auth login  →  ssg init  →  ssg status\n\n' "$_BOLD" "$_RESET" >&2
fi

# ── Timing line + JSON ─────────────────────────────────────────────────────────
# result=partial means a STEP FAILED (daemon didn't come up, auth failed, or
# status failed) above — never for a step skipped by design (--no-setup,
# --no-auth, or no token/no TTY for auth). over-budget is purely a total_ms
# vs target_ms signal and does not itself imply any step failed.
_TARGET_MS=60000
_TOTAL_MS=$(($(_now_ms) - _T_SCRIPT_START - _AUTH_EXCLUDE_MS))
if [ "$_TOTAL_MS" -gt "$_TARGET_MS" ]; then
  _RESULT="over-budget"
fi

printf 'ssg-install-timing: installer=sh os=%s arch=%s version=%s fetch_ms=skipped download_ms=%s verify_ms=%s extract_ms=%s fleet_ms=%s init_ms=%s daemon_ms=%s auth_ms=%s status_ms=%s total_ms=%s target_ms=%s result=%s\n' \
  "$_OS" "$_ARCH" "$VERSION" "$_DOWNLOAD_MS" "$_VERIFY_MS" "$_EXTRACT_MS" "$_FLEET_MS" "$_INIT_MS" "$_DAEMON_MS" "$_AUTH_MS" "$_STATUS_MS" "$_TOTAL_MS" "$_TARGET_MS" "$_RESULT" >&2

_TIMING_JSON_DIR="${SIGMASHAKE_HOME:-$HOME/.sigmashake}"
_TIMING_JSON_PATH="$_TIMING_JSON_DIR/install-timing.json"
_RECORDED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date +"%Y-%m-%dT%H:%M:%SZ")"
if mkdir -p "$_TIMING_JSON_DIR" 2>/dev/null; then
  cat > "$_TIMING_JSON_PATH" 2>/dev/null <<EOF || true
{
  "installer": "sh",
  "os": "$_OS",
  "arch": "$_ARCH",
  "version": "$VERSION",
  "fetch_ms": "skipped",
  "download_ms": $(_json_val "$_DOWNLOAD_MS"),
  "verify_ms": $(_json_val "$_VERIFY_MS"),
  "extract_ms": $(_json_val "$_EXTRACT_MS"),
  "fleet_ms": $(_json_val "$_FLEET_MS"),
  "init_ms": $(_json_val "$_INIT_MS"),
  "daemon_ms": $(_json_val "$_DAEMON_MS"),
  "auth_ms": $(_json_val "$_AUTH_MS"),
  "status_ms": $(_json_val "$_STATUS_MS"),
  "total_ms": $(_json_val "$_TOTAL_MS"),
  "target_ms": $_TARGET_MS,
  "result": "$_RESULT",
  "recorded_at": "$_RECORDED_AT"
}
EOF
fi

# result=partial is the only non-fatal-but-non-zero outcome: a real step
# failed above (its own _warn/next-step hint was already printed at the
# point of failure). over-budget and steps skipped by design both exit 0.
if [ "$_RESULT" = "partial" ]; then
  exit 3
fi
exit 0
