feat(packaging): add install-linux-binary.sh for standalone binary installs

install-linux.sh downloads a native package and registers a systemd
service, which is the wrong shape for deployments that run the server
binary directly -- a hand-run process, a non-systemd supervisor, or a
MinIO deployment being migrated in place.

Add a Linux counterpart to install-mac.sh that fetches only the binary:
resolve the current release from the gh-pages pointer, download the
matching asset, verify its SHA-256, chmod it, and leave ./buckit in the
working directory. It does not touch PATH and does not install a service.

The checksum is verified against both the release-host .sha256sum and the
digest published in the gh-pages pointer, and the two must agree. The
binary and its adjacent checksum share an origin, so that digest alone
only proves the transfer was not corrupted; the pointer is served from a
different origin and turns the check into something an attacker must
defeat in two places. A pinned BUCKIT_VERSION skips the cross-check,
since the pointer only ever describes the latest release.

Publish the script to gh-pages alongside the others, and advertise it in
the README and the generated release-notes install table.
This commit is contained in:
abuckit
2026-08-03 20:04:48 -04:00
parent 440be6909c
commit fa2f0483a4
3 changed files with 238 additions and 1 deletions
+6 -1
View File
@@ -205,9 +205,10 @@ jobs:
- name: Publish install scripts
run: |
cp src/packaging/install-linux.sh pages/install-linux.sh
cp src/packaging/install-linux-binary.sh pages/install-linux-binary.sh
cp src/packaging/install-mac.sh pages/install-mac.sh
cp src/packaging/install-windows.ps1 pages/install-windows.ps1
chmod +x pages/install-linux.sh pages/install-mac.sh
chmod +x pages/install-linux.sh pages/install-linux-binary.sh pages/install-mac.sh
- name: Write self-update checksum files
run: |
@@ -323,6 +324,10 @@ jobs:
<td><strong>Linux</strong>rpm / deb / apk</td>
<td><code style="font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px">curl -fsSL https://buckit-io.github.io/buckit/install-linux.sh | sh</code></td>
</tr>
<tr>
<td><strong>Linux</strong>standalone binary</td>
<td><code style="font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px">curl -fsSL https://buckit-io.github.io/buckit/install-linux-binary.sh | sh</code></td>
</tr>
<tr>
<td><strong>macOS</strong>Apple Silicon</td>
<td><code style="font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px">curl -fsSL https://buckit-io.github.io/buckit/install-mac.sh | sh</code></td>
+11
View File
@@ -122,6 +122,17 @@ SHA-256 checksum, and prints the package-manager command to run:
curl -fsSL https://buckit-io.github.io/buckit/install-linux.sh | sh
```
### Linux Standalone Binary
To run the server without a package or a systemd service — for example when
taking over an existing deployment that is started by hand — download the
binary on its own. The helper script verifies its SHA-256 checksum and leaves
an executable `buckit` in the current directory:
```sh
curl -fsSL https://buckit-io.github.io/buckit/install-linux-binary.sh | sh
```
### Build From Source
Buckit requires Go 1.25 or newer. If Go is not installed, download and install
+221
View File
@@ -0,0 +1,221 @@
#!/bin/sh
# install-linux-binary.sh — Linux standalone-binary installer helper for buckit.
#
# Downloads the Linux buckit binary for the latest stable release to a
# predictable filename (buckit), verifies its published SHA-256 checksum, and
# leaves it in place so you can run it directly. It does NOT install it onto
# your PATH, and it does NOT register a systemd service.
#
# For a package-managed install with a systemd service, use install-linux.sh
# instead, which downloads the .deb/.rpm/.apk for this host.
#
# Usage:
# curl -fsSL https://buckit-io.github.io/buckit/install-linux-binary.sh | sh
# ./buckit --help # run it from where it was downloaded
#
# 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 binary 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}"
# Set by resolve_release.
TAG=""
POINTER_SHA=""
err() {
echo "install-linux-binary.sh: $*" >&2
exit 1
}
info() {
echo "==> $*"
}
# detect_platform requires Linux and sets ARCH to the Go-style arch tuple
# (amd64 / arm64) matching the published release assets.
detect_platform() {
os="$(uname -s)"
[ "$os" = "Linux" ] || err "this installer is for Linux (detected '$os'). On macOS use install-mac.sh; on Windows use install-windows.ps1"
arch="$(uname -m)"
case "$arch" in
x86_64 | amd64) ARCH="amd64" ;;
aarch64 | arm64) ARCH="arm64" ;;
*) err "unsupported architecture '$arch'. Only amd64 and arm64 builds are published." ;;
esac
}
# validate_tag rejects anything that is not a plain release identifier. Both
# the pinned BUCKIT_VERSION and the pointer-resolved tag go through this, so a
# value like '../../evil' cannot reach the download URL as path traversal.
validate_tag() {
case "$1" in
RELEASE.?*) ;;
*) err "unexpected release tag '$1' (expected RELEASE.*)" ;;
esac
# Only the characters real release tags use. Rejects '/', whitespace,
# control characters, and URL delimiters such as '?' and '#'.
case "$1" in
*[!A-Za-z0-9._-]*) err "release tag '$1' contains unsupported characters" ;;
esac
}
# normalize_sha lowercases a hex digest and requires exactly 64 hex characters,
# so a truncated or malformed checksum record can never be compared as if it
# were a valid digest. Echoes the normalized digest.
normalize_sha() {
_sha="$(printf '%s' "$1" | tr 'ABCDEF' 'abcdef')"
case "$_sha" in
"" | *[!0-9a-f]*) err "malformed sha256 digest: '$1'" ;;
esac
[ "${#_sha}" -eq 64 ] || err "malformed sha256 digest: '$1'"
printf '%s' "$_sha"
}
# 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 -> normalized hex digest on stdout. The hash utility runs on
# its own rather than inside a pipeline, so a failure surfaces instead of being
# masked by the exit status of a downstream parser.
sha256_of() {
if command -v sha256sum >/dev/null 2>&1; then
_hash_out="$(sha256sum "$1")" || err "sha256sum failed on $1"
elif command -v shasum >/dev/null 2>&1; then
_hash_out="$(shasum -a 256 "$1")" || err "shasum failed on $1"
else
err "need sha256sum or shasum to verify the download"
fi
normalize_sha "$(printf '%s\n' "$_hash_out" | awk 'NR==1{print $1}')"
}
# resolve_release sets TAG, and POINTER_SHA when the release was resolved from
# the gh-pages pointer. A pinned BUCKIT_VERSION leaves POINTER_SHA empty: the
# pointer only ever describes the latest release, so it cannot vouch for an
# arbitrary pinned version.
resolve_release() {
if [ -n "${BUCKIT_VERSION:-}" ]; then
validate_tag "$BUCKIT_VERSION"
TAG="$BUCKIT_VERSION"
POINTER_SHA=""
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="$(printf '%s\n' "$pointer" | awk 'NR==1{print $2}')"
case "$name" in
buckit.*) ;;
*) err "unexpected release pointer payload: $pointer" ;;
esac
TAG="${name#buckit.}"
validate_tag "$TAG"
POINTER_SHA="$(normalize_sha "$(printf '%s\n' "$pointer" | awk 'NR==1{print $1}')")"
}
main() {
detect_platform
info "platform: linux-$ARCH"
resolve_release
[ -n "$TAG" ] || err "could not resolve a release tag"
info "release: $TAG"
asset="buckit-linux-$ARCH.$TAG"
download_url="$RELEASE_BASE/$TAG/$asset"
dldir="${BUCKIT_DOWNLOAD_DIR:-.}"
mkdir -p "$dldir"
binfile="$dldir/buckit"
# Refuse to run when the destination is a directory. 'mv' would move the
# temp file inside it and the script would report success while leaving
# nothing runnable at the path it prints.
[ ! -d "$binfile" ] || err "$binfile is a directory — remove it or set BUCKIT_DOWNLOAD_DIR"
# Download to a temporary sibling and only move it into the predictable
# path after the checksum verifies, so a failed or interrupted download
# can never clobber an existing good binary or leave a partial/unverified
# file at the path the printed command references.
tmpfile="$(mktemp "$dldir/.buckit.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"
# Capture the payload first: piping the fetch straight into a parser would
# hide a failed transfer behind the parser's exit status.
checksum_payload="$(fetch "$download_url.sha256sum")" ||
err "could not fetch checksum at $download_url.sha256sum"
release_sha="$(normalize_sha "$(printf '%s\n' "$checksum_payload" | awk 'NR==1{print $1}')")"
# The binary and the checksum beside it come from the same origin, so that
# digest alone only proves the download was not corrupted in transit. The
# gh-pages pointer publishes the same digest from a separate origin;
# when it is available, require the two to agree before trusting either.
if [ -n "$POINTER_SHA" ]; then
if [ "$POINTER_SHA" != "$release_sha" ]; then
err "published digests disagree (pages $POINTER_SHA, release $release_sha) — refusing to continue"
fi
want_sha="$POINTER_SHA"
info "sha256 cross-checked against the release pointer"
else
want_sha="$release_sha"
fi
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"
chmod 0755 "$tmpfile"
mv -f "$tmpfile" "$binfile"
trap - EXIT
echo
echo "Downloaded and verified:"
echo " $binfile"
echo
echo "Run it from here:"
echo " \"$binfile\""
echo
echo "(Move it onto your PATH to call 'buckit' from anywhere.)"
echo
}
main "$@"