#!/usr/bin/env bash
#
# Ch∆In — POSIX install script.
#
# Usage:
#     curl -fsSL https://get.chain.flemming.ai | sh
#     curl -fsSL https://get.chain.flemming.ai | sh -s -- --channel beta
#     curl -fsSL https://get.chain.flemming.ai | sh -s -- --no-bootstrap
#
# What it does:
#   1. Detects host OS + architecture and maps to a manifest key.
#   2. Fetches the channel's `manifest.json` (override with
#      `CHAIN_MANIFEST_URL=<url>`).
#   3. Downloads the matching binary, verifies its sha256 against
#      the manifest, drops it at `~/.local/bin/chain` (or a custom
#      `CHAIN_INSTALL_DIR`).
#   4. Runs `chain bootstrap` so `~/.chain/channels/<channel>/bin/chain`
#      exists, the `~/.chain/bin/chain` shim is in place, and your
#      shell rc has `~/.chain/bin` on PATH.
#
# Environment overrides:
#   CHAIN_CHANNEL          dev | beta | production (default: production)
#   CHAIN_MANIFEST_URL     full URL to manifest.json
#   CHAIN_INSTALL_DIR      where to put the entry-point binary
#                        (default: $HOME/.local/bin)
#   CHAIN_VERSION          force a specific version instead of latest
#   CHAIN_FORCE            1 = skip the "already installed" confirmation
#
# Exit codes:
#   0  installed cleanly
#   1  unsupported platform / missing dependency
#   2  manifest fetch failed
#   3  binary download / verification failed
#   4  bootstrap failed
#   6  an existing install was found and the operator declined
#      (or the run was non-interactive without CHAIN_FORCE=1)
#
# This script is shipped at `installer/install.sh` in the platform
# repo and mirrored as the body of <https://get.chain.flemming.ai>.

set -eu

# -- configuration -------------------------------------------------

CHANNEL="${CHAIN_CHANNEL:-production}"
INSTALL_DIR="${CHAIN_INSTALL_DIR:-$HOME/.local/bin}"
DEFAULT_MANIFEST_HOST="https://releases.chain.flemming.ai"
MANIFEST_URL="${CHAIN_MANIFEST_URL:-${DEFAULT_MANIFEST_HOST}/${CHANNEL}/manifest.json}"
DO_BOOTSTRAP=1
# Skip the "an existing chain is already installed" confirmation.
FORCE="${CHAIN_FORCE:-0}"
# Skip the "proceed with the installation?" confirmation (unattended).
ASSUME_YES="${CHAIN_YES:-0}"

# Allow flags after `sh -s --` via `curl ... | sh -s -- --flag`.
while [ $# -gt 0 ]; do
  case "$1" in
    --channel)        CHANNEL="$2"; MANIFEST_URL="${DEFAULT_MANIFEST_HOST}/${CHANNEL}/manifest.json"; shift 2 ;;
    --manifest-url)   MANIFEST_URL="$2"; shift 2 ;;
    --install-dir)    INSTALL_DIR="$2"; shift 2 ;;
    --no-bootstrap)   DO_BOOTSTRAP=0; shift ;;
    --force)          FORCE=1; shift ;;
    --yes|-y)         ASSUME_YES=1; shift ;;
    --help|-h)
      sed -n '3,40p' "$0"
      exit 0
      ;;
    *)
      echo "unknown option: $1" >&2
      exit 1
      ;;
  esac
done

# -- language ------------------------------------------------------
# UI language from CHAIN_LANG or the locale; same five as flemming.ai.
# Technical/diagnostic lines stay English; the human-facing prose below
# is translated. `printf` templates carry %s for paths/keys.
_loc="${CHAIN_LANG:-${LC_ALL:-${LC_MESSAGES:-${LANG:-en}}}}"
case "$(printf '%s' "$_loc" | cut -c1-2 | tr '[:upper:]' '[:lower:]')" in
  de) L=de ;; es) L=es ;; fr) L=fr ;; it) L=it ;; *) L=en ;;
esac

msg() {
  case "$1" in
    tagline)
      case "$L" in
        de) printf 'souveräne Workflow-Automatisierung' ;;
        es) printf 'automatización soberana de flujos' ;;
        fr) printf 'automatisation souveraine des flux' ;;
        it) printf 'automazione sovrana dei flussi' ;;
        *)  printf 'sovereign workflow automation' ;;
      esac ;;
    plan_head)
      case "$L" in
        de) printf 'Dies geschieht vollständig in deinem Home-Verzeichnis (kein sudo, nichts systemweit):' ;;
        es) printf 'Esto ocurre por completo en tu carpeta personal (sin sudo, nada a nivel de sistema):' ;;
        fr) printf 'Tout se passe dans ton dossier personnel (sans sudo, rien au niveau système) :' ;;
        it) printf 'Tutto avviene nella tua cartella home (senza sudo, niente a livello di sistema):' ;;
        *)  printf 'This will, entirely inside your home directory (no sudo, nothing system-wide):' ;;
      esac ;;
    plan_step1) # %s = platform key
      case "$L" in
        de) printf "  1. lädt die 'chain'-Binary für %s und prüft sha256 + Signatur" "$2" ;;
        es) printf "  1. descargar el binario 'chain' para %s y verificar su sha256 + firma" "$2" ;;
        fr) printf "  1. télécharger le binaire 'chain' pour %s et vérifier son sha256 + signature" "$2" ;;
        it) printf "  1. scaricare il binario 'chain' per %s e verificarne sha256 + firma" "$2" ;;
        *)  printf "  1. download the 'chain' binary for %s and verify its sha256 + signature" "$2" ;;
      esac ;;
    plan_step2) # %s = target bin
      case "$L" in
        de) printf '  2. installiert sie nach %s' "$2" ;;
        es) printf '  2. instalarlo en %s' "$2" ;;
        fr) printf '  2. l'\''installer dans %s' "$2" ;;
        it) printf '  2. installarlo in %s' "$2" ;;
        *)  printf '  2. install it to %s' "$2" ;;
      esac ;;
    plan_step3) # %s = chain home
      case "$L" in
        de) printf "  3. führt 'chain bootstrap' aus (legt %s an, ergänzt %s/bin in deinem PATH)" "$2" "$2" ;;
        es) printf "  3. ejecutar 'chain bootstrap' (crea %s, añade %s/bin a tu PATH)" "$2" "$2" ;;
        fr) printf "  3. exécuter 'chain bootstrap' (crée %s, ajoute %s/bin à ton PATH)" "$2" "$2" ;;
        it) printf "  3. eseguire 'chain bootstrap' (crea %s, aggiunge %s/bin al tuo PATH)" "$2" "$2" ;;
        *)  printf "  3. run 'chain bootstrap' (creates %s, adds %s/bin to your PATH)" "$2" "$2" ;;
      esac ;;
    remove_intro)
      case "$L" in
        de) printf 'Zum späteren Entfernen:' ;;
        es) printf 'Para quitarlo más tarde:' ;;
        fr) printf 'Pour le retirer plus tard :' ;;
        it) printf 'Per rimuoverlo in seguito:' ;;
        *)  printf 'To remove it again later:' ;;
      esac ;;
    remove_keep)
      case "$L" in
        de) printf 'behält deine Daten' ;;
        es) printf 'conserva tus datos' ;;
        fr) printf 'conserve tes données' ;;
        it) printf 'mantiene i tuoi dati' ;;
        *)  printf 'keeps your data' ;;
      esac ;;
    remove_purge) # %s = chain home
      case "$L" in
        de) printf 'entfernt auch %s' "$2" ;;
        es) printf 'elimina también %s' "$2" ;;
        fr) printf 'supprime aussi %s' "$2" ;;
        it) printf 'rimuove anche %s' "$2" ;;
        *)  printf 'removes %s too' "$2" ;;
      esac ;;
    proceed)
      case "$L" in
        de) printf 'Mit der Installation fortfahren? [J/n] ' ;;
        es) printf '¿Continuar con la instalación? [S/n] ' ;;
        fr) printf "Poursuivre l'installation ? [O/n] " ;;
        it) printf "Procedere con l'installazione? [S/n] " ;;
        *)  printf 'Proceed with the installation? [Y/n] ' ;;
      esac ;;
    aborted)
      case "$L" in
        de) printf 'Abgebrochen — nichts wurde verändert.' ;;
        es) printf 'Cancelado — no se cambió nada.' ;;
        fr) printf 'Annulé — rien n'\''a été modifié.' ;;
        it) printf 'Annullato — nulla è stato modificato.' ;;
        *)  printf 'Aborted — nothing was changed.' ;;
      esac ;;
    noninteractive)
      case "$L" in
        de) printf '(nicht-interaktiv: fahre fort. Aus einem Terminal gestartet wirst du gefragt.)' ;;
        es) printf '(no interactivo: continuando. Ejecútalo desde una terminal para que te pregunte.)' ;;
        fr) printf '(non interactif : poursuite. Lance-le depuis un terminal pour être consulté.)' ;;
        it) printf '(non interattivo: proseguo. Avvialo da un terminale per essere interpellato.)' ;;
        *)  printf '(non-interactive: proceeding. Re-run from a terminal to be asked.)' ;;
      esac ;;
    wizard_offer)
      case "$L" in
        de) printf '\nGeführte Einrichtung jetzt starten (Aufgabe + Tools wählen, inkl. Studio)? [J/n] ' ;;
        es) printf '\n¿Iniciar la configuración guiada ahora (tarea + herramientas, incl. Studio)? [S/n] ' ;;
        fr) printf '\nLancer la configuration guidée maintenant (tâche + outils, dont Studio) ? [O/n] ' ;;
        it) printf '\nAvviare ora la configurazione guidata (attività + strumenti, incl. Studio)? [S/n] ' ;;
        *)  printf '\nRun the guided setup now (pick your task + tools, incl. Studio)? [Y/n] ' ;;
      esac ;;
    nextsteps_head)
      case "$L" in
        de) printf 'Öffne ein neues Terminal (oder lade deine Shell-rc), dann:' ;;
        es) printf 'Abre una nueva terminal (o haz '\''source'\'' de tu rc), luego:' ;;
        fr) printf 'Ouvre un nouveau terminal (ou '\''source'\'' ton rc), puis :' ;;
        it) printf 'Apri un nuovo terminale (o fai '\''source'\'' della tua rc), poi:' ;;
        *)  printf 'Open a new terminal (or '\''source'\'' your shell rc), then:' ;;
      esac ;;
    uninstall_hint) # %s = chain home
      case "$L" in
        de) printf 'Spätere Deinstallation:  chain uninstall   (--purge entfernt auch %s)' "$2" ;;
        es) printf 'Desinstalar más tarde:   chain uninstall   (--purge elimina también %s)' "$2" ;;
        fr) printf 'Désinstaller plus tard:  chain uninstall   (--purge supprime aussi %s)' "$2" ;;
        it) printf 'Disinstallare in seguito: chain uninstall   (--purge rimuove anche %s)' "$2" ;;
        *)  printf 'To uninstall later:  chain uninstall   (add --purge to also remove %s)' "$2" ;;
      esac ;;
  esac
}

# -- prerequisites -------------------------------------------------

need() {
  command -v "$1" >/dev/null 2>&1 || {
    echo "fatal: '$1' is required but not on PATH" >&2
    exit 1
  }
}
need curl
need uname
# sha256 tool: prefer sha256sum (Linux), fall back to shasum (macOS)
if command -v sha256sum >/dev/null 2>&1; then
  SHA_CMD="sha256sum"
elif command -v shasum >/dev/null 2>&1; then
  SHA_CMD="shasum -a 256"
else
  echo "fatal: neither sha256sum nor shasum is available" >&2
  exit 1
fi
# openssl is used to verify the binary's ECDSA P-256 signature
# against the published Flemming.AI signing key. Optional — when
# missing we skip the signature check but still enforce sha256.
# Operators who want enforced verification can set
# CHAIN_REQUIRE_SIGNATURE=1.
HAVE_OPENSSL=0
if command -v openssl >/dev/null 2>&1; then HAVE_OPENSSL=1; fi
REQUIRE_SIG="${CHAIN_REQUIRE_SIGNATURE:-0}"
# Embedded Flemming.AI release-signing pubkey (ECDSA P-256).
# Kept inline rather than fetched at runtime so a MITM with a
# valid wildcard cert for *.flemming.ai cannot substitute the
# key together with a malicious binary. Mirrors the same
# pattern as the Rust hub's `include_str!`. Rotation procedure:
# update `infra/cosign/official.pub` AND this constant in
# install.sh + install.ps1 in the same commit; users running
# old installers will reject the new release, and the version
# bump is the intended forcing function.
SIG_PUBKEY_PEM='-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8SsjXx7VcvjvEbg4qrTag2GRn4kL
PUCZm85YCe0udF5qqKep1aeaTjmkvm9UutlDW+bUmtVSC54Qme5h3NNkFA==
-----END PUBLIC KEY-----'
# Optional override only for offline test fixtures + key
# rotation grace periods — do NOT rely on this for normal
# operation. When set, the URL takes precedence over the
# embedded key, which defeats the MITM defence by design.
SIG_PUBKEY_URL="${CHAIN_SIG_PUBKEY_URL:-}"

# -- platform detection -------------------------------------------

OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
case "$OS/$ARCH" in
  linux/x86_64)        KEY="linux-x86_64"   ; EXT=""    ;;
  linux/aarch64|linux/arm64)
                       KEY="linux-aarch64"  ; EXT=""    ;;
  darwin/arm64)        KEY="macos-aarch64"  ; EXT=""    ;;
  darwin/x86_64)       KEY="macos-x86_64"   ; EXT=""    ;;
  *)
    echo "fatal: unsupported platform $OS/$ARCH" >&2
    echo "Supported: linux-x86_64, linux-aarch64, macos-aarch64, macos-x86_64." >&2
    echo "For Windows use installer/install.ps1." >&2
    exit 1
    ;;
esac

# -- branding -----------------------------------------------------
# The Ch∆In mark from flemming.ai: two interlocking chain links (the
# site's "link" glyph that sits before the Ch∆In wordmark), drawn with
# rounded box-drawing in signal-teal (#2e8f9e), with the Ch∆In wordmark
# (∆ in teal) + © line to its right. Honours NO_COLOR + CHAIN_NO_ART;
# degrades truecolor → 256 → 16 → plain.
chain_banner() {
  [ "${CHAIN_NO_ART:-0}" = 1 ] && return 0
  _c=0
  if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
    case "${COLORTERM:-}" in
      *truecolor*|*24bit*) _c=24 ;;
      *) case "${TERM:-}" in *-256color|*256col*) _c=8 ;; dumb|"") _c=0 ;; *) _c=4 ;; esac ;;
    esac
  fi
  # brand colours: TEAL = signal #2e8f9e (links + ∆), PAPER = #f4f3ef
  case "$_c" in
    24) TEAL='\033[38;2;46;143;158m'; PAPER='\033[1;38;2;244;243;239m'; DIM='\033[2m'; R='\033[0m' ;;
    8)  TEAL='\033[38;5;37m';         PAPER='\033[1;38;5;255m';         DIM='\033[2;38;5;245m'; R='\033[0m' ;;
    4)  TEAL='\033[36m';              PAPER='\033[1;97m';               DIM='\033[2m'; R='\033[0m' ;;
    *)  TEAL=''; PAPER=''; DIM=''; R='' ;;
  esac
  WM="${PAPER}Ch${TEAL}∆${PAPER}In${R}"
  printf '\n'
  printf "   ${TEAL}%s${R}\n"          "╭──╮"
  printf "   ${TEAL}%s${R}     %b\n"   "│ ╭┼─╮" "$WM"
  printf "   ${TEAL}%s${R}     ${DIM}© 2026 Flemming.AI${R}\n" "╰─┼╯ │"
  printf "   ${TEAL}%s${R}     ${DIM}%s${R}\n\n" "  ╰──╯" "$(msg tagline)"
}

chain_banner
echo "Ch∆In install — platform: $KEY, channel: $CHANNEL"
echo "  manifest: $MANIFEST_URL"

# -- explain, then ask before doing anything ----------------------
# Running `curl … | sh` should not silently mutate the machine. Lay
# out exactly what will happen, then wait for a yes. CHAIN_YES=1 /
# --yes skips it; a fully non-interactive run with no terminal prints
# the plan and proceeds (invoking the one-liner is the consent).
TARGET_BIN="$INSTALL_DIR/chain$EXT"
CHAIN_HOME="${CHAIN_HOME:-$HOME/.chain}"

echo
echo "$(msg plan_head)"
echo "$(msg plan_step1 "$KEY")"
echo "$(msg plan_step2 "$TARGET_BIN")"
[ "$DO_BOOTSTRAP" = 1 ] && \
echo "$(msg plan_step3 "$CHAIN_HOME")"
echo
echo "$(msg remove_intro)  chain uninstall          ($(msg remove_keep))"
echo "                          chain uninstall --purge  ($(msg remove_purge "$CHAIN_HOME"))"
echo

if [ "$ASSUME_YES" = 1 ] || [ "$FORCE" = 1 ]; then
  : # caller opted in up front
elif ( exec 3<>/dev/tty ) 2>/dev/null; then
  # Probe /dev/tty in a subshell first: a failed exec-redirect in the
  # current shell ABORTS dash (POSIX: non-interactive shell exits on
  # exec redirection error), so the old brace-group probe silently
  # killed every no-TTY install on Debian/Ubuntu. The subshell absorbs
  # the failure; the current-shell exec below only runs on success.
  exec 3<>/dev/tty
  printf '%s' "$(msg proceed)" >&3
  read _go <&3 || _go=""
  exec 3>&- 3<&-
  case "$_go" in
    n|N|no|NO) echo "$(msg aborted)"; exit 0 ;;
  esac
else
  echo "$(msg noninteractive)"
fi

# -- detect prior installs from other sources ---------------------
#
# We install the entry-point binary to $INSTALL_DIR/chain. A
# `chain` from a *different* source (Homebrew, cargo, a dev build,
# an earlier custom --install-dir) lands elsewhere and will shadow
# or be shadowed by this one depending on PATH order — and later
# uninstalling one source silently leaves the other behind. So we
# look for every `chain` on PATH plus the well-known package-manager
# locations, and make the operator confirm before adding a copy.
TARGET_BIN="$INSTALL_DIR/chain$EXT"
CHAIN_HOME="${CHAIN_HOME:-$HOME/.chain}"

scan_existing_chain() {
  # Every `chain` reachable on PATH ...
  _ifs_save=$IFS
  IFS=:
  for d in $PATH; do
    [ -n "$d" ] || d="."
    [ -x "$d/chain$EXT" ] && printf '%s\n' "$d/chain$EXT"
  done
  IFS=$_ifs_save
  # ... plus well-known dirs that may be off a piped shell's PATH. The
  # FIRST of these is chain's own shim under ~/.chain/bin — where every
  # prior install lands, including a from-source / `chain dev` setup that
  # a `curl | sh` won't otherwise see (its rc isn't sourced).
  for d in "$CHAIN_HOME/bin" "$HOME/.local/bin" /opt/homebrew/bin /usr/local/bin "$HOME/.cargo/bin"; do
    [ -x "$d/chain$EXT" ] && printf '%s\n' "$d/chain$EXT"
  done
}

chain_version_of() {
  _v=$("$1" --version 2>/dev/null | head -n1) || _v=""
  [ -n "$_v" ] && printf '%s' "$_v" || printf 'version unknown'
}

# An existing operator home (`~/.chain`, left by a prior bootstrap) OR
# any `chain` binary on disk means this machine already has Ch∆In —
# possibly a from-source dev setup. Re-running the installer overwrites
# the entry binary and re-bootstraps, which would clobber that. Warn and
# make the operator confirm; `chain update apply` is the in-place upgrade.
EXISTING=$(scan_existing_chain | sort -u)
if [ -d "$CHAIN_HOME" ] || [ -n "$EXISTING" ]; then
  echo >&2
  echo "warning: Ch∆In already appears to be installed on this machine." >&2
  [ -d "$CHAIN_HOME" ] && \
    echo "  • operator home:  $CHAIN_HOME  (config, data, audit log, dev setup)" >&2
  for p in $EXISTING; do
    echo "  • binary:         $p  ($(chain_version_of "$p"))" >&2
  done
  echo >&2
  echo "  Re-running this installer overwrites the entry binary and re-runs" >&2
  echo "  bootstrap — that can replace a from-source / 'chain dev' install." >&2
  echo "  To upgrade an existing install in place instead, use:" >&2
  echo "      chain update apply" >&2
  echo >&2
  if [ "$FORCE" = 1 ]; then
    echo "  (CHAIN_FORCE=1 / --force set — reinstalling anyway)" >&2
  elif ( exec 3<>/dev/tty ) 2>/dev/null; then
    # A controlling terminal is reachable even under `curl | sh`
    # (stdin is the script, so we read the answer from /dev/tty).
    # Probe in a subshell first — see the proceed prompt above for
    # why a current-shell exec failure would abort dash outright.
    exec 3<>/dev/tty
    printf 'Reinstall anyway? [y/N] ' >&3
    read _ans <&3 || _ans=""
    exec 3>&- 3<&-
    case "$_ans" in
      y|Y|yes|YES) : ;;
      *) echo "Aborted — nothing changed." >&2; exit 6 ;;
    esac
  else
    echo "fatal: already installed and this run is non-interactive." >&2
    echo "       Re-run with CHAIN_FORCE=1 to reinstall, or use 'chain update apply'." >&2
    exit 6
  fi
fi

# -- fetch manifest -----------------------------------------------

TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

MANIFEST_FILE="$TMPDIR/manifest.json"
if ! curl -fsSL "$MANIFEST_URL" -o "$MANIFEST_FILE"; then
  echo "fatal: could not fetch manifest from $MANIFEST_URL" >&2
  exit 2
fi

# Verify the manifest's own signature against the embedded
# pubkey BEFORE reading any binary entries. Closes the
# manifest-host-compromise attack vector where an attacker
# who controls the manifest could swap every binary entry +
# its sha256 + its signature_url at once.
MANIFEST_SIG_URL="${MANIFEST_URL}.sig"
MANIFEST_VERIFIED=0
if [ "$HAVE_OPENSSL" = 1 ]; then
  MANIFEST_SIG_FILE="$TMPDIR/manifest.json.sig"
  MANIFEST_PUBKEY_FILE="$TMPDIR/manifest.pub"
  if curl -fsSL "$MANIFEST_SIG_URL" -o "$MANIFEST_SIG_FILE" 2>/dev/null; then
    if [ -n "$SIG_PUBKEY_URL" ]; then
      curl -fsSL "$SIG_PUBKEY_URL" -o "$MANIFEST_PUBKEY_FILE" \
        || { echo "fatal: override pubkey fetch failed" >&2; exit 3; }
    else
      printf '%s\n' "$SIG_PUBKEY_PEM" > "$MANIFEST_PUBKEY_FILE"
    fi
    MANIFEST_SIG_RAW="$TMPDIR/manifest.json.sig.raw"
    if base64 -d < "$MANIFEST_SIG_FILE" > "$MANIFEST_SIG_RAW" 2>/dev/null \
       && openssl dgst -sha256 \
            -verify "$MANIFEST_PUBKEY_FILE" \
            -signature "$MANIFEST_SIG_RAW" \
            "$MANIFEST_FILE" >/dev/null 2>&1; then
      MANIFEST_VERIFIED=1
      echo "  manifest:  verified"
    else
      echo "fatal: manifest signature is invalid — refusing to read $MANIFEST_URL" >&2
      exit 2
    fi
  else
    echo "warning: manifest signature ($MANIFEST_SIG_URL) not available; binary sha256 + sig will still be enforced where present" >&2
  fi
fi
if [ "$MANIFEST_VERIFIED" = 0 ] && [ "$REQUIRE_SIG" = 1 ]; then
  echo "fatal: CHAIN_REQUIRE_SIGNATURE=1 but manifest was not signature-verified" >&2
  exit 2
fi

# Crude JSON extraction without a hard jq dep — looks for the
# `<key>` block inside `binaries`. If jq is available, prefer it.
if command -v jq >/dev/null 2>&1; then
  URL=$(jq -r ".binaries[\"$KEY\"].url // empty" "$MANIFEST_FILE")
  SHA=$(jq -r ".binaries[\"$KEY\"].sha256 // empty" "$MANIFEST_FILE")
  SIG_URL=$(jq -r ".binaries[\"$KEY\"].signature_url // empty" "$MANIFEST_FILE")
  VERSION=$(jq -r ".version // empty" "$MANIFEST_FILE")
else
  # Fallback: grep + sed. Manifest is small + machine-generated by
  # the release CI so the regex stays predictable. If you hand-edit
  # the manifest, install jq.
  BLOCK=$(grep -A3 "\"$KEY\"" "$MANIFEST_FILE" || true)
  URL=$(printf '%s' "$BLOCK" | sed -n 's/.*"url":[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
  SHA=$(printf '%s' "$BLOCK" | sed -n 's/.*"sha256":[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
  SIG_URL=$(printf '%s' "$BLOCK" | sed -n 's/.*"signature_url":[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
  VERSION=$(sed -n 's/.*"version":[[:space:]]*"\([^"]*\)".*/\1/p' "$MANIFEST_FILE" | head -1)
fi

if [ -z "$URL" ] || [ -z "$SHA" ] || [ -z "$VERSION" ]; then
  echo "fatal: manifest does not advertise a binary for $KEY" >&2
  echo "manifest body:" >&2
  cat "$MANIFEST_FILE" >&2
  exit 2
fi

echo "  version:  $VERSION"
echo "  binary:   $URL"

# -- download + verify --------------------------------------------

BIN_TMP="$TMPDIR/chain$EXT"
if ! curl -fsSL "$URL" -o "$BIN_TMP"; then
  echo "fatal: download failed: $URL" >&2
  exit 3
fi

ACTUAL_SHA=$($SHA_CMD "$BIN_TMP" | awk '{print $1}')
if [ "$(printf '%s' "$ACTUAL_SHA" | tr '[:upper:]' '[:lower:]')" != \
     "$(printf '%s' "$SHA"        | tr '[:upper:]' '[:lower:]')" ]; then
  echo "fatal: sha256 mismatch" >&2
  echo "  expected: $SHA"      >&2
  echo "  got:      $ACTUAL_SHA" >&2
  exit 3
fi
chmod +x "$BIN_TMP"
echo "  sha256:   verified"

# -- signature verification (defence-in-depth) ---------------------
#
# When the manifest advertises a signature_url and openssl is
# available, verify the ECDSA P-256 signature against the
# Flemming.AI publishing key. A failed check aborts the install.
#
# When openssl is missing or signature_url is empty, behaviour
# depends on CHAIN_REQUIRE_SIGNATURE:
#   0 (default) — skip with a warning, continue
#   1           — abort
SIG_VERIFIED=0
if [ -n "$SIG_URL" ]; then
  if [ "$HAVE_OPENSSL" = 1 ]; then
    SIG_FILE="$TMPDIR/chain.sig"
    PUBKEY_FILE="$TMPDIR/official.pub"
    # Pubkey source: embedded constant by default; URL only
    # when CHAIN_SIG_PUBKEY_URL is set (offline-test/rotation
    # grace-period escape hatch, NOT for normal operation).
    if [ -n "$SIG_PUBKEY_URL" ]; then
      curl -fsSL "$SIG_PUBKEY_URL" -o "$PUBKEY_FILE" \
        || { echo "fatal: could not fetch override pubkey from $SIG_PUBKEY_URL" >&2; exit 3; }
      PUBKEY_SOURCE="$SIG_PUBKEY_URL"
    else
      printf '%s\n' "$SIG_PUBKEY_PEM" > "$PUBKEY_FILE"
      PUBKEY_SOURCE="embedded"
    fi
    if curl -fsSL "$SIG_URL" -o "$SIG_FILE"; then
      SIG_RAW="$TMPDIR/chain.sig.raw"
      if base64 -d < "$SIG_FILE" > "$SIG_RAW" 2>/dev/null; then
        if openssl dgst -sha256 \
             -verify "$PUBKEY_FILE" \
             -signature "$SIG_RAW" \
             "$BIN_TMP" >/dev/null 2>&1; then
          SIG_VERIFIED=1
          echo "  signature: verified (key: $PUBKEY_SOURCE)"
        else
          echo "fatal: signature verification failed for $URL" >&2
          echo "  signed-by-key: $PUBKEY_SOURCE" >&2
          exit 3
        fi
      else
        echo "fatal: signature sidecar is not valid base64: $SIG_URL" >&2
        exit 3
      fi
    else
      echo "warning: signature_url present but download failed; sha256 alone enforced" >&2
    fi
  else
    echo "warning: openssl missing; cannot verify signature (sha256 still enforced)" >&2
  fi
fi
if [ "$SIG_VERIFIED" = 0 ] && [ "$REQUIRE_SIG" = 1 ]; then
  echo "fatal: CHAIN_REQUIRE_SIGNATURE=1 but no signature was verified" >&2
  exit 3
fi

# -- install entry-point binary -----------------------------------

mkdir -p "$INSTALL_DIR"
INSTALLED_BIN="$INSTALL_DIR/chain$EXT"
mv -f "$BIN_TMP" "$INSTALLED_BIN"
echo "installed: $INSTALLED_BIN"

# -- self-bootstrap ------------------------------------------------

if [ "$DO_BOOTSTRAP" = 1 ]; then
  echo "running bootstrap ..."
  if ! "$INSTALLED_BIN" bootstrap --channel "$CHANNEL"; then
    echo "warning: bootstrap reported a non-zero status" >&2
    echo "        the entry-point binary is installed at $INSTALLED_BIN" >&2
    echo "        run 'chain bootstrap' manually to finish setup" >&2
    exit 4
  fi
fi

echo
echo "Ch∆In $VERSION installed on channel '$CHANNEL'."

# -- first-run guided setup (interactive) --------------------------
# Offer the wizard right after install: it asks what you want to do
# and which tools (incl. Studio) to install, then configures it all.
# Skipped when non-interactive or CHAIN_NO_WIZARD=1.
RAN_WIZARD=0
if [ "$DO_BOOTSTRAP" = 1 ] && [ "${CHAIN_NO_WIZARD:-0}" != 1 ]; then
  # Subshell probe — a current-shell exec failure would abort dash
  # AFTER a fully successful install (exit 2 with no error message).
  if ( exec 3<>/dev/tty ) 2>/dev/null; then
    exec 3<>/dev/tty
    printf '%s' "$(msg wizard_offer)" >&3
    read _w <&3 || _w=""
    exec 3>&- 3<&-
    case "$_w" in
      n|N|no|NO) : ;;
      # Connect the wizard's stdin to the terminal — when invoked via
      # `curl | sh`, the script's own stdin is the pipe (already at EOF),
      # which would make every prompt read nothing. /dev/tty is the
      # operator's keyboard.
      *) "$INSTALLED_BIN" init --interactive </dev/tty && RAN_WIZARD=1 ;;
    esac
  fi
fi

if [ "$RAN_WIZARD" = 0 ]; then
  echo "$(msg nextsteps_head)"
  echo "  chain init          # guided setup (pick tasks + tools)"
  echo "  chain daemon start"
  echo "  chain admin doctor"
fi
echo
echo "$(msg uninstall_hint "$CHAIN_HOME")"
