#!/bin/sh # Install sptool on Linux. # # sh install.sh # latest, into /usr/local/bin # SPTOOL_BINDIR=~/bin sh install.sh # somewhere you own (no sudo) # SPTOOL_VERSION=0.0.1 sh install.sh # a specific release # # The SupervisorTool repository is private, so its release assets need # credentials. The same binaries are also published as a single-layer scratch # image on GHCR, and that package is public — this script pulls the layer with # nothing but curl and tar. After the first install, `sptool update` does the # same thing without needing this script. set -eu REGISTRY=ghcr.io IMAGE=andrewsav/supervisor-tool-dist VERSION="${SPTOOL_VERSION:-latest}" DEST="${SPTOOL_BINDIR:-/usr/local/bin}" case "$VERSION" in latest) TAG=latest ;; v*) TAG="$VERSION" ;; *) TAG="v$VERSION" ;; esac need() { command -v "$1" >/dev/null 2>&1 || { echo "install: $1 is required but not installed" >&2; exit 1; } } need curl need tar # Whichever sha256 tool this host has. Returns non-zero if it has none, which is # a hard failure below rather than a silently skipped check. sha256_of() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1 elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1 elif command -v openssl >/dev/null 2>&1; then openssl dgst -sha256 "$1" | awk '{print $NF}' else return 1 fi } os=$(uname -s | tr '[:upper:]' '[:lower:]') if [ "$os" != linux ]; then echo "install: sptool has no $os build (linux and windows only)" >&2 exit 1 fi case "$(uname -m)" in x86_64|amd64) arch=amd64 ;; aarch64|arm64) arch=arm64 ;; *) echo "install: unsupported architecture $(uname -m) (amd64 and arm64 only)" >&2; exit 1 ;; esac member="sptool-${os}-${arch}" tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT echo "fetching sptool ${TAG} for ${os}/${arch} from ${REGISTRY}/${IMAGE}" # Anonymous pull token. Works only because the package is public. token=$(curl -fsSL "https://${REGISTRY}/token?scope=repository:${IMAGE}:pull&service=${REGISTRY}" \ | tr ',{}' '\n' | sed -n 's/^[[:space:]]*"token":"\(.*\)"$/\1/p') if [ -z "$token" ]; then echo "install: could not get a pull token — is ${REGISTRY}/${IMAGE} public?" >&2 exit 1 fi # The manifest lists the config blob first and the layers after it, so the last # sha256 in the document is the layer holding the binaries. This only holds # because the release workflow publishes a plain single-platform image. digest=$(curl -fsSL \ -H "Authorization: Bearer $token" \ -H 'Accept: application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.v2+json' \ "https://${REGISTRY}/v2/${IMAGE}/manifests/${TAG}" \ | grep -o 'sha256:[0-9a-f]\{64\}' | tail -n1) if [ -z "$digest" ]; then echo "install: no layer digest for tag ${TAG} — does that release exist?" >&2 exit 1 fi curl -fsSL -H "Authorization: Bearer $token" \ "https://${REGISTRY}/v2/${IMAGE}/blobs/${digest}" -o "$tmp/layer.tgz" # Verify the download against the digest the manifest named, before unpacking it. # This is not optional: the blob GET is redirected to a CDN host that curl (quite # correctly) does not send the pull token to, so the digest is the only thing # binding those bytes to the registry. No hasher means no install. want=${digest#sha256:} if ! got=$(sha256_of "$tmp/layer.tgz"); then echo "install: need sha256sum, shasum, or openssl to verify the download" >&2 exit 1 fi if [ "$want" != "$got" ]; then echo "install: layer digest mismatch (manifest $want, downloaded $got)" >&2 exit 1 fi tar -xzf "$tmp/layer.tgz" -C "$tmp" if [ ! -f "$tmp/$member" ]; then echo "install: $member is not in the published image" >&2 exit 1 fi if [ -w "$DEST" ] || { [ ! -e "$DEST" ] && [ -w "$(dirname "$DEST")" ]; }; then mkdir -p "$DEST" install -m 0755 "$tmp/$member" "$DEST/sptool" elif [ "$(id -u)" != 0 ] && command -v sudo >/dev/null 2>&1; then echo "install: $DEST needs root; escalating with sudo" sudo mkdir -p "$DEST" sudo install -m 0755 "$tmp/$member" "$DEST/sptool" else echo "install: cannot write to $DEST — re-run as root, or set SPTOOL_BINDIR to a directory you own" >&2 exit 1 fi echo "installed $("$DEST/sptool" version) to $DEST/sptool" case ":$PATH:" in *":$DEST:"*) ;; *) echo "note: $DEST is not on your PATH" ;; esac