#!/usr/bin/env sh
# get.hilo.team one-liner:   curl -fsSL https://get.hilo.team | sh
#
# Downloads the latest release (built by release/make-release.sh, published by
# release/publish-release.sh), VERIFIES its sha256 against the manifest, lands the code
# in a DURABLE code dir, and runs the installer from there. Works with a PRIVATE code
# repo — it fetches release tarballs, not the repo.
#
# TRUST MODEL — read this. This bootstrap trusts the CHANNEL over TLS, NOT the signing key.
# The sha256 it checks comes from the same manifest it just fetched, so it only catches a
# corrupted download, not a malicious one: anyone who can write the bucket rewrites the
# manifest, the tarball, AND this very script together. That is inherent to `curl | sh` —
# the script itself arrives from the channel (like rustup, homebrew, etc.). The root of
# trust for INSTALL is therefore Cloudflare's control of get.hilo.team + TLS. The pinned
# ed25519 key protects UPDATES, not this first fetch: it lives in the installed node's own
# code (config.OFFICIAL_UPDATE_PUBKEY) and so survives a later bucket compromise. For a
# stronger install anchor, use the git-clone / agent-paste path against the private repo
# (GitHub auth is the anchor there). docs/RELEASING.md has the full threat model.
#
# The code dir matters: the installer registers a service pointing at wherever the code
# sits, and the self-updater swaps THAT dir in place — so the extraction must never be
# installed from a temp path (it would be deleted out from under the running service).
#
# Env: HILO_CODE_DIR (override the native application-data location where code lives;
#                     this is NOT the customer data dir ~/.hilo)
#      HILO_RELEASE_BASE (default https://get.hilo.team/releases)
#      HILO_MANIFEST (default <HILO_RELEASE_BASE>/manifest.json when HILO_RELEASE_BASE is
#      set, else https://get.hilo.team/manifest.json)
# Installer flags pass through. Hosted onboarding uses:
#   curl -fsSL https://get.hilo.team | sh -s -- --connection-file '/exact/file.hilo-connection'
set -e

default_code_dir() {
  case "$(uname -s)" in
    Darwin) printf '%s\n' "$HOME/Library/Application Support/Hilo/node" ;;
    Linux)  printf '%s\n' "${XDG_DATA_HOME:-$HOME/.local/share}/hilo/node" ;;
    *)      printf '%s\n' "${XDG_DATA_HOME:-$HOME/.local/share}/hilo/node" ;;
  esac
}

CODE_DIR="${HILO_CODE_DIR:-$(default_code_dir)}"

# Releases before the native-layout cutover landed at ~/hilo. Never interpret a missing
# new default as permission to install a SECOND runtime against the same ~/.hilo data:
# an existing legacy release keeps its path and continues through the signed self-updater.
# A deliberate HILO_CODE_DIR always wins, including for a second-node installation.
if [ -z "${HILO_CODE_DIR:-}" ] && [ ! -e "$CODE_DIR" ] && \
   [ -f "$HOME/hilo/hilo_node/__init__.py" ]; then
  CODE_DIR="$HOME/hilo"
fi
if [ -n "${HILO_RELEASE_BASE:-}" ]; then
  BASE="$HILO_RELEASE_BASE"
  MANIFEST="${HILO_MANIFEST:-$BASE/manifest.json}"
else
  BASE="https://get.hilo.team/releases"
  MANIFEST="${HILO_MANIFEST:-https://get.hilo.team/manifest.json}"
fi
command -v python3 >/dev/null 2>&1 || { echo "hilo: python3 is required" >&2; exit 1; }
command -v bash >/dev/null 2>&1 || { echo "hilo: bash is required" >&2; exit 1; }

fetch() {  # fetch URL -> file (curl or wget; supports file://)
  if command -v curl >/dev/null 2>&1; then curl -fsSL "$1" -o "$2"
  else wget -qO "$2" "$1"; fi
}
sha_of() {
  if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}'
  else sha256sum "$1" | awk '{print $1}'; fi
}
jval() { python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get(sys.argv[2],""))' "$1" "$2"; }

# An existing install keeps its code dir — the SELF-UPDATER owns upgrades (backup +
# verify + health-check + rollback); re-extracting over live code would bypass all of it.
# The one exception is retrying the exact hosted bootstrap command. Its one-time file is
# consumed before the final doctor check, so a healthy retry must reuse the installed code
# and durable pairing journal rather than demand an already-consumed credential again.
if [ -e "$CODE_DIR" ]; then
  if [ -f "$CODE_DIR/hilo_node/__init__.py" ]; then
    HOSTED_RETRY=0
    for arg in "$@"; do
      [ "$arg" = "--connection-file" ] && HOSTED_RETRY=1
    done
    if [ "$HOSTED_RETRY" = 1 ]; then
      echo "hilo: resuming the existing hosted installation at $CODE_DIR"
      HILO_HOSTED_RESUME=1 exec bash "$CODE_DIR/install/install.sh" "$@"
    fi
    echo "hilo: an install already exists at $CODE_DIR." >&2
    echo "hilo: to upgrade it, run:  hilo-node update (or hilo update)" >&2
    echo "hilo: to install a second node elsewhere:  HILO_CODE_DIR=/other/dir + HILO_HOME=/other/home" >&2
  else
    echo "hilo: $CODE_DIR exists and is not a hilo install — refusing to touch it." >&2
    echo "hilo: set HILO_CODE_DIR to an empty/new path." >&2
  fi
  exit 1
fi

TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
echo "hilo: reading $MANIFEST"
fetch "$MANIFEST" "$TMP/manifest.json"
VER="$(jval "$TMP/manifest.json" version)"
URL="$(jval "$TMP/manifest.json" tarball_url)"
SHA="$(jval "$TMP/manifest.json" sha256)"
[ -n "$VER" ] && [ -n "$URL" ] && [ -n "$SHA" ] || { echo "hilo: bad manifest" >&2; exit 1; }
# the tarball URL comes from the fetched manifest (remote-controlled) — same scheme wall
# as the node updater (update.py _require_safe_url): https or file (local tests) only
case "$URL" in
  https://*|file://*) ;;
  *) echo "hilo: refusing tarball over insecure scheme: $URL" >&2; exit 1 ;;
esac

echo "hilo: downloading $VER"
fetch "$URL" "$TMP/hilo.tar.gz"
GOT="$(sha_of "$TMP/hilo.tar.gz")"
[ "$GOT" = "$SHA" ] || { echo "hilo: checksum mismatch — refusing (expected $SHA, got $GOT)" >&2; exit 1; }

mkdir -p "$TMP/src"
tar -xzf "$TMP/hilo.tar.gz" -C "$TMP/src"
# the release tarball is FLAT (hilo_node/ at root); tolerate a wrapped one too
INSTALL="$(find "$TMP/src" -maxdepth 3 -path '*/install/install.sh' | head -1)"
[ -n "$INSTALL" ] || { echo "hilo: installer not found in the release" >&2; exit 1; }

# land the code somewhere durable BEFORE installing: the service plist/unit and the
# self-updater both point at this dir for the life of the install
ROOT="$(dirname "$(dirname "$INSTALL")")"
PARENT="$(dirname "$CODE_DIR")"
mkdir -p "$PARENT"
# mv can fail mid-copy across filesystems, leaving a PARTIAL $CODE_DIR — clear it before
# the copy fallback or cp -R would nest the tree INSIDE it. Safe: we refused any
# pre-existing $CODE_DIR above, so whatever sits there now is our own debris.
if ! mv "$ROOT" "$CODE_DIR" 2>/dev/null; then
  rm -rf "$CODE_DIR"
  cp -R "$ROOT" "$CODE_DIR"
fi
echo "hilo: code → $CODE_DIR"

echo "hilo: installing $VER"
# get.sh itself is deliberately POSIX because customers pipe it into `sh`. The installed
# entrypoint is deliberately Bash (`set -o pipefail`, arrays in future-safe helpers).
# macOS currently aliases /bin/sh to a Bash-compatible shell, which hid this defect;
# Ubuntu's /bin/sh is Dash and correctly rejects pipefail. Honor the installer's declared
# interpreter instead of forcing it back through the bootstrap shell.
bash "$CODE_DIR/install/install.sh" "$@"
