#!/usr/bin/env bash
# install.sh — one-command installer for banger.
#
# Designed to be invoked as:
#
#     curl -fsSL https://releases.thaloco.com/banger/install.sh | bash
#
# The script runs as the invoking user, downloads + verifies the
# release tarball unprivileged, and only re-execs sudo for the actual
# install step (writing to /usr/local/* and creating systemd units).
# Right before the sudo prompt the user gets a plain-language summary
# of exactly what's about to happen, so they're authorising a known
# scope rather than the whole pipeline.
#
# Flags:
#   --yes        skip the interactive confirmation (CI / scripted use).
#                Same effect as exporting BANGER_INSTALL_NONINTERACTIVE=1,
#                which is friendlier through `curl | bash` since you can
#                set the env var in the same line.
#   --version v  install a specific version instead of latest_stable
#
# Trust model:
#   * The cosign public key below is pinned at script-write time and
#     matches internal/updater/verify_signature.go in the source repo.
#   * The script verifies the cosign signature on SHA256SUMS, then
#     verifies the tarball's hash against SHA256SUMS, before extracting.
#   * Verification uses openssl (every Linux distro ships it). cosign
#     is needed only for *signing* a release, never for verifying one.
#   * Manifest URL is hardcoded so a DNS-redirect cannot point us at a
#     different bucket.

set -euo pipefail

MANIFEST_URL="https://releases.thaloco.com/banger/manifest.json"
BUCKET_BASE="https://releases.thaloco.com/banger"
TRUST_DOC_URL="https://git.thaloco.com/thaloco/banger/src/branch/main/docs/privileges.md"

# This must stay byte-identical to BangerReleasePublicKey in
# internal/updater/verify_signature.go — publish-banger-release.sh
# rejects publishing if they drift apart.
BANGER_PUBLIC_KEY='-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAElWFSLKLosBrdjfuF8ZS6U01Ufky4
zNeVPCkA6HEJ/oe634fRqwFxkXKGWg03eGFSnlwRxnUxN2+duXQSsR0pzQ==
-----END PUBLIC KEY-----'

log()  { printf '[banger-install] %s\n' "$*" >&2; }
warn() { printf '[banger-install] WARN: %s\n' "$*" >&2; }
die()  { printf '[banger-install] ERROR: %s\n' "$*" >&2; exit 1; }

# ----------------------------------------------------------------------
# Flag parsing
# ----------------------------------------------------------------------
ASSUME_YES="${BANGER_INSTALL_NONINTERACTIVE:-0}"
TARGET_VERSION=""

while [[ $# -gt 0 ]]; do
    case "$1" in
        -y|--yes)     ASSUME_YES=1 ;;
        --version)    TARGET_VERSION="${2:-}"; shift ;;
        --version=*)  TARGET_VERSION="${1#--version=}" ;;
        -h|--help)
            sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'
            exit 0
            ;;
        *) die "unknown argument: $1 (try --help)" ;;
    esac
    shift
done

# ----------------------------------------------------------------------
# Platform + tool prerequisites
# ----------------------------------------------------------------------
[[ "$(uname -s)" == "Linux" ]] || die "banger only supports Linux (saw $(uname -s))"
[[ "$(uname -m)" == "x86_64" ]] || die "banger only supports amd64 (saw $(uname -m))"

for tool in curl sha256sum tar openssl mktemp base64 grep sed; do
    command -v "$tool" >/dev/null \
        || die "required tool not in PATH: $tool"
done

# ----------------------------------------------------------------------
# Resolve target version
# ----------------------------------------------------------------------
if [[ -z "$TARGET_VERSION" ]]; then
    log "fetching $MANIFEST_URL"
    MANIFEST=$(curl -fsSL --max-time 30 "$MANIFEST_URL") \
        || die "failed to fetch manifest"
    # Pull `latest_stable` out without depending on jq — manifest shape
    # is well-defined and we control it.
    TARGET_VERSION=$(printf '%s' "$MANIFEST" \
        | grep -oE '"latest_stable"[[:space:]]*:[[:space:]]*"v[^"]+"' \
        | head -n1 \
        | sed -E 's/.*"v([^"]+)".*/v\1/')
    [[ -n "$TARGET_VERSION" ]] || die "could not parse latest_stable from manifest"
fi

case "$TARGET_VERSION" in
    v*.*.*) ;;
    *) die "unexpected version shape: $TARGET_VERSION (want vX.Y.Z)" ;;
esac

log "target version: $TARGET_VERSION"

# ----------------------------------------------------------------------
# Download tarball + sums + signature
# ----------------------------------------------------------------------
WORK_DIR=$(mktemp -d -t banger-install.XXXXXX)
trap 'rm -rf "$WORK_DIR"' EXIT

TARBALL_NAME="banger-$TARGET_VERSION-linux-amd64.tar.gz"
RELEASE_BASE="$BUCKET_BASE/$TARGET_VERSION"

log "downloading $TARBALL_NAME"
curl -fsSL --max-time 300 "$RELEASE_BASE/$TARBALL_NAME"   -o "$WORK_DIR/$TARBALL_NAME" \
    || die "failed to download tarball"
curl -fsSL --max-time 30  "$RELEASE_BASE/SHA256SUMS"      -o "$WORK_DIR/SHA256SUMS" \
    || die "failed to download SHA256SUMS"
curl -fsSL --max-time 30  "$RELEASE_BASE/SHA256SUMS.sig"  -o "$WORK_DIR/SHA256SUMS.sig" \
    || die "failed to download SHA256SUMS.sig"

# ----------------------------------------------------------------------
# Verify cosign signature on SHA256SUMS (the tarball's hash is INSIDE
# SHA256SUMS, so a valid signature on SHA256SUMS plus a hash match on
# the tarball authenticates the whole release).
# ----------------------------------------------------------------------
log "verifying signature on SHA256SUMS"
printf '%s\n' "$BANGER_PUBLIC_KEY" > "$WORK_DIR/cosign.pub"
base64 -d "$WORK_DIR/SHA256SUMS.sig" > "$WORK_DIR/SHA256SUMS.sig.bin" \
    || die "signature is not valid base64"
openssl dgst -sha256 \
    -verify "$WORK_DIR/cosign.pub" \
    -signature "$WORK_DIR/SHA256SUMS.sig.bin" \
    "$WORK_DIR/SHA256SUMS" >/dev/null 2>&1 \
    || die "signature verification failed — refusing to install"
log "  signature OK"

# ----------------------------------------------------------------------
# Verify tarball hash against SHA256SUMS
# ----------------------------------------------------------------------
log "verifying $TARBALL_NAME against SHA256SUMS"
( cd "$WORK_DIR" && sha256sum -c --status SHA256SUMS ) \
    || die "tarball hash mismatch — refusing to install"
log "  hash OK"

# ----------------------------------------------------------------------
# Extract (validation is server-side via StageTarball when banger
# update runs; the install script trusts the verified tarball).
# ----------------------------------------------------------------------
log "extracting"
mkdir -p "$WORK_DIR/stage"
tar -xzf "$WORK_DIR/$TARBALL_NAME" -C "$WORK_DIR/stage"

for bin in banger bangerd banger-vsock-agent; do
    [[ -f "$WORK_DIR/stage/$bin" ]] \
        || die "tarball missing expected binary: $bin"
done

# ----------------------------------------------------------------------
# System install: confirm scope, then re-exec sudo
# ----------------------------------------------------------------------
SUMMARY=$(cat <<EOF

About to install banger $TARGET_VERSION (requires sudo):

  /usr/local/bin/banger
  /usr/local/bin/bangerd
  /usr/local/lib/banger/banger-vsock-agent
  /etc/systemd/system/bangerd.service       (background daemon)
  /etc/systemd/system/bangerd-root.service  (privileged helper)

banger needs your permission to:

  • set up VM networking (bridges, NAT, DNS routing for <vm>.vm)
  • manage VM storage (rootfs snapshots, loop devices, image files)
  • launch and stop firecracker processes under jailer isolation
  • install the binaries to /usr/local and the systemd units above

Once installed, day-to-day commands like 'banger vm run' and
'banger image pull' run as you. Only the narrow set of operations
above goes through the privileged helper service.

For details, see: $TRUST_DOC_URL

EOF
)
printf '%s\n' "$SUMMARY"

if [[ "$ASSUME_YES" -ne 1 ]]; then
    if [[ ! -t 0 ]] && [[ ! -r /dev/tty ]]; then
        die "no terminal available to confirm; re-run with --yes"
    fi
    REPLY=""
    if [[ -t 0 ]]; then
        read -r -p "Continue? [y/N] " REPLY
    else
        # curl|bash path: stdin is the pipe; reach for the user's tty.
        read -r -p "Continue? [y/N] " REPLY < /dev/tty
    fi
    case "$REPLY" in
        y|Y|yes|YES) ;;
        *) die "aborted by user" ;;
    esac
fi

log "elevating to sudo for the install step"
SUDO=""
if [[ "$EUID" -ne 0 ]]; then
    command -v sudo >/dev/null \
        || die "not running as root and sudo is not in PATH"
    SUDO="sudo"
fi

# Copy binaries into place. We do the copies + chmod + system install
# from the *staged* tarball under $WORK_DIR; using `install` is the
# right tool here because it handles atomic-ish replacement and mode
# bits in one shot.
$SUDO install -m 0755 -D "$WORK_DIR/stage/banger"             /usr/local/bin/banger
$SUDO install -m 0755 -D "$WORK_DIR/stage/bangerd"            /usr/local/bin/bangerd
$SUDO install -m 0755 -D "$WORK_DIR/stage/banger-vsock-agent" /usr/local/lib/banger/banger-vsock-agent

log "registering systemd units (banger system install)"
$SUDO /usr/local/bin/banger system install

cat <<EOF >&2

banger $TARGET_VERSION installed.

Next steps:
  banger doctor                    # confirm host readiness
  banger vm run                    # boot a sandbox
  banger ssh-config --install      # optional: enable 'ssh <name>.vm'

Updates land via:
  banger update --check
  sudo banger update

EOF
