#!/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) # --user install binaries to ~/.local/bin and stop; the user # runs `sudo banger system install` later when ready # --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=0 USER_INSTALL=0 TARGET_VERSION="" while [[ $# -gt 0 ]]; do case "$1" in -y|--yes) ASSUME_YES=1 ;; --user) USER_INSTALL=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 # ---------------------------------------------------------------------- # --user mode: drop binaries in ~/.local/bin and stop # ---------------------------------------------------------------------- if [[ "$USER_INSTALL" -eq 1 ]]; then USER_BIN="${HOME}/.local/bin" USER_LIB="${HOME}/.local/lib/banger" mkdir -p "$USER_BIN" "$USER_LIB" install -m 0755 "$WORK_DIR/stage/banger" "$USER_BIN/banger" install -m 0755 "$WORK_DIR/stage/bangerd" "$USER_BIN/bangerd" install -m 0755 "$WORK_DIR/stage/banger-vsock-agent" "$USER_LIB/banger-vsock-agent" cat <&2 Installed banger $TARGET_VERSION to: $USER_BIN/banger $USER_BIN/bangerd $USER_LIB/banger-vsock-agent Make sure $USER_BIN is in your PATH, then finish setup with: sudo $USER_BIN/banger system install $USER_BIN/banger doctor EOF exit 0 fi # ---------------------------------------------------------------------- # System install: confirm scope, then re-exec sudo # ---------------------------------------------------------------------- SUMMARY=$(cat </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 <&2 banger $TARGET_VERSION installed. Next steps: banger doctor # confirm host readiness banger image pull debian-bookworm # fetch a default image banger vm run # boot a sandbox Updates land via: banger update --check sudo banger update EOF