banger/scripts/install.sh
Thales Maciel f1b17f6f8e
install: surface ssh-config --install in next-steps blurb
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:57:26 -03:00

237 lines
9.1 KiB
Bash
Executable file

#!/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