Files
buckit/packaging/install-linux.sh
abuckit 0801e31164 refactor(packaging): merge the per-platform binary installers (#28)
install-linux-binary.sh and install-mac.sh were 443 lines that differed
in three places: the OS check and architecture allowlist, the platform
token in the pointer URL and asset name, and clearing the macOS
quarantine attribute. The other ~190 lines were identical.

That duplication already cost something. Hardening the installers meant
applying five fixes twice, by hand, in parallel -- pin-bypass path
traversal, pipeline masking, digest normalisation, directory
destination, and the cross-origin digest check. The next fix would have
had the same shape, and eventually one would land in only one file.

Merge them into install-binary.sh, which detects Linux or macOS and
validates the architecture against what is published for that platform:
Linux ships amd64 and arm64, macOS ships Apple Silicon only.

The release workflow publishes it under the old names as well, so URLs
already in the wild, in the docs, and in the blog post keep working and
pick up the merged behaviour on every release. Nothing needs to change
on the reader's side, and there is no window where a documented command
404s.

Two benign consequences: install-mac.sh now also works on Linux and
install-linux-binary.sh on macOS, and error messages self-identify as
install-binary.sh whichever URL was fetched.
2026-08-04 20:57:01 -04:00

237 lines
7.1 KiB
Bash
Executable File

#!/bin/sh
# install-linux.sh — Linux native-package installer helper for buckit.
#
# Detects this host's package manager (dnf/yum/zypper, apt/apt-get/dpkg, or
# apk), downloads the matching .rpm/.deb/.apk for the latest stable release to
# a predictable filename (buckit.rpm / buckit.deb / buckit.apk), verifies its
# published SHA-256 checksum, and prints the command to install it. It does
# NOT run the package manager for you — the final install (which needs root)
# is left to you to review and run.
#
# Usage:
# curl -fsSL https://buckit-io.github.io/buckit/install-linux.sh | sh
# sudo dnf install ./buckit.rpm # or apt/apk, per the printed command
#
# Environment overrides:
# BUCKIT_PAGES_BASE gh-pages base URL
# (default: https://buckit-io.github.io/buckit)
# BUCKIT_RELEASE_BASE release download base
# (default: https://github.com/buckit-io/buckit/releases/download)
# BUCKIT_VERSION pin a release tag (e.g. RELEASE.2026-05-11T17-20-40Z)
# instead of resolving the latest stable release
# BUCKIT_DOWNLOAD_DIR directory to download the package into
# (default: the current directory)
set -eu
PAGES_BASE="${BUCKIT_PAGES_BASE:-https://buckit-io.github.io/buckit}"
RELEASE_BASE="${BUCKIT_RELEASE_BASE:-https://github.com/buckit-io/buckit/releases/download}"
err() {
echo "install-linux.sh: $*" >&2
exit 1
}
info() {
echo "==> $*"
}
# detect_sudo sets SUDO to the privilege-escalation prefix for install
# commands: empty when already root, "sudo " when sudo is available, otherwise
# empty with a warning (the printed command must then be run as root).
detect_sudo() {
if [ "$(id -u)" -eq 0 ]; then
SUDO=""
elif command -v sudo >/dev/null 2>&1; then
SUDO="sudo "
else
SUDO=""
info "not running as root and sudo not found — run the install command below as root"
fi
}
# detect_arch sets ARCH to the Go-style arch tuple (amd64 / arm64) and
# requires a Linux host — native packages are Linux-only.
detect_arch() {
os="$(uname -s)"
[ "$os" = "Linux" ] || err "this installer is for Linux (detected '$os'). For a standalone binary use install-binary.sh; on Windows use install-windows.ps1"
arch="$(uname -m)"
case "$arch" in
x86_64 | amd64) ARCH="amd64" ;;
arm64 | aarch64) ARCH="arm64" ;;
*) err "unsupported architecture '$arch'. Supported: amd64, arm64." ;;
esac
}
# detect_pkg sets PKG (rpm|deb|apk) and INSTALL_CMD (the command prefix used
# to install a local package file) based on the available package manager.
# Requires SUDO to be set first (see detect_sudo).
detect_pkg() {
if command -v dnf >/dev/null 2>&1; then
PKG="rpm"
INSTALL_CMD="${SUDO}dnf install"
elif command -v yum >/dev/null 2>&1; then
PKG="rpm"
INSTALL_CMD="${SUDO}yum install"
elif command -v zypper >/dev/null 2>&1; then
PKG="rpm"
INSTALL_CMD="${SUDO}zypper install"
elif command -v apt >/dev/null 2>&1; then
PKG="deb"
INSTALL_CMD="${SUDO}apt install"
elif command -v apt-get >/dev/null 2>&1; then
PKG="deb"
INSTALL_CMD="${SUDO}apt-get install"
elif command -v apk >/dev/null 2>&1; then
PKG="apk"
INSTALL_CMD="${SUDO}apk add --allow-untrusted"
elif command -v rpm >/dev/null 2>&1; then
PKG="rpm"
INSTALL_CMD="${SUDO}rpm -i"
elif command -v dpkg >/dev/null 2>&1; then
PKG="deb"
INSTALL_CMD="${SUDO}dpkg -i"
else
err "no supported package manager found (need dnf/yum/zypper, apt/dpkg, or apk)"
fi
}
# fetch URL -> stdout
fetch() {
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$1"
elif command -v wget >/dev/null 2>&1; then
wget -qO- "$1"
else
err "need curl or wget to download"
fi
}
# fetch_to URL FILE
fetch_to() {
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$1" -o "$2"
elif command -v wget >/dev/null 2>&1; then
wget -qO "$2" "$1"
else
err "need curl or wget to download"
fi
}
# sha256_of FILE -> hex digest on stdout
sha256_of() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$1" | awk '{print $1}'
else
err "need sha256sum or shasum to verify the download"
fi
}
# resolve_tag echoes the release tag to install, either the pinned
# BUCKIT_VERSION or the latest stable tag from the gh-pages pointer.
resolve_tag() {
if [ -n "${BUCKIT_VERSION:-}" ]; then
echo "$BUCKIT_VERSION"
return
fi
pointer_url="$PAGES_BASE/server/buckit/release/linux-$ARCH/buckit.sha256sum"
# Pointer format: "<sha256> buckit.<tag>"
pointer="$(fetch "$pointer_url")" || err "could not fetch release pointer at $pointer_url"
name="$(echo "$pointer" | awk '{print $2}')"
tag="${name#buckit.}"
case "$tag" in
RELEASE.*) ;;
*) err "unexpected release pointer payload: $pointer" ;;
esac
echo "$tag"
}
# pkg_version TAG -> nfpm package version, mirroring the release workflow:
# RELEASE.2026-05-11T17-20-40Z[.rcN] -> 20260511172040.0.0
pkg_version() {
echo "$1" | sed 's/^RELEASE\.//' | sed 's/\.rc[0-9]*$//' | tr -d '\-:TZ' | sed 's/$/.0.0/'
}
main() {
detect_arch
detect_sudo
detect_pkg
info "platform: linux-$ARCH ($PKG)"
tag="$(resolve_tag)"
[ -n "$tag" ] || err "could not resolve a release tag"
info "release: $tag"
pkgver="$(pkg_version "$tag")"
# Build the package filename. nfpm maps amd64->x86_64 / arm64->aarch64
# for rpm and apk; deb keeps the Debian arch names.
case "$PKG" in
rpm)
case "$ARCH" in
amd64) pkgarch="x86_64" ;;
arm64) pkgarch="aarch64" ;;
esac
asset="buckit-${pkgver}.${pkgarch}.rpm"
;;
deb)
asset="buckit_${pkgver}_${ARCH}.deb"
;;
apk)
case "$ARCH" in
amd64) pkgarch="x86_64" ;;
arm64) pkgarch="aarch64" ;;
esac
asset="buckit_${pkgver}_${pkgarch}.apk"
;;
esac
download_url="$RELEASE_BASE/$tag/$asset"
# Save to a predictable filename (buckit.rpm / buckit.deb / buckit.apk) so
# the install command is stable and copy-pasteable in docs. The "./" form
# also matters for apt, which treats a path without a slash as a package
# name rather than a local file.
dldir="${BUCKIT_DOWNLOAD_DIR:-.}"
mkdir -p "$dldir"
pkgfile="$dldir/buckit.$PKG"
# Download to a temporary sibling and only move it into the predictable
# path after the checksum verifies. A failed or interrupted download then
# can never clobber an existing good package or leave a partial/unverified
# file at the path the printed install command references.
tmpfile="$(mktemp "$dldir/.buckit.$PKG.XXXXXX")" || err "could not create temp file in $dldir"
trap 'rm -f "$tmpfile"' EXIT
info "downloading $asset"
fetch_to "$download_url" "$tmpfile" || err "download failed: $download_url"
info "fetching published checksum"
want_sha="$(fetch "$download_url.sha256sum" | awk '{print $1}')" ||
err "could not fetch checksum at $download_url.sha256sum"
[ -n "$want_sha" ] || err "release checksum is empty"
got_sha="$(sha256_of "$tmpfile")"
if [ "$got_sha" != "$want_sha" ]; then
err "checksum mismatch (expected $want_sha, got $got_sha) — refusing to continue"
fi
info "sha256 verified"
mv -f "$tmpfile" "$pkgfile"
trap - EXIT
echo
echo "Downloaded and verified:"
echo " $pkgfile"
echo
echo "To install, run:"
echo " $INSTALL_CMD \"$pkgfile\""
echo
}
main "$@"