#!/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: " buckit." 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 "$@"