diff --git a/wrappers/crucible.sh b/wrappers/crucible.sh new file mode 100755 index 0000000..cae4637 --- /dev/null +++ b/wrappers/crucible.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# crucible — bootstrap wrapper (POSIX/bash). Prefers a crucible already on PATH +# (Nix devshell / installed); otherwise downloads the latest released binary +# from Gitea into a per-version cache and execs it. +# +# CANONICAL SOURCE: sow-tools/wrappers/crucible.sh. Do not hand-edit copies in +# consumer repos — they are kept in sync by sow-tools CI (see drift-check). +# +# Requires: bash, curl, and sha256sum (Linux) or shasum (macOS). +# Env: CRUCIBLE_HOME (cache dir), CRUCIBLE_TOKEN (private releases), +# CRUCIBLE_GITEA (default https://git.westgate.pw), +# CRUCIBLE_REPO (default ShadowsOverWestgate/sow-tools). +set -euo pipefail + +# 0. Prefer an existing crucible (Nix users, or already installed). +if command -v crucible >/dev/null 2>&1; then + exec crucible "$@" +fi + +gitea="${CRUCIBLE_GITEA:-https://git.westgate.pw}" +repo="${CRUCIBLE_REPO:-ShadowsOverWestgate/sow-tools}" + +# 1. --repo-local materializes into /.crucible instead of the user cache. +repo_local=0 +if [ "${1:-}" = "--repo-local" ]; then repo_local=1; shift; fi + +# 2. OS/arch -> asset name. +os="$(uname -s)"; arch="$(uname -m)" +case "$os" in + Linux) os=linux ;; + Darwin) os=darwin ;; + *) echo "crucible: unsupported OS '$os' — install ffmpeg-free crucible manually" >&2; exit 1 ;; +esac +case "$arch" in + x86_64|amd64) arch=amd64 ;; + aarch64|arm64) arch=arm64 ;; + *) echo "crucible: unsupported arch '$arch'" >&2; exit 1 ;; +esac +asset="crucible-${os}-${arch}" + +# 3. Auth: anonymous first, token fallback. +token="${CRUCIBLE_TOKEN:-}" +if [ -z "$token" ] && [ -f "$HOME/.config/crucible/token" ]; then + token="$(cat "$HOME/.config/crucible/token")" +fi +fetch() { # fetch URL DEST ; tries anon then token + if curl -fsSL "$1" -o "$2" 2>/dev/null; then return 0; fi + if [ -n "$token" ]; then curl -fsSL -H "Authorization: token $token" "$1" -o "$2"; return $?; fi + return 1 +} +fetch_stdout() { # prints body; anon then token + if curl -fsSL "$1" 2>/dev/null; then return 0; fi + if [ -n "$token" ]; then curl -fsSL -H "Authorization: token $token" "$1"; return $?; fi + return 1 +} + +# 4. Resolve the latest release tag (no jq dependency). +api="${gitea}/api/v1/repos/${repo}/releases/latest" +tag="$(fetch_stdout "$api" 2>/dev/null | grep -o '"tag_name":"[^"]*"' | head -1 | cut -d'"' -f4 || true)" + +# 5. Cache dir. +if [ "$repo_local" = 1 ]; then + cache_root="$(cd "$(dirname "$0")" && pwd)/.crucible" +else + cache_root="${CRUCIBLE_HOME:-${XDG_CACHE_HOME:-$HOME/.cache}/crucible}" +fi + +# 6. Offline fallback: if tag lookup failed, use the newest cached version. +if [ -z "$tag" ]; then + if [ -d "$cache_root" ]; then + cached="$(ls -1 "$cache_root" 2>/dev/null | sort -V | tail -1 || true)" + if [ -n "$cached" ] && [ -x "$cache_root/$cached/crucible" ]; then + echo "crucible: offline — using cached $cached" >&2 + exec "$cache_root/$cached/crucible" "$@" + fi + fi + echo "crucible: cannot reach $gitea and no cached binary found." >&2 + echo " set CRUCIBLE_TOKEN if releases are private, or check the network." >&2 + exit 1 +fi + +dest_dir="$cache_root/$tag" +bin="$dest_dir/crucible" + +# 7. Download + verify on cache miss. +if [ ! -x "$bin" ]; then + mkdir -p "$dest_dir" + base="${gitea}/${repo}/releases/download/${tag}" + tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT + echo "crucible: fetching ${asset} ${tag}..." >&2 + fetch "${base}/${asset}" "$tmp/crucible" || { echo "crucible: download failed for ${asset}" >&2; exit 1; } + if fetch "${base}/SHA256SUMS" "$tmp/SHA256SUMS"; then + want="$(grep " ${asset}\$" "$tmp/SHA256SUMS" | awk '{print $1}')" + if command -v sha256sum >/dev/null 2>&1; then + got="$(sha256sum "$tmp/crucible" | awk '{print $1}')" + else + got="$(shasum -a 256 "$tmp/crucible" | awk '{print $1}')" + fi + if [ -n "$want" ] && [ "$want" != "$got" ]; then + echo "crucible: checksum mismatch for ${asset} (want $want got $got)" >&2; exit 1 + fi + fi + chmod +x "$tmp/crucible" + mv "$tmp/crucible" "$bin" +fi + +exec "$bin" "$@"