237 lines
9.1 KiB
Bash
Executable file
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
|