#!/usr/bin/env sh
#
# Quantum Core PQ Scanner installer.
#
#   curl -fsSL https://functorfund.com/quantum/install.sh | sh
#
# Piping a script to a shell means trusting this host, so the script does what it can
# to be worth that trust:
#   1. it downloads the binary for your platform from functorfund.com/quantum/releases/;
#   2. it checks the binary's SHA-256 against the published checksums.txt;
#   3. it checks checksums.txt's ML-DSA-65 signature (NIST FIPS 204, post-quantum) against
#      the public key PINNED BELOW, so a file swapped on the server can't swap the key.
#      Needs OpenSSL 3.5+ (macOS: brew install openssl@3). Without it the signature
#      check is skipped with a warning, unless QUANTUM_REQUIRE_SIGNATURE=1.
# It refuses to install on any mismatch. Rather not pipe? Download it, read it, run it.
#
# Options (environment): QUANTUM_VERSION=0.5.1 · QUANTUM_INSTALL_DIR=~/bin ·
# QUANTUM_REQUIRE_SIGNATURE=1 · QUANTUM_BASE_URL (for testing against a local server)

set -eu

SITE="${QUANTUM_BASE_URL:-https://functorfund.com/quantum/releases}"
VERSION="${QUANTUM_VERSION:-latest}"
INSTALL_DIR="${QUANTUM_INSTALL_DIR:-}"

# Release signing key. SHA-256 of its DER form: 8c09dfef97fd603d99f0fb46435140f5f73e2fc803ae9499a38d0e3f5cd1e080
PUBKEY='-----BEGIN PUBLIC KEY-----
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
b66KNnun
-----END PUBLIC KEY-----'

log()  { printf '%s\n' "$*" >&2; }
fail() { printf 'error: %s\n' "$*" >&2; exit 1; }
need() { command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"; }

need uname
need mktemp
if command -v curl >/dev/null 2>&1; then
    fetch() { curl -fsSL -A "quantum-install" "$1" -o "$2"; }
elif command -v wget >/dev/null 2>&1; then
    fetch() { wget -qO "$2" "$1"; }
else
    fail "need curl or wget"
fi

os="$(uname -s | tr '[:upper:]' '[:lower:]')"
arch="$(uname -m)"
case "$os" in
    linux|darwin) ;;
    *) fail "unsupported OS: $os" ;;
esac
case "$arch" in
    x86_64|amd64) arch="x86_64" ;;
    aarch64|arm64) arch="$([ "$os" = "darwin" ] && echo arm64 || echo aarch64)" ;;
    *) fail "unsupported architecture: $arch" ;;
esac
asset="quantum-scan-${os}-${arch}"

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

if [ "$VERSION" = "latest" ]; then
    fetch "${SITE}/latest.txt" "${tmp}/latest.txt" || fail "could not reach ${SITE}"
    VERSION="$(tr -d '[:space:]' < "${tmp}/latest.txt")"
fi
base="${SITE}/v${VERSION}"

log "Quantum Core v${VERSION} for ${os}/${arch}"
fetch "${base}/checksums.txt" "${tmp}/checksums.txt" || fail "no release v${VERSION} at ${base}"
expected="$(grep " ${asset}\$" "${tmp}/checksums.txt" | awk '{print $1}')"
[ -n "$expected" ] || fail "v${VERSION} has no build for ${os}/${arch} yet (see https://functorfund.com/quantum/docs.html)"

# Signature first: only trust checksums.txt once its signature checks out.
fetch "${base}/checksums.txt.mldsa65.sig" "${tmp}/checksums.sig" || fail "no signature published; refusing to install"
OPENSSL=""
for c in openssl /opt/homebrew/opt/openssl@3/bin/openssl /usr/local/opt/openssl@3/bin/openssl; do
    if command -v "$c" >/dev/null 2>&1 && "$c" list -signature-algorithms 2>/dev/null | grep -qi "ML-DSA-65"; then OPENSSL="$c"; break; fi
done
if [ -n "$OPENSSL" ]; then
    printf '%s\n' "$PUBKEY" > "${tmp}/pub.pem"
    "$OPENSSL" pkeyutl -verify -rawin -pubin -inkey "${tmp}/pub.pem" -in "${tmp}/checksums.txt" \
        -sigfile "${tmp}/checksums.sig" >/dev/null 2>&1 || fail "ML-DSA-65 SIGNATURE MISMATCH on checksums.txt. NOT installing."
    log "Signature OK (ML-DSA-65, pinned key)."
elif [ "${QUANTUM_REQUIRE_SIGNATURE:-0}" = "1" ]; then
    fail "QUANTUM_REQUIRE_SIGNATURE=1 but no OpenSSL 3.5+ with ML-DSA found"
else
    log "WARNING: no OpenSSL 3.5+ here, so the post-quantum signature was NOT checked (checksum only)."
fi

fetch "${base}/${asset}" "${tmp}/quantum-scan" || fail "download failed"
if command -v sha256sum >/dev/null 2>&1; then
    actual="$(sha256sum "${tmp}/quantum-scan" | awk '{print $1}')"
elif command -v shasum >/dev/null 2>&1; then
    actual="$(shasum -a 256 "${tmp}/quantum-scan" | awk '{print $1}')"
else
    fail "need sha256sum or shasum to verify the download"
fi
[ "$expected" = "$actual" ] || fail "checksum mismatch: expected ${expected}, got ${actual}. NOT installing."
log "Checksum OK."

if [ -z "$INSTALL_DIR" ]; then
    if [ -w "/usr/local/bin" ]; then INSTALL_DIR="/usr/local/bin"; else INSTALL_DIR="${HOME}/.local/bin"; fi
fi
mkdir -p "$INSTALL_DIR"
chmod +x "${tmp}/quantum-scan"
mv "${tmp}/quantum-scan" "${INSTALL_DIR}/quantum-scan"
log ""
log "Installed ${INSTALL_DIR}/quantum-scan (v${VERSION})"
case ":${PATH}:" in
    *":${INSTALL_DIR}:"*) ;;
    *) log "NOTE: ${INSTALL_DIR} is not on your PATH:  export PATH=\"${INSTALL_DIR}:\$PATH\"" ;;
esac
log "Try it:  quantum-scan . --out quantum-report.md --source-only"
