#!/bin/sh
# AC installer — served at https://get.500.sh
# Usage: curl -fsSL https://get.500.sh | sh -s -- join [--cell <cell>]
#   `ah join` then shows a code; approve it in AC on your phone (docs/463 §4q).
#   --cell names your account's home cell, as the phone's command does (docs/503 P0-7).
#
# ─────────────────────────────────────────────────────────────────────────────
# docs/236 D8 — THIS FILE IS THE SOURCE. The web server is a copy.
#
# It was untracked for its whole life: `git grep` found zero copies, while the
# product told every user (`mobile/lib/chat_target.dart` joinCommandPrefix,
# `enroll.rs` module header) to `curl | sh` it. That is one thing on a laptop an
# operator runs by hand, and another entirely under docs/236, where the control
# plane instructs N third-party vendors to execute these bytes on boxes it
# provisions. Bytes no commit produces cannot be reviewed, bisected, or attested.
#
# Deploy: copy to vm1:/var/www/get.500.sh/html/index (mode 0644). Verify with
#   curl -fsSL https://get.500.sh | diff - dist/get.500.sh
# A difference means the web server drifted from this file; this file wins.
#
# THE ONE MAINTENANCE RULE: AH_CLOUD_BOOTSTRAP below is a MINTED value, not a
# constant. `ah boot-ticket` mints a fresh one and the previous one stops being
# admitted — measured 2026-08-25, when the baked ticket and a fresh one diverged
# at byte 12 and EVERY claim was refused with the single sentence I-CODE-ONCE
# gives unknown/expired/rate-limited alike. Nothing detects that: the fleet just
# stops being able to add computers. On any relay or bootstrap rotation, re-bake:
#   ssh vm1 'ah boot-ticket' | tail -1     # then replace the value below, land, deploy
# docs/222 §7 names this risk for baked tickets; this comment is the standing answer.
# Only a join with no --cell reads the baked ticket; --cell reads the cell's published one.
# ─────────────────────────────────────────────────────────────────────────────
set -e

# docs/503 P0-7: a computer's join begins at its account's home cell. The names are the release's
# cell records (dist/ac-cloud/cells/); any other is refused before anything runs.
CELL=""
if [ "${1:-}" = "join" ] && [ "${2:-}" = "--cell" ]; then
  CELL="${3:-}"
  case "$CELL" in
    asia-south|asia-east|eu-central|eu-north|us-east|us-west) ;;
    *)
      echo "AC install: \"$CELL\" is not a cell this release names (asia-south, asia-east, eu-central, eu-north, us-east, us-west)." >&2
      echo "Nothing was installed." >&2
      exit 1
      ;;
  esac
  if [ -n "${AH_CLOUD_BOOTSTRAP:-}" ]; then
    echo "AC install: --cell $CELL and the exported AH_CLOUD_BOOTSTRAP each name a plane; unset one." >&2
    echo "Nothing was installed." >&2
    exit 1
  fi
  shift 3
  set -- join "$@"
fi

OS="$(uname -s)"
ARCH="$(uname -m)"
# docs/236 L9 owns widening this. This installer only serves linux-x86_64 from
# https://get.500.sh/dist/linux-x86_64. Mac civilians use the catalog DMG
# (docs/244 / docs/268), not this script. There is no GitHub Actions
# release.yml (docs/244 ACTIONS-ABSENT). aarch64-unknown-linux-gnu is not in
# the 244 catalog fence. Neither gap is a code-path flag — both are this `case`.
if [ "$OS" != "Linux" ] || [ "$ARCH" != "x86_64" ]; then
  echo "AC install: only linux-x86_64 is available from this server today (detected $OS/$ARCH)." >&2
  echo "Nothing was installed." >&2
  exit 1
fi

BASE="https://get.500.sh/dist/linux-x86_64"
DEST="${AH_BIN_DIR:-/usr/local/bin}"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT

# The cell's current boot ticket: its pair token, WireGuard keys and bootstrap issuer are minted per
# plane and rotate, so the release publishes each cell's own on every serving box.
if [ -n "$CELL" ]; then
  BOOT_URL="https://get.500.sh/cells/$CELL.boot"
  if ! curl -fsSL --max-filesize 4096 "$BOOT_URL" -o "$TMP/boot"; then
    echo "AC install: could not fetch $CELL's join ticket from $BOOT_URL." >&2
    echo "Nothing was installed." >&2
    exit 1
  fi
  AH_CLOUD_BOOTSTRAP="$(cat "$TMP/boot")"
  case "$AH_CLOUD_BOOTSTRAP" in
    ac-boot-v1-*) TICKET_BODY="${AH_CLOUD_BOOTSTRAP#ac-boot-v1-}" ;;
    *) TICKET_BODY="" ;;
  esac
  case "$TICKET_BODY" in
    "" | *[!A-Za-z0-9_-]*)
      echo "AC install: $BOOT_URL did not answer one ac-boot-v1- ticket." >&2
      echo "Nothing was installed." >&2
      exit 1
      ;;
  esac
fi

echo "AC install: downloading ah/ac for linux-x86_64..." >&2
curl -fsSL "$BASE/ah" -o "$TMP/ah"
curl -fsSL "$BASE/ac" -o "$TMP/ac"
chmod +x "$TMP/ah" "$TMP/ac"

SUDO=""
if [ "$(id -u)" != "0" ]; then
  if [ ! -w "$DEST" ]; then
    SUDO="sudo"
  fi
fi

$SUDO mv "$TMP/ah" "$DEST/ah"
$SUDO mv "$TMP/ac" "$DEST/ac"
echo "AC install: installed $DEST/ah and $DEST/ac" >&2

# This cloud's own boot relay — config data, not a user secret. Overridable by an
# already-exported AH_CLOUD_BOOTSTRAP so an operator testing a different relay is not
# silently overridden. Re-bake on rotation — see THE ONE MAINTENANCE RULE above.
: "${AH_CLOUD_BOOTSTRAP:=ac-boot-v1-QUNKMQF3hQMhxJwTwEHzYikxQSxb1n98vYaE6gA8y_sVoXIaESGLcMt0Yl8k2vbw3hLuV0uNMYlpvapGRn7OYTcfRE9ISmUjgdCR9SpoHYGKtrRZBPqhJa8UJ8cREw0CKY-rDWYAQwB3c3M6Ly9yZWxheS41MDAuc2g6NDQzLyNHQ1pUQXBDRk9NblFPb3FJRlNOOWlQNG45QUhEWldYeU9jdnVjRFk2ZmdV4ANubAAAAAAgADllMzA3YWUxYzNmNTkwZmQ1NDljMjlhYTBkNDUyZGVlQwB3c3M6Ly9yZWxheS41MDAuc2g6NDQzLyNHQ1pUQXBDRk9NblFPb3FJRlNOOWlQNG45QUhEWldYeU9jdnVjRFk2ZmdVjg7TWjx7XhRUR0NJ7sFC2Tb30WVcXVVGAV4ZX0cnvqD3PYlQzI0ESfRiAE59PkU1esgx8BGVEmDd3olYpV2qDg}"
export AH_CLOUD_BOOTSTRAP

# docs/236 §13 — WITHOUT THIS, EVERY JOIN ON A CLEAN MACHINE DIES.
# `ah join` keeps durable enrolment state (the install secret, the pending claim, the
# minted overlay identity) under AH_HOME, and ac REQUIRES the variable everywhere rather
# than defaulting it (`api.rs` ah_home_from -> HomeRequired; `enroll_client.rs` -> Home).
# That fail-closed rule is right for a server the operator configures and wrong for the
# one command we ask a stranger to paste, which arrives with no environment at all: the
# installer landed both binaries and then exited 1 with "AH_HOME is required for durable
# enrolment state". Same idiom as the bootstrap above, so an operator who exports their
# own AH_HOME is not overridden.
: "${AH_HOME:=/var/lib/ac}"
mkdir -p "$AH_HOME" || { echo "AC install: cannot create $AH_HOME" >&2; exit 1; }
export AH_HOME

# docs/236 §13 — the SECOND env the pasted one-liner arrives without. cloud-init and
# `sh -c` run with no HOME, and `ah join` supervises the worker it starts, which needs
# one ("HOME required for supervision"). Measured on a real box: the claim SUCCEEDED and
# registration completed, then supervision refused — so the device existed in the fleet
# and could never serve a turn, which is the exact shape docs/222 §7 warns about.
if [ -z "${HOME:-}" ]; then
  HOME="$(getent passwd "$(id -u)" 2>/dev/null | cut -d: -f6)"
  [ -n "$HOME" ] || HOME="$AH_HOME"
  export HOME
fi

if [ "$#" -eq 0 ]; then
  echo "AC install: nothing to join. Run: $DEST/ah join" >&2
  exit 0
fi

exec "$DEST/ah" "$@"
