#!/bin/sh
# yah.dev/install.sh — one-line installer for yah's prebuilt binaries.
#
#   curl -fsSL yah.dev/install.sh | sh                     # installs yah
#   curl -fsSL yah.dev/install.sh | sh -s -- yah           # same, explicit
#   curl -fsSL yah.dev/install.sh | sh -s -- mesofact      # a different package
#   curl -fsSL yah.dev/install.sh | sh -s -- --dry-run     # resolve only
#   curl -fsSL yah.dev/install.sh | sh -s -- mesofact --store ~/.mesofact
#                                     # into a multi-version store, see STORE
#
# R560-F1. The landing page has promised this URL since FinalCTA.tsx shipped;
# nothing was ever served at it. This is that file.
#
# WHAT IT INSTALLS BY DEFAULT (R638-F1). `yah` — the CLI the landing page's
# one-liner promises — plus the three sibling binaries that ship in the same
# tarball: `yahh`, `yahb`, `yaha`. The no-argument invocation installs `yah`
# because that is the product the `curl … | sh` block on yah.dev shows; every
# other vended package is one positional argument away.
#
# Pass `mesofact` for the mesofact runtime instead: `mesofact` (prod — serve|
# proxy|publish) and `mes` (the dev-tier watcher + local S3 surface). Those are
# two separate binaries from two separate crates on purpose: the dev
# affordances must never sit in the prod binary's dependency closure (W225 §2).
# They ship in one tarball and land as two files. Slots installed before
# MFT-R822 carry the dev binary under its old name `mesofact-dev`; the shim
# below resolves either.
#
# HOW IT RESOLVES. Everything hangs off ONE mutable pointer per package —
#
#     https://cdn.yah.dev/<pkg>/latest.json
#
# — which maps a target triple to
#   { url, hash, bootstrap_hash, sha256, size_bytes, bundle_url }.
# The bytes it points at live under an immutable `<pkg>/<version>/<triple>/`
# prefix, so a release is a pointer flip and old versions stay downloadable
# forever. That is the same layout the desktop publish (R587-T1/T2) uses; this
# script is deliberately generic over `<pkg>` rather than hard-coding one
# product's paths, so the next thing we vend needs no second installer.
#
# TRUST MODEL, stated plainly rather than implied:
#   * The manifest is fetched over TLS from cdn.yah.dev.
#   * The tarball's sha256 is checked against that manifest, ALWAYS. A mismatch
#     is fatal.
#   * WHY SHA-256 HERE AND BLAKE3 EVERYWHERE ELSE (R330-F40). BLAKE3 is THE yah
#     asset hash — one algorithm across the ecosystem, and `hash` in the
#     manifest carries it as `blake3:<hex>`. This script cannot make it the
#     mandatory check, because a bare POSIX box has three ways to compute
#     sha256 (sha256sum / shasum / openssl) and zero to compute blake3 —
#     verifying blake3 would mean first downloading an unverified `b3sum` to
#     verify with, which is circular. So `bootstrap_hash` (`sha256:<hex>`) is a
#     BOOTSTRAP-VERIFICATION AID scoped to this one path, never an identity.
#     Both are tagged with their algorithm so nothing here ever has to guess
#     which digest it is holding; if `b3sum` happens to be installed, the
#     canonical blake3 is checked too, as a bonus rather than a requirement.
#   * The sigstore (cosign) signature is ALSO checked when `cosign` is on PATH.
#     It is skipped with a loud notice when it is not, because requiring cosign
#     on a fresh box would defeat the point of a one-line installer. Set
#     YAH_INSTALL_REQUIRE_SIG=1 to make a missing cosign fatal instead — do that
#     in CI and anywhere the extra hop is affordable. Verification is against a
#     cosign 3.x sigstore bundle (one file carrying signature + certificate +
#     transparency proof — `bundle_url` in the manifest), not the old separate
#     `.sig`/`.cert` pair.
#
# MULTI-VERSION STORE (R759-F1). `--store DIR` (or YAH_INSTALL_STORE) installs
# into `DIR/versions/<version>/` instead of a flat prefix, so N versions of a
# package coexist and nothing on the box is ever overwritten by an install.
# Nothing goes on PATH in this mode — a shim in front of the store (R759-F2)
# does per-invocation selection. The marker file `DIR/versions/<version>/
# .installed` (key=value lines: package, version, triple, bins, installed_at)
# is written LAST, so a directory without it is a partial install: this script
# removes and redoes such a directory, and a directory WITH it is skipped
# before any download. The layout is mirrored by crates/yah/mesofact-store,
# which reads the same marker — change one, change both.
#
# Env overrides:
#   YAH_INSTALL_PACKAGE   package to install         (default: yah)
#   YAH_INSTALL_VERSION   pin a version              (default: whatever latest.json says)
#   YAH_INSTALL_DIR       install prefix             (default: /usr/local/bin, then ~/.local/bin)
#   YAH_INSTALL_STORE     multi-version store root   (mutually exclusive with YAH_INSTALL_DIR)
#   YAH_INSTALL_SHIM_DIR  with --store: also place the mesofact/mes/mesofact-dev
#                         shims here (R759-F2; `--shim DIR`). The first version
#                         installed into a store becomes its default.
#   YAH_INSTALL_CDN       CDN base                   (default: https://cdn.yah.dev)
#   YAH_INSTALL_REQUIRE_SIG=1  fail when cosign is unavailable
#   YAH_INSTALL_COSIGN_IDENTITY  trust spec; `key:<ref>` for key-based cosign,
#                              otherwise a keyless cert-identity regexp
#
# POSIX sh on purpose — this runs on whatever /bin/sh a fresh box has.

set -eu

CDN="${YAH_INSTALL_CDN:-https://cdn.yah.dev}"
PACKAGE="${YAH_INSTALL_PACKAGE:-yah}"
# Trust spec for the release signature. A leading `key:` selects key-based
# cosign (`--key <ref>`, no Fulcio certificate); anything else is a keyless
# certificate-identity regexp checked against COSIGN_ISSUER. Same convention as
# ReleaseTrust::parse on the Rust side — a release cut off GitHub CANNOT use
# Fulcio keyless, because the public Fulcio only issues certs for OIDC issuers
# on its own allowlist (R605-F1). GitHub Actions is retired as this project's
# release venue, so the default is key-based against the vaulted release key's
# published public half, not a GitHub identity regexp that can never match
# again.
COSIGN_IDENTITY="${YAH_INSTALL_COSIGN_IDENTITY:-key:https://cdn.yah.dev/keys/yah-release.pub}"
COSIGN_ISSUER='https://token.actions.githubusercontent.com'
DRY_RUN=0

while [ $# -gt 0 ]; do
  case "$1" in
    --dry-run) DRY_RUN=1 ;;
    --dir) shift; YAH_INSTALL_DIR="${1:-}" ;;
    --version) shift; YAH_INSTALL_VERSION="${1:-}" ;;
    --store) shift; YAH_INSTALL_STORE="${1:-}" ;;
    --shim) shift; YAH_INSTALL_SHIM_DIR="${1:-}" ;;
    -h|--help)
      # Derive the help text from the header block rather than a hard-coded
      # line range — the range silently truncated the moment the header grew.
      # Prints line 2 onward while the lines are still comments, stopping at
      # the first that isn't.
      awk 'NR > 1 { if (!/^#/) exit; sub(/^# ?/, ""); print }' "$0" 2>/dev/null \
        || echo "usage: install.sh [package] [--dry-run] [--dir DIR] [--version VER] [--store DIR]"
      exit 0 ;;
    -*) echo "install.sh: unknown flag $1" >&2; exit 2 ;;
    *) PACKAGE="$1" ;;
  esac
  shift
done

STORE="${YAH_INSTALL_STORE:-}"
SHIM_DIR="${YAH_INSTALL_SHIM_DIR:-}"
if [ -n "$STORE" ] && [ -n "${YAH_INSTALL_DIR:-}" ]; then
  echo "install.sh: --store and --dir are mutually exclusive" >&2; exit 2
fi
if [ -n "$SHIM_DIR" ] && [ -z "$STORE" ]; then
  echo "install.sh: --shim needs --store (the shim resolves against a store)" >&2; exit 2
fi

# ── the shim ────────────────────────────────────────────────────────────────
# Verbatim copy of app/yah/web/marketing/public/mesofact-shim.sh — the ONE
# trampoline (R759-F2). crates/yah/mesofact-store byte-compares this block to
# that file in a test, so edit the file and re-paste; never edit here.
write_shim() { # <dest-file>
  cat > "$1" <<'MESOFACT_SHIM_EOF'
#!/bin/sh
# mesofact shim — the ONE trampoline both the desktop and install.sh place on
# PATH as `mesofact`, `mes` and `mesofact-dev` (R759-F2). Resolves which
# installed version to run, then execs it with argv untouched.
#
# RESOLUTION ORDER (first hit wins):
#   1. $MESOFACT_VERSION            explicit override, for CI and one-offs
#   2. .mesofact-version            one line, searched from $PWD upward
#   3. <root>/default               one line, set by `mesofact-vm use`
# The value at any step may be a VERSION or an ALIAS; an alias is a one-line
# file <root>/aliases/<name> naming a version. <root> is $MESOFACT_HOME, else
# ~/.mesofact. Which binary to exec comes from the name this shim was invoked
# as: `mesofact` runs the prod binary, `mes` and `mesofact-dev` the dev one —
# same resolution, so the pair can never drift (W225 §2 boundary).
#
# The dev binary's FILENAME INSIDE A SLOT is resolved, not assumed: `mes` since
# MFT-R822, `mesofact-dev` in every slot installed before it. Nothing rewrites
# an installed slot after the fact — it is a directory of files a past
# install.sh untarred — so a release that renames a binary cannot rename the
# copies already on disk, and asking for one spelling only would make every
# older version unrunnable. Both invocation names resolve through the same
# two-step probe, so they can never disagree. When no supported slot predates
# the rename, the `mesofact-dev` fallback goes; it is a support-window
# decision, and crates/yah/mesofact-store's LEGACY_BIN_NAMES retires with it.
#
# `mft` was a third name for the PROD binary. It is retired: it collided with
# an unrelated NTFS-forensics tool of that name on PATH, and running `mft` for
# prod beside `mes` for dev put two three-letter names on opposite sides of the
# boundary the long names exist to make visible. Shims already on disk under
# that name still carry an older copy of this script and keep working; the
# installer sweeps them.
#
# A slot missing its .installed marker or EITHER binary is an error, never a
# half-resolution: if the pin names a version only one binary has, both
# invocations fail the same way.
#
# This file is embedded verbatim in install.sh and in crates/yah/mesofact-store
# (which byte-compares the two in a test) — edit here, never in the copies.
# POSIX sh: it runs in front of every invocation including the dev loop, so it
# does no subprocess work beyond `basename`-free parameter expansion.

set -u

name="${0##*/}"
case "$name" in
  mes|mesofact-dev) bin=mes ;;
  mesofact) bin=mesofact ;;
  *) echo "mesofact-shim: invoked as unexpected name '$name'" >&2; exit 70 ;;
esac

root="${MESOFACT_HOME:-${HOME:-/nonexistent}/.mesofact}"

# Read a one-line file: first line, surrounding whitespace stripped.
read_one() { # <file>  → stdout; empty when missing
  [ -f "$1" ] || return 0
  IFS= read -r line < "$1" || true
  line="${line#"${line%%[![:space:]]*}"}"
  line="${line%"${line##*[![:space:]]}"}"
  printf '%s' "$line"
}

want=""
source=""
if [ -n "${MESOFACT_VERSION:-}" ]; then
  want="$MESOFACT_VERSION"; source='$MESOFACT_VERSION'
else
  dir="$PWD"
  while :; do
    if [ -f "$dir/.mesofact-version" ]; then
      want="$(read_one "$dir/.mesofact-version")"; source="$dir/.mesofact-version"
      break
    fi
    [ "$dir" != "/" ] && [ -n "$dir" ] || break
    dir="${dir%/*}"; [ -n "$dir" ] || dir="/"
  done
  if [ -z "$want" ] && [ -f "$root/default" ]; then
    want="$(read_one "$root/default")"; source="$root/default"
  fi
fi

if [ -z "$want" ]; then
  echo "$bin: no version selected — no .mesofact-version above $PWD, no $root/default" >&2
  echo "  pick one: mesofact-vm use <version>   (or set MESOFACT_VERSION)" >&2
  exit 69
fi

# One alias hop, then a version. No chains: an alias naming an alias is an
# error, so resolution stays O(1) and a loop can't hang the dev loop.
version="$want"
if [ -f "$root/aliases/$want" ]; then
  version="$(read_one "$root/aliases/$want")"
  [ -n "$version" ] || { echo "$bin: alias '$want' ($root/aliases/$want) is empty" >&2; exit 78; }
  if [ -f "$root/aliases/$version" ]; then
    echo "$bin: alias '$want' points at another alias '$version' — aliases must name a version" >&2
    exit 78
  fi
fi

slot="$root/versions/$version"

# The dev binary under whichever name THIS slot carries (see the header). The
# completeness check below uses the resolved path, so a pre-rename slot reads
# as complete rather than as "partially installed".
dev="$slot/mes"
[ -x "$dev" ] || dev="$slot/mesofact-dev"
if [ "$bin" = mes ]; then exe="$dev"; else exe="$slot/$bin"; fi

if [ ! -f "$slot/.installed" ] || [ ! -x "$slot/mesofact" ] || [ ! -x "$dev" ]; then
  if [ -d "$slot" ]; then
    echo "$bin: mesofact $version is only partially installed at $slot (selected by $source)" >&2
  else
    echo "$bin: mesofact $version is not installed (selected by $source)" >&2
  fi
  echo "  mesofact-vm install $version" >&2
  exit 69
fi

exec "$exe" "$@"
MESOFACT_SHIM_EOF
}

place_shims() { # writes the three names into $SHIM_DIR; no-op when unset
  [ -n "$SHIM_DIR" ] || return 0
  # One script, three names. Copies rather than symlinks so a relocated shim
  # dir keeps working and `$0` carries the invoked name on every sh.
  mkdir -p "$SHIM_DIR" 2>/dev/null || die "cannot create $SHIM_DIR"
  [ -w "$SHIM_DIR" ] || die "$SHIM_DIR is not writable"
  for s in mesofact mes mesofact-dev; do
    write_shim "$SHIM_DIR/$s.tmp" && chmod 0755 "$SHIM_DIR/$s.tmp" \
      && mv -f "$SHIM_DIR/$s.tmp" "$SHIM_DIR/$s" || die "could not write shim $SHIM_DIR/$s"
    say "shim $SHIM_DIR/$s"
  done
  # Retire `mft` (superseded by `mes`). Only ever removes a file that is one of
  # OUR shims — `mft` is also a real NTFS-forensics tool, and deleting a user's
  # copy of it because it shares a name would be inexcusable.
  if [ -f "$SHIM_DIR/mft" ] && grep -q '^# mesofact shim' "$SHIM_DIR/mft" 2>/dev/null; then
    rm -f "$SHIM_DIR/mft" && say "retired $SHIM_DIR/mft (use mes)"
  fi
  case ":$PATH:" in
    *":$SHIM_DIR:"*) ;;
    *) say "NOTE: $SHIM_DIR is not on your PATH. Add it:"; say "      export PATH=\"$SHIM_DIR:\$PATH\"" ;;
  esac
}

say()  { printf '  %s\n' "$*"; }
step() { printf '\033[1m==>\033[0m %s\n' "$*"; }
die()  { printf '\033[31merror:\033[0m %s\n' "$*" >&2; exit 1; }

need() {
  command -v "$1" >/dev/null 2>&1 || die "$1 is required but not installed"
}

# ── target triple ───────────────────────────────────────────────────────────
# Report the host's real triple and let the manifest decide what exists. Which
# triples are vended is per-package and changes per release — `yah` ships musl
# and darwin; `mesofact` ships gnu and darwin from GitHub Actions and its two
# musl legs from the build-worker fleet (.yah/qed/mesofact-musl.toml, R560-T8/T9),
# because it statically links V8 and a musl V8 cannot be cross-built from a
# glibc runner — so a triple table baked in here goes stale silently. An
# unvended host gets "publishes no artifact for <triple>" from the resolve step
# below, which names its own triple and is always true.
#
# NOTHING HERE NEEDS TO CHANGE WHEN A LEG STARTS SHIPPING, and that is the
# design: this script asks the host what it runs and asks the manifest what
# exists. The alpine path became real the moment the musl entries appeared in
# `mesofact/latest.json`, with no edit to the detection below.
#
# ONE ASYMMETRIC EXCEPTION lives at the resolve step, not here (R330-B49): a
# host that reports `-unknown-linux-gnu` and finds no gnu artifact retries once
# against `-unknown-linux-musl`. Detection stays honest about what the host
# actually runs — that honesty is what keeps the alpine invariant below
# readable — and the substitution happens where the manifest is consulted,
# which is the only place that knows a leg is missing.
#
# The direction is the whole safety argument, so read it before touching it: a
# static musl binary runs on glibc, a glibc binary does NOT run on musl. So
# gnu→musl degrades to "a slightly different libc, still works" while
# musl→gnu would produce an exec failure that reads as a broken install. Only
# one of those two directions exists here, and it is deliberately the safe one.
detect_triple() {
  os="$(uname -s)"
  arch="$(uname -m)"
  case "$arch" in
    arm64|aarch64) arch=aarch64 ;;
    x86_64|amd64)  arch=x86_64 ;;
    *) die "unsupported CPU architecture: $arch" ;;
  esac
  case "$os" in
    Darwin) echo "${arch}-apple-darwin" ;;
    Linux)
      # Report musl vs gnu by what the host actually RUNS. The invariant this
      # protects is one-directional and worth stating as such: never hand an
      # alpine box a glibc binary, because that produces a "not found" on exec
      # that looks like a broken install rather than a missing leg.
      #
      # This used to read "never by what happens to be published", and that was
      # too strong (R330-B49). It conflated the detection with the resolution:
      # `yah` publishes musl-only by deliberate choice (R330-S26) and its Linux
      # binaries are static, so every Debian/Ubuntu box — the commonest Linux —
      # correctly reported `-gnu` here and then died at the resolve step against
      # a manifest that has never carried a gnu entry. The gnu→musl fallback
      # down there fixes that WITHOUT weakening the invariant above, because it
      # cannot run in the direction the invariant forbids: a musl host never
      # reaches it. Keep this function honest; put substitutions at the resolve.
      if [ -f /etc/alpine-release ] || (ldd --version 2>&1 | head -1 | grep -qi musl); then
        echo "${arch}-unknown-linux-musl"
      else
        echo "${arch}-unknown-linux-gnu"
      fi ;;
    *) die "unsupported OS: $os" ;;
  esac
}

# ── minimal JSON field read ─────────────────────────────────────────────────
# The manifest is machine-generated by `jq -n` (one field per line, two-space
# indent, no nested objects inside a triple entry), so a scoped line scan is
# sufficient and keeps `jq` off the list of things a fresh box must have. Uses
# `jq` when it IS present, because that is exact rather than merely sufficient.
json_get() { # <file> <triple> <field>
  if command -v jq >/dev/null 2>&1; then
    jq -r --arg t "$2" --arg f "$3" '.triples[$t][$f] // empty' "$1"
    return
  fi
  awk -v triple="$2" -v field="$3" '
    !inblk { if (index($0, "\"" triple "\":")) inblk = 1; next }
    /^[ \t]*\}/ { exit }
    index($0, "\"" field "\":") {
      v = $0
      sub(/^[^:]*:[ \t]*/, "", v)
      sub(/,[ \t]*$/, "", v)
      sub(/^"/, "", v); sub(/"$/, "", v)
      print v; exit
    }
  ' "$1"
}

fetch() { # <url> <dest>
  # Pin the protocol for the real CDN so a redirect can't downgrade the
  # transport out from under us. YAH_INSTALL_CDN exists for mirrors and for
  # testing this script against a local server, so don't impose https there.
  case "$1" in
    https://*) proto_args='--proto =https --tlsv1.2' ;;
    *)         proto_args='' ;;
  esac
  if command -v curl >/dev/null 2>&1; then
    # shellcheck disable=SC2086  # deliberate word-split of the flag list
    curl -fsSL $proto_args -o "$2" "$1"
  elif command -v wget >/dev/null 2>&1; then
    wget -q -O "$2" "$1"
  else
    die "neither curl nor wget is available"
  fi
}

sha256_of() { # <file>
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" | awk '{print $1}'
  elif command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$1" | awk '{print $1}'
  elif command -v openssl >/dev/null 2>&1; then
    openssl dgst -sha256 "$1" | awk '{print $NF}'
  else
    die "no sha256 tool found (sha256sum / shasum / openssl) — refusing to install unverified bytes"
  fi
}

# ── tagged hashes ───────────────────────────────────────────────────────────
# Manifest hashes are written `<algo>:<hex>` (R330-F40) so nothing downstream
# infers an algorithm from a field name. One `${h#*:}` splits it — no jq, no
# nested object access, works in every /bin/sh.
hash_algo() { # <tagged>  → blake3 | sha256 | "" when untagged
  case "$1" in *:*) echo "${1%%:*}" ;; *) echo "" ;; esac
}
hash_hex()  { # <tagged>  → the bare digest
  echo "${1#*:}"
}

need uname
need tar
need awk

TRIPLE="$(detect_triple)"
step "yah installer — $PACKAGE for $TRIPLE"

TMP="$(mktemp -d 2>/dev/null || mktemp -d -t yah-install)"
# shellcheck disable=SC2064  # $TMP must expand now, not at trap time
trap "rm -rf '$TMP'" EXIT INT TERM

# ── resolve ─────────────────────────────────────────────────────────────────
if [ -n "${YAH_INSTALL_VERSION:-}" ]; then
  MANIFEST_URL="$CDN/$PACKAGE/$YAH_INSTALL_VERSION/manifest.json"
else
  MANIFEST_URL="$CDN/$PACKAGE/latest.json"
fi
step "resolving $MANIFEST_URL"
fetch "$MANIFEST_URL" "$TMP/manifest.json" \
  || die "could not fetch $MANIFEST_URL (is '$PACKAGE' a vended package?)"

# `version` is top-level, not inside a triple block, so it reads directly —
# it is the first "version": line and json_get is scoped to a triple.
VERSION="$(awk -F'"' '/"version":/ {print $4; exit}' "$TMP/manifest.json")"
URL="$(json_get "$TMP/manifest.json" "$TRIPLE" url)"

# ── gnu → musl fallback (R330-B49) ──────────────────────────────────────────
# Measured 2026-09-03 on clean debian:bookworm-slim: the documented one-liner
# printed "yah 0.8.29 publishes no artifact for aarch64-unknown-linux-gnu" and
# stopped. The installer was healthy — it fetched, resolved and reported
# correctly. `yah/latest.json` simply carries four triples (two darwin, two
# musl) and has never carried a gnu one, because yah's Linux binaries are
# static musl on purpose (R330-S26). So the commonest Linux on earth could not
# run the primary install command, and every part of the machine was behaving
# as designed.
#
# The fix is this retry, NOT two redundant `-gnu` build legs: the musl binaries
# already run on glibc, so gnu artifacts would be more matrix, more bytes, more
# things to sign, for a binary that is byte-for-byte equivalent in what it can
# do. See the direction argument in the detect_triple header — musl-on-glibc is
# safe, glibc-on-musl is not, and only the safe direction is implemented.
#
# REASSIGNING $TRIPLE IS THE POINT, not just $URL. Six later reads key off it —
# bootstrap_hash, sha256, hash, bundle_url, the cosign failure messages, and
# the `triple=` line in a store's .installed marker. A fallback that rewrote
# only $URL would download the musl tarball and then verify it against the
# absent gnu entry's hashes, i.e. against nothing, and write a marker claiming
# a triple that was never installed.
#
# The substitution is announced. A silent one turns into a confusing bug report
# six months out ("it says gnu at the top and musl in the URL") that costs more
# than the one line it saves.
TRIED="$TRIPLE"
if [ -z "$URL" ]; then
  case "$TRIPLE" in
    *-unknown-linux-gnu)
      FALLBACK="${TRIPLE%-gnu}-musl"
      TRIED="$TRIPLE or $FALLBACK"
      URL="$(json_get "$TMP/manifest.json" "$FALLBACK" url)"
      if [ -n "$URL" ]; then
        say "no $TRIPLE artifact — using $FALLBACK (static, runs on glibc)"
        TRIPLE="$FALLBACK"
      fi
      ;;
  esac
fi
[ -n "$URL" ] || die "$PACKAGE $VERSION publishes no artifact for $TRIED"

# Prefer the tagged `bootstrap_hash` and fall back to the deprecated bare
# `sha256`, so this script keeps working against manifests published either
# side of R330-F40. A tag that is present but ISN'T sha256 is fatal rather than
# assumed: comparing a digest against the wrong tool is precisely the failure
# the tagging exists to prevent.
BOOTSTRAP="$(json_get "$TMP/manifest.json" "$TRIPLE" bootstrap_hash)"
if [ -n "$BOOTSTRAP" ]; then
  algo="$(hash_algo "$BOOTSTRAP")"
  [ "$algo" = "sha256" ] \
    || die "manifest bootstrap_hash for $TRIPLE is '${algo:-untagged}', not sha256 — this installer cannot verify it"
  SHA256="$(hash_hex "$BOOTSTRAP")"
else
  SHA256="$(json_get "$TMP/manifest.json" "$TRIPLE" sha256)"
fi
[ -n "$SHA256" ] || die "manifest has no sha256 for $TRIPLE — refusing to install unverified bytes"

# The canonical yah asset hash. Checked only opportunistically — see the trust
# model above for why it cannot be the mandatory one on a bare box.
BLAKE3="$(json_get "$TMP/manifest.json" "$TRIPLE" hash)"
say "version $VERSION"
say "$URL"

if [ "$DRY_RUN" -eq 1 ]; then
  [ -z "$STORE" ] || say "store slot $STORE/versions/$VERSION"
  step "dry run — resolved, nothing downloaded"
  exit 0
fi

# Store mode: the slot is immutable once its marker exists, so an already
# installed version is a no-op decided BEFORE the download, and a slot without
# a marker is a partial install from an interrupted run — never trust it.
if [ -n "$STORE" ]; then
  DEST="$STORE/versions/$VERSION"
  if [ -f "$DEST/.installed" ]; then
    step "already installed — $DEST"
    place_shims  # a shim refresh is still wanted even when the slot exists
    exit 0
  fi
  if [ -e "$DEST" ]; then
    step "removing incomplete install at $DEST"
    rm -rf "$DEST"
  fi
fi

# ── download + verify ───────────────────────────────────────────────────────
ARCHIVE="$TMP/$(basename "$URL")"
step "downloading"
fetch "$URL" "$ARCHIVE" || die "download failed: $URL"

step "verifying sha256"
GOT="$(sha256_of "$ARCHIVE")"
if [ "$GOT" != "$SHA256" ]; then
  die "sha256 mismatch for $(basename "$URL")
    expected $SHA256
    got      $GOT
  Refusing to install. This means the bytes you received are not the bytes we published."
fi
say "ok  sha256:${SHA256}"

# Bonus check when the box happens to have b3sum: blake3 is the canonical
# identity, so verifying it here means the public download and every internal
# consumer agree on the same value. Absence is not an error (that would defeat
# the one-line installer); a MISMATCH is.
if [ -n "$BLAKE3" ] && command -v b3sum >/dev/null 2>&1; then
  algo="$(hash_algo "$BLAKE3")"
  if [ "$algo" = "blake3" ]; then
    step "verifying blake3"
    want="$(hash_hex "$BLAKE3")"
    got="$(b3sum --no-names "$ARCHIVE")"
    if [ "$got" != "$want" ]; then
      die "blake3 mismatch for $(basename "$URL")
    expected $want
    got      $got
  Refusing to install. This means the bytes you received are not the bytes we published."
    fi
    say "ok  blake3:${want}"
  fi
fi

step "verifying sigstore signature"
if command -v cosign >/dev/null 2>&1; then
  BUNDLE_URL="$(json_get "$TMP/manifest.json" "$TRIPLE" bundle_url)"
  if [ -z "$BUNDLE_URL" ]; then
    die "cosign is installed but the manifest carries no signature bundle for $TRIPLE"
  fi
  fetch "$BUNDLE_URL" "$ARCHIVE.sigstore.json" \
    || die "could not fetch signature bundle: $BUNDLE_URL"
  case "$COSIGN_IDENTITY" in
    key:*)
      # Key-based: no Fulcio cert identity to check, just the pinned key.
      # --insecure-ignore-tlog: key-based signing deliberately signs with no
      # transparency-log upload (W235 — the point of key trust is working
      # with no public-Sigstore-infra dependency), so verification has to be
      # told the same thing or it hard-fails expecting an inclusion proof
      # that was never produced. Mirrors ReleaseTrust::verify_flags in
      # oss/yubaba/crates/cloud/src/release_manifest.rs — keep both in sync.
      COSIGN_KEY="${COSIGN_IDENTITY#key:}"
      cosign verify-blob \
        --key "$COSIGN_KEY" \
        --insecure-ignore-tlog \
        --bundle "$ARCHIVE.sigstore.json" \
        "$ARCHIVE" >/dev/null 2>&1 \
        || die "cosign verify-blob REJECTED $(basename "$URL") — not installing"
      say "ok  signed by key $COSIGN_KEY"
      ;;
    *)
      cosign verify-blob \
        --certificate-identity-regexp "$COSIGN_IDENTITY" \
        --certificate-oidc-issuer "$COSIGN_ISSUER" \
        --bundle "$ARCHIVE.sigstore.json" \
        "$ARCHIVE" >/dev/null 2>&1 \
        || die "cosign verify-blob REJECTED $(basename "$URL") — not installing"
      say "ok  signed by $COSIGN_ISSUER"
      ;;
  esac
elif [ "${YAH_INSTALL_REQUIRE_SIG:-0}" = "1" ]; then
  die "YAH_INSTALL_REQUIRE_SIG=1 but cosign is not installed (https://docs.sigstore.dev/cosign)"
else
  say "skipped — cosign not installed; the sha256 check above still ran."
  say "         install cosign, or set YAH_INSTALL_REQUIRE_SIG=1, for full supply-chain verification."
fi

# ── unpack ──────────────────────────────────────────────────────────────────
step "unpacking"
mkdir -p "$TMP/unpacked"
# The tarball wraps its binaries in one `<pkg>-<tag>-<triple>/` stage dir (the
# shape every release.yml packaging step produces); strip it so the binaries
# land flat regardless of what the tag was.
tar -xzf "$ARCHIVE" -C "$TMP/unpacked" --strip-components=1

BINS=""
for f in "$TMP/unpacked"/*; do
  [ -f "$f" ] || continue
  BINS="$BINS $(basename "$f")"
done
[ -n "$BINS" ] || die "archive contained no binaries"

# ── install ─────────────────────────────────────────────────────────────────
# Prefer /usr/local/bin (already on PATH everywhere), escalate with sudo only
# if it exists and we are not already root, and fall back to ~/.local/bin
# rather than failing — a user without sudo should still end up with working
# binaries, just with a PATH note.
SUDO=""
if [ -n "$STORE" ]; then
  # A store is user-owned by construction — never sudo into it.
  mkdir -p "$DEST" 2>/dev/null || die "cannot create $DEST"
  [ -w "$DEST" ] || die "$DEST is not writable"
elif [ -n "${YAH_INSTALL_DIR:-}" ]; then
  DEST="$YAH_INSTALL_DIR"
  mkdir -p "$DEST" 2>/dev/null || die "cannot create $DEST"
  [ -w "$DEST" ] || die "$DEST is not writable"
else
  DEST=/usr/local/bin
  if [ -d "$DEST" ] && [ -w "$DEST" ]; then
    :
  elif [ "$(id -u)" != 0 ] && command -v sudo >/dev/null 2>&1; then
    SUDO=sudo
    step "installing to $DEST (needs sudo)"
  else
    DEST="$HOME/.local/bin"
    mkdir -p "$DEST"
  fi
fi

step "installing to $DEST"
for f in "$TMP/unpacked"/*; do
  [ -f "$f" ] || continue
  $SUDO install -m 0755 "$f" "$DEST/$(basename "$f")" 2>/dev/null \
    || { $SUDO cp "$f" "$DEST/$(basename "$f")" && $SUDO chmod 0755 "$DEST/$(basename "$f")"; } \
    || die "could not install $(basename "$f") into $DEST"
  say "$DEST/$(basename "$f")"
done

if [ -n "$STORE" ]; then
  # Marker last: its presence is the completeness proof the store relies on.
  # No PATH note and no courtesy --help — nothing here is on PATH, the shim is.
  {
    echo "package=$PACKAGE"
    echo "version=$VERSION"
    echo "triple=$TRIPLE"
    echo "bins=${BINS# }"
    echo "installed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo unknown)"
  } > "$DEST/.installed" || die "could not write $DEST/.installed"
  # First version into a store becomes its default, so a fresh box's shim
  # resolves something. Never overwrite a default the user already chose.
  if [ "$PACKAGE" = mesofact ] && [ ! -f "$STORE/default" ]; then
    printf '%s\n' "$VERSION" > "$STORE/default"
    say "default → $VERSION"
  fi
  place_shims
  printf '\n'
  step "installed $PACKAGE $VERSION into store —$BINS"
  exit 0
fi

case ":$PATH:" in
  *":$DEST:"*) ;;
  *)
    printf '\n'
    say "NOTE: $DEST is not on your PATH. Add it:"
    say "      export PATH=\"$DEST:\$PATH\""
    ;;
esac

printf '\n'
step "installed $PACKAGE $VERSION —$BINS"
# Courtesy `--help`, generalized off the package name rather than hard-coded to
# one product. The package's *own* binary is the one worth showing (`yah` for
# `yah`, `mesofact` for `mesofact`) — the siblings in the tarball (yahh/yahb/
# yaha, mes) are not the entry point. Best-effort throughout: a
# package whose main binary isn't named after it just prints nothing extra,
# and a cross-installed binary that can't exec here must not fail the install
# that already succeeded.
if [ -x "$DEST/$PACKAGE" ]; then
  "$DEST/$PACKAGE" --help 2>/dev/null | head -12 || true
fi
