build: auto-download Jerboa release toolchain when jerbuild is missing

ober

7d94d2c51dfc678ec4026487091fcf29f761412a

diff --git a/.gitignore b/.gitignore
index 8ca0dbe..b15eb26 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ gitsafe-macos
 gitsafe-macos.sha256
 *.so
 *.wpo
+.jerboa/
diff --git a/Makefile b/Makefile
index 1932d93..ed437c4 100644
--- a/Makefile
+++ b/Makefile
@@ -1,26 +1,43 @@
 # jerbuild bundles Chez Scheme + the jerboa stdlib, so building gitsafe needs
 # only `jerbuild` + a C compiler — no jerboa source checkout and no separately
-# built Chez.
-JERBUILD ?= jerbuild
-JH := $(shell $(JERBUILD) --jerboa-home 2>/dev/null)
-ifeq ($(JH),)
-$(error jerbuild not found on PATH (or '$(JERBUILD) --jerboa-home' failed). Install jerbuild, or set JERBUILD=/path/to/jerbuild)
-endif
-
-LIBDIRS := --libdirs $(CURDIR):$(JH)/lib
-JEXEC   := $(JERBUILD) exec $(LIBDIRS)
+# built Chez. If no jerbuild is found, `make binary` downloads the Jerboa
+# release toolchain for this os/arch into .jerboa/bin (support/ensure-jerboa.sh).
+JERBOA_VERSION ?= v0.2.0
+JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin
+JERBUILD ?= $(shell if [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ]; then echo "$(JERBOA_TOOL_DIR)/jerbuild"; \
+	elif [ -x ../jerboa/dist/jerbuild ] && [ -x ../jerboa/dist/jerboa ]; then echo ../jerboa/dist/jerbuild; \
+	elif command -v jerbuild >/dev/null 2>&1; then command -v jerbuild; \
+	else echo "$(JERBOA_TOOL_DIR)/jerbuild"; fi)
+JH = $(shell "$(JERBUILD)" --jerboa-home 2>/dev/null)
+
+LIBDIRS = --libdirs $(CURDIR):$(JH)/lib
+JEXEC   = $(JERBUILD) exec $(LIBDIRS)
 BIN     := gitsafe-bin
 BIN_DIR := $(HOME)/.local/bin
 TEMPLATE_DIR := $(HOME)/.git-templates
 HOOK_DIR := $(TEMPLATE_DIR)/hooks
 
-.PHONY: all build binary run test install clean help
+.PHONY: all build binary run test install clean help ensure-jerboa-tools
 .DEFAULT_GOAL := help
 
 all: binary
 
+# Make sure a working jerbuild exists; download the release toolchain for
+# this os/arch into $(JERBOA_TOOL_DIR) when none is found.
+ensure-jerboa-tools:
+	@if "$(JERBUILD)" --jerboa-home >/dev/null 2>&1; then \
+	  echo "=== Using Jerboa toolchain: $(JERBUILD) ==="; \
+	else \
+	  echo "=== Fetching Jerboa $(JERBOA_VERSION) release tools into $(JERBOA_TOOL_DIR) ==="; \
+	  sh support/ensure-jerboa.sh "$(JERBOA_VERSION)" "$(JERBOA_TOOL_DIR)"; \
+	fi
+	@"$(JERBUILD)" --jerboa-home >/dev/null || { \
+	  echo "ERROR: Jerboa toolchain is unavailable; set JERBUILD=/path/to/jerbuild or JERBOA_VERSION=<tag>" >&2; \
+	  exit 1; \
+	}
+
 # Standalone native binary via .jerbuild (entry gitsafe/main-binary.ss).
-binary:
+binary: ensure-jerboa-tools
 	$(JERBUILD) build
 
 build: binary
@@ -28,7 +45,7 @@ build: binary
 run: binary
 	./$(BIN) $(ARGS)
 
-test:
+test: ensure-jerboa-tools
 	$(JEXEC) test/test-gitsafe.ss
 
 install: binary
@@ -49,7 +66,7 @@ clean:
 help:
 	@echo "gitsafe — secret-scanning git hooks (jerbuild + cc only)"
 	@echo ""
-	@echo "  make binary           Build the standalone ./gitsafe-bin"
+	@echo "  make binary           Build the standalone ./gitsafe-bin (auto-downloads jerbuild)"
 	@echo "  make run ARGS='...'    Build + run ./gitsafe-bin"
 	@echo "  make test             Run the test suite"
 	@echo "  make install          Build + install to ~/.local/bin + global git hooks"
diff --git a/README.md b/README.md
index 3c8c01b..7dedee9 100644
--- a/README.md
+++ b/README.md
@@ -7,23 +7,19 @@ A fast, zero-dependency git hook that blocks commits and pushes containing leake
 ```bash
 git clone https://git.sr.ht/~lisp/jerboa-gitsafe
 cd jerboa-gitsafe
-make install
+make binary install
 ```
 
-This builds a fully static binary via Docker and installs it to `~/.local/bin/gitsafe`, then configures global git hooks so every new repo is protected automatically.
+This builds a native binary and installs it to `~/.local/bin/gitsafe`, then configures global git hooks so every new repo is protected automatically. No Jerboa checkout needed: if `jerbuild` isn't already installed, the build downloads the prebuilt Jerboa toolchain for your os/arch into `.jerboa/bin` (macOS arm64, Linux amd64/arm64, FreeBSD amd64). The only requirement is a C compiler.
 
 ## Install
 
-### From Docker (recommended)
-
-Requires only Docker. No Chez Scheme, no Jerboa, no compiler toolchain.
-
 ```bash
 make install
 ```
 
-This runs `make linux` (Docker build) and then:
-- Copies the static binary to `~/.local/bin/gitsafe`
+This builds the binary (fetching the Jerboa toolchain on first run if needed) and then:
+- Copies the binary to `~/.local/bin/gitsafe`
 - Creates pre-commit and pre-push hooks in `~/.git-templates/hooks/`
 - Sets `git config --global init.templateDir ~/.git-templates`
 
@@ -37,10 +33,11 @@ export PATH="$HOME/.local/bin:$PATH"
 ### Build only (no install)
 
 ```bash
-make linux                 # Docker build → ./gitsafe-musl
-make linux-local           # Local build (requires musl-gcc + musl Chez)
+make binary                # Native build → ./gitsafe-bin
 ```
 
+To pin a toolchain explicitly: `make binary JERBUILD=/path/to/jerbuild` or `JERBOA_VERSION=<tag>`.
+
 ## Setting Up Existing Repos
 
 `make install` configures git's global template directory, which applies to all **new** repos (`git init` / `git clone`). To add gitsafe hooks to repos that already exist, you have two options:
@@ -296,12 +293,9 @@ Options:
 ## Make Targets
 
 ```
-make linux                  Docker build (canonical, reproducible)
-make linux-local            Local build (requires musl-gcc + musl Chez)
-make install                Docker build + install to ~/.local/bin + global hooks
-make verify-harden          Verify binary hardening (stripped, no path leaks)
-make binary                 Native build (requires local Chez + Jerboa)
-make run ARGS='...'         Run in interpreter mode (development)
+make binary                 Native build → ./gitsafe-bin (auto-downloads jerbuild)
+make install                Build + install to ~/.local/bin + global hooks
+make run ARGS='...'         Build + run ./gitsafe-bin
 make test                   Run test suite
 make clean                  Remove all build artifacts
 ```
diff --git a/support/ensure-jerboa.sh b/support/ensure-jerboa.sh
new file mode 100755
index 0000000..e96aed7
--- /dev/null
+++ b/support/ensure-jerboa.sh
@@ -0,0 +1,121 @@
+#!/bin/sh
+# Bootstrap a project-local Jerboa toolchain from SourceHut release artifacts.
+#
+# Usage:
+#   support/ensure-jerboa.sh v0.2.0 .jerboa/bin
+#
+# Override the artifact location with:
+#   JERBOA_RELEASE_BASE=https://example.org/releases/v0.2.0
+# or the SourceHut repo with:
+#   JERBOA_RELEASE_REPO=~lisp/jerboa
+# For testing or unusual hosts, override target detection with:
+#   JERBOA_RELEASE_TARGET=macos-arm64
+
+set -eu
+
+usage() {
+    echo "Usage: $0 VERSION [BINDIR]" >&2
+}
+
+[ "${1:-}" ] || { usage; exit 2; }
+
+version=$1
+bindir=${2:-.jerboa/bin}
+repo=${JERBOA_RELEASE_REPO:-~lisp/jerboa}
+origin=${JERBOA_RELEASE_ORIGIN:-https://git.sr.ht}
+
+if [ "${JERBOA_RELEASE_TARGET:-}" ]; then
+    target=$JERBOA_RELEASE_TARGET
+else
+    os=$(uname -s)
+    arch=$(uname -m)
+    case "$os-$arch" in
+        Darwin-arm64) target=macos-arm64 ;;
+        Linux-x86_64|Linux-amd64) target=linux-amd64 ;;
+        Linux-aarch64|Linux-arm64) target=linux-arm64 ;;
+        FreeBSD-amd64|FreeBSD-x86_64) target=freebsd-amd64 ;;
+        *)
+            echo "ERROR: unsupported platform for Jerboa release artifacts: $os $arch" >&2
+            echo "       set JERBUILD=/path/to/jerbuild or build Jerboa locally for this host" >&2
+            exit 1
+            ;;
+    esac
+fi
+
+case "$target" in
+    macos-arm64|linux-amd64|linux-arm64|freebsd-amd64) ;;
+    *)
+        echo "ERROR: unsupported release target: $target" >&2
+        exit 1
+        ;;
+esac
+
+file="jerboa-${version}-${target}.tar.gz"
+base=${JERBOA_RELEASE_BASE:-${origin}/${repo}/refs/download/${version}}
+url="${base%/}/${file}"
+sum_url="${url}.sha256"
+
+download() {
+    src=$1
+    dst=$2
+    if command -v curl >/dev/null 2>&1; then
+        curl -fsSL "$src" -o "$dst"
+    elif command -v fetch >/dev/null 2>&1; then
+        fetch -q -o "$dst" "$src"
+    elif command -v wget >/dev/null 2>&1; then
+        wget -q -O "$dst" "$src"
+    else
+        echo "ERROR: need curl, fetch, or wget to download Jerboa" >&2
+        exit 1
+    fi
+}
+
+sha256_file() {
+    path=$1
+    if command -v sha256sum >/dev/null 2>&1; then
+        sha256sum "$path" | awk '{print $1}'
+    elif command -v shasum >/dev/null 2>&1; then
+        shasum -a 256 "$path" | awk '{print $1}'
+    elif command -v sha256 >/dev/null 2>&1; then
+        sha256 -q "$path"
+    else
+        echo "ERROR: need sha256sum, shasum, or sha256" >&2
+        exit 1
+    fi
+}
+
+tmp=$(mktemp -d "${TMPDIR:-/tmp}/jerboa-bootstrap.XXXXXX")
+trap 'rm -rf "$tmp"' EXIT HUP INT TERM
+
+archive="$tmp/$file"
+sum_file="$tmp/$file.sha256"
+
+echo "fetching $url"
+download "$url" "$archive"
+download "$sum_url" "$sum_file"
+
+expected=$(awk '{print $1; exit}' "$sum_file")
+actual=$(sha256_file "$archive")
+if [ "$expected" != "$actual" ]; then
+    echo "ERROR: checksum mismatch for $file" >&2
+    echo "expected: $expected" >&2
+    echo "actual:   $actual" >&2
+    exit 1
+fi
+
+mkdir -p "$tmp/extract"
+tar -xzf "$archive" -C "$tmp/extract"
+root="$tmp/extract/jerboa-${version}-${target}"
+[ -x "$root/bin/jerboa" ] || {
+    echo "ERROR: archive did not contain bin/jerboa" >&2
+    exit 1
+}
+
+mkdir -p "$bindir"
+cp "$root/bin/jerboa" "$bindir/jerboa"
+chmod 0755 "$bindir/jerboa"
+for link in jmcp jlsp jerbuild jpkg; do
+    ln -sf jerboa "$bindir/$link"
+done
+
+echo "installed $bindir/{jerboa,jmcp,jlsp,jerbuild,jpkg}"