#!/usr/bin/env sh # study-server installer for Linux (no Docker required). # # Pulls the requested binaries straight out of the public GHCR "dist" image # using only curl + tar — no Docker, crane, oras, or jq needed. Works because # the package is public, so the registry serves it to an anonymous token. # # curl -fsSL https://study.bat.nz | sh # both binaries # curl -fsSL https://study.bat.nz | sh -s -- studyctl # just the client # # Env overrides: # STUDYSERVER_VERSION image tag to pull (default: latest) # STUDYSERVER_BINDIR install directory (default: /usr/local/bin) # # Source of truth: AndrewSav/study-server, install/install.sh. This is the # deployed copy served by the `study` nginx container. set -eu REGISTRY="ghcr.io" IMAGE="andrewsav/study-server-dist" TAG="${STUDYSERVER_VERSION:-latest}" DEST="${STUDYSERVER_BINDIR:-/usr/local/bin}" WHAT="${1:-both}" case "$WHAT" in studyctl | studyserver | both) ;; *) echo "usage: install.sh [studyctl|studyserver|both]" >&2; exit 2 ;; esac need() { command -v "$1" >/dev/null 2>&1 || { echo "missing required tool: $1" >&2; exit 1; }; } need curl need tar echo "Resolving ${REGISTRY}/${IMAGE}:${TAG} ..." token=$(curl -fsSL "https://${REGISTRY}/token?scope=repository:${IMAGE}:pull&service=${REGISTRY}" \ | tr ',{}' '\n' | sed -n 's/^[[:space:]]*"token":"\(.*\)"$/\1/p') [ -n "$token" ] || { echo "failed to get anonymous pull token (is the package public?)" >&2; exit 1; } # The dist image is a single-layer scratch image, so its manifest holds exactly # two digests: the config blob, then the one layer blob. Take the last. 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) [ -n "$digest" ] || { echo "could not read image manifest for tag ${TAG}" >&2; exit 1; } tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT echo "Downloading binaries ..." # The layer blob is a gzipped tar of the four binaries at its root. curl -fsSL -H "Authorization: Bearer $token" \ "https://${REGISTRY}/v2/${IMAGE}/blobs/${digest}" | tar -xzf - -C "$tmp" install_one() { name="$1" src="$tmp/${name}-linux-amd64" [ -f "$src" ] || { echo "binary not found in image: ${name}-linux-amd64" >&2; exit 1; } if [ -w "$DEST" ] || [ "$(id -u)" = "0" ]; then install -m 0755 "$src" "$DEST/$name" elif command -v sudo >/dev/null 2>&1; then sudo install -m 0755 "$src" "$DEST/$name" else echo "no write access to $DEST and no sudo; set STUDYSERVER_BINDIR to a writable dir" >&2 exit 1 fi echo "installed $DEST/$name" } case "$WHAT" in studyctl) install_one studyctl ;; studyserver) install_one studyserver ;; both) install_one studyctl; install_one studyserver ;; esac