#!/bin/sh
# plumb installer.  curl -fsSL https://plumbing.sh/install | sh
#
# ⚠️ THE CHECKSUM IS VERIFIED BEFORE THE BINARY IS PLACED, NOT AFTER.
# The whole point of this file is that a tampered download never reaches a
# directory on your PATH. Everything happens in a temporary directory that is
# removed on exit; the only write outside it is the final atomic `mv`, which
# runs after the archive has matched its expected digest.
#
# ⚠️ VERSION IS A CONSTANT, NOT AN OVERRIDE. The digests below are the digests
# of ONE release. Letting a caller change the version while keeping the digests
# would turn every install into a checksum failure, or -- worse, if the check
# were made conditional to accommodate it -- into no check at all.
#
# Overridable, deliberately:
#   PLUMB_BASE_URL     where to fetch from. `file://` is supported so the
#                      installer can be tested without a live site.
#   PLUMB_INSTALL_DIR  where to place the binary. Default ~/.local/bin.
set -eu

PLUMB_VERSION=1.0.0
BASE_URL="${PLUMB_BASE_URL:-https://plumbing.sh/dist}"

die() { printf 'plumb install: %s\n' "$*" >&2; exit 1; }
note() { printf '  %s\n' "$*"; }

# ---------------------------------------------------------------- the manifest
#
# ⚠️ ONE FACT PER PLATFORM: the digest of the ARCHIVE. The binary's own digest
# is deliberately NOT recorded -- it is derived by extracting an archive that
# already matched, so recording it separately would be a second copy of one
# fact and the two would eventually disagree.
#
# ⚠️ THESE LINES ARE MACHINE-WRITTEN. `python3 tools/stamp_checksums.py` fills
# them from the built artifacts. Do not type a digest by hand. A placeholder
# left in this table is the failure this file exists to prevent, so an
# unstamped platform says UNPUBLISHED and REFUSES rather than installing
# something it did not verify.
asset_for() {
  case "$1" in
    # stamped: platform | archive | sha256
    macos-arm64)   ARCHIVE=plumb-macos-arm64.zip;      FORMAT=zip; SHA256=2e8b586194c1abe7745fffb4f2d9bceab5d1d68d5e858b6e095610f701310f58 ;;
    macos-x86_64)  ARCHIVE=plumb-macos-x86_64.zip;     FORMAT=zip; SHA256=UNPUBLISHED ;;
    linux-x86_64)  ARCHIVE=plumb-linux-x86_64.tar.gz;  FORMAT=tar; SHA256=8248af89a38a39afc734da81fd5e6c2fa3e9ee9c9e6b37b41468a1240fe8f11e ;;
    linux-aarch64) ARCHIVE=plumb-linux-aarch64.tar.gz; FORMAT=tar; SHA256=4a4cc3a4343d78d686b5b32f536dd2a51ac3748bd5022b64c36316c31dc56ade ;;
    *) die "internal error: no asset table entry for '$1'" ;;
  esac
}

# ------------------------------------------------------------------- detection
detect() {
  os="$(uname -s)"
  arch="$(uname -m)"
  case "$os" in
    Darwin)
      case "$arch" in
        arm64|aarch64) PLATFORM=macos-arm64 ;;
        x86_64|amd64)  PLATFORM=macos-x86_64 ;;
        *) die "unsupported macOS architecture: $arch" ;;
      esac ;;
    Linux)
      case "$arch" in
        x86_64|amd64)  PLATFORM=linux-x86_64 ;;
        aarch64|arm64) PLATFORM=linux-aarch64 ;;
        *) die "unsupported Linux architecture: $arch" ;;
      esac ;;
    # ⚠️ WSL is NOT this branch. Inside WSL `uname -s` is Linux, so WSL takes
    # the Linux path above and works. This branch is native Windows -- Git Bash,
    # MSYS2, Cygwin -- where plumb genuinely does not run: process control uses
    # POSIX process groups.
    MINGW*|MSYS*|CYGWIN*|Windows_NT)
      die "plumb does not run on native Windows.

  It runs on Windows through WSL (Windows Subsystem for Linux).
  Open a WSL shell and run the same command there:

      wsl.exe
      curl -fsSL https://plumbing.sh/install | sh

  Install into the Linux filesystem, not /mnt/c -- a binary on a
  DrvFs mount cannot be marked executable." ;;
    *) die "unsupported operating system: $os (plumb supports macOS and Linux)" ;;
  esac
}

# -------------------------------------------------------------------- fetching
fetch() {  # fetch <url> <dest>
  case "$1" in
    file://*)
      src="${1#file://}"
      [ -f "$src" ] || die "not found: $src"
      cp "$src" "$2" ;;
    *)
      if command -v curl >/dev/null 2>&1; then
        curl -fsSL --proto '=https' --tlsv1.2 "$1" -o "$2" \
          || die "download failed: $1"
      elif command -v wget >/dev/null 2>&1; then
        wget -q --https-only -O "$2" "$1" \
          || die "download failed: $1"
      else
        die "neither curl nor wget is available"
      fi ;;
  esac
}

# ⚠️ THE TOOL IS CHOSEN HERE, NOT INSIDE THE FUNCTION. `die` called from within
# a command substitution exits the SUBSHELL. Whether that aborts the script is
# shell-dependent, so "no SHA-256 tool" must be able to fail loudly at top level
# rather than returning an empty digest that the caller then reports as a
# checksum mismatch -- the wrong diagnosis for a missing dependency.
pick_digest_tool() {
  if command -v shasum >/dev/null 2>&1;      then DIGEST=shasum
  elif command -v sha256sum >/dev/null 2>&1; then DIGEST=sha256sum
  elif command -v openssl >/dev/null 2>&1;   then DIGEST=openssl
  else die "no SHA-256 tool found (need shasum, sha256sum or openssl).

  Refusing to install without verifying the download."
  fi
}

# ⚠️ NEVER READ $? AFTER A PIPE. This captures the pipeline's stdout; the exit
# status here belongs to `awk`, not to the digest tool, so it is not consulted.
# What is checked instead is the SHAPE of the result, at the caller.
sha256_of() {
  case "$DIGEST" in
    shasum)    shasum -a 256 "$1" | awk '{print $1}' ;;
    sha256sum) sha256sum "$1"     | awk '{print $1}' ;;
    openssl)   openssl dgst -sha256 "$1" | awk '{print $NF}' ;;
  esac
}

# ------------------------------------------------------------------------ main
detect
pick_digest_tool
asset_for "$PLATFORM"

[ "$SHA256" = UNPUBLISHED ] && die "there is no published $PLATFORM build of plumb $PLUMB_VERSION yet.

  Detected: $(uname -s) $(uname -m)
  Published: macOS arm64.

  Refusing to install an unverified artifact."

printf 'plumb %s — %s\n' "$PLUMB_VERSION" "$PLATFORM"

TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT INT TERM

note "downloading $ARCHIVE"
fetch "$BASE_URL/v$PLUMB_VERSION/$ARCHIVE" "$TMP/$ARCHIVE"

note "verifying checksum"
got="$(sha256_of "$TMP/$ARCHIVE")"
# ⚠️ Shape first, comparison second. An empty or malformed digest must be
# reported as a broken digest tool, never as a checksum mismatch.
case "$got" in
  [0-9a-f]*) [ "${#got}" -eq 64 ] || die "the digest tool returned '$got', which is not a SHA-256" ;;
  *) die "the digest tool returned '$got', which is not a SHA-256" ;;
esac
if [ "$got" != "$SHA256" ]; then
  rm -f "$TMP/$ARCHIVE"
  die "CHECKSUM MISMATCH — nothing was installed.

  expected  $SHA256
  got       $got

  The download does not match the release it claims to be. Do not run it.
  Report this to haring.nathan@gmail.com."
fi
note "sha256 ok"

note "unpacking"
case "$FORMAT" in
  zip) command -v unzip >/dev/null 2>&1 || die "unzip is required to unpack $ARCHIVE"
       unzip -q -o "$TMP/$ARCHIVE" -d "$TMP/x" ;;
  tar) mkdir -p "$TMP/x"; tar -xzf "$TMP/$ARCHIVE" -C "$TMP/x" ;;
esac
[ -f "$TMP/x/plumb" ] || die "the archive did not contain a 'plumb' executable"
chmod 755 "$TMP/x/plumb"

# --------------------------------------------------------------- where it goes
# ⚠️ ~/.local/bin FIRST, and no sudo anywhere in this script. An installer that
# escalates to write /usr/local/bin is asking for a privilege it does not need;
# a per-user directory is the correct default and the one the XDG layout names.
if [ -n "${PLUMB_INSTALL_DIR:-}" ]; then
  DEST="$PLUMB_INSTALL_DIR"
  mkdir -p "$DEST" 2>/dev/null || die "cannot create $DEST"
  [ -w "$DEST" ] || die "$DEST is not writable"
else
  DEST="$HOME/.local/bin"
  if ! (mkdir -p "$DEST" 2>/dev/null && [ -w "$DEST" ]); then
    if [ -w /usr/local/bin ]; then
      DEST=/usr/local/bin
    else
      die "cannot write to $HOME/.local/bin or /usr/local/bin.

  Choose a directory you own and re-run:

      curl -fsSL https://plumbing.sh/install | PLUMB_INSTALL_DIR=~/bin sh"
    fi
  fi
fi

# ⚠️ Place via a temporary name in the DESTINATION directory, then rename. `mv`
# within one filesystem is atomic, so an interrupted install can never leave a
# half-written binary sitting on someone's PATH under the name `plumb`. Copying
# straight onto the target would also fail with "Text file busy" if a plumb
# process were running.
note "installing to $DEST/plumb"
cp "$TMP/x/plumb" "$DEST/.plumb.incoming.$$"
chmod 755 "$DEST/.plumb.incoming.$$"
mv -f "$DEST/.plumb.incoming.$$" "$DEST/plumb"

# ⚠️ Files placed by a shell script never receive the macOS quarantine
# attribute, so Gatekeeper is never consulted and no network check occurs on
# first launch. This is why the curl path needs no stapled artifact. Do not
# "helpfully" add xattr handling here -- there is no xattr to clear.

note "verifying the installed binary runs"
"$DEST/plumb" --version >/dev/null 2>&1 || die "installed, but $DEST/plumb did not run"

# ⚠️ `plumb --version` already prints "plumb 1.0.0", so a literal "plumb" here
# produced "plumb plumb 1.0.0 installed at ...". Caught by actually reading the
# output of a real staged install rather than trusting the format string.
printf '\n  %s installed at %s\n' "$("$DEST/plumb" --version)" "$DEST/plumb"

case ":${PATH:-}:" in
  *":$DEST:"*) ;;
  *) printf '\n  ⚠️  %s is not on your PATH. Add it:\n\n      export PATH="%s:$PATH"\n' "$DEST" "$DEST" ;;
esac

# ⚠️ Warn about SHADOWING, not just absence. A plumb earlier on the PATH means
# the user types `plumb` and gets the old one, with no error to explain it.
other="$(command -v plumb 2>/dev/null || true)"
if [ -n "$other" ] && [ "$other" != "$DEST/plumb" ]; then
  printf '\n  ⚠️  another plumb is earlier on your PATH: %s\n' "$other"
fi

printf '
  Next:
      cd <your repository>
      plumb            what this repository declares
      plumb rules      what it requires, quoted with file:line

  `plumb check` runs them and needs a license: https://plumbing.sh/buy
'
