Add base Jerboa installer

ober

9c1cbce8093f4e0160bee6bee260b8243309f4ea

diff --git a/docs/release-artifacts.md b/docs/release-artifacts.md
index 818662e..11c9959 100644
--- a/docs/release-artifacts.md
+++ b/docs/release-artifacts.md
@@ -22,6 +22,40 @@ jerboa-vX.Y.Z-<target>/bin/jmcp     -> jerboa
 jerboa-vX.Y.Z-<target>/bin/jlsp     -> jerboa
 ```
 
+## End-User Install
+
+Install the base Jerboa toolchain with:
+
+```sh
+curl -fsSL https://git.sr.ht/~lisp/jerboa/blob/master/support/install.sh | bash
+```
+
+That installs `jerboa`, `jmcp`, `jlsp`, `jerbuild`, and `jpkg` into
+`$HOME/.local/bin` by default. After that, use `jpkg` to install additional
+binary packages from package repos.
+
+The installer defaults to the newest `vX.Y.Z` SourceHut tag. Pin a release
+with:
+
+```sh
+curl -fsSL https://git.sr.ht/~lisp/jerboa/blob/master/support/install.sh \
+  | JERBOA_VERSION=v0.2.3 bash
+```
+
+Install somewhere else with:
+
+```sh
+curl -fsSL https://git.sr.ht/~lisp/jerboa/blob/master/support/install.sh \
+  | PREFIX=/usr/local bash
+```
+
+or:
+
+```sh
+curl -fsSL https://git.sr.ht/~lisp/jerboa/blob/master/support/install.sh \
+  | JERBOA_INSTALL_DIR="$HOME/bin" bash
+```
+
 ## Build Locally
 
 ```sh
diff --git a/support/install.sh b/support/install.sh
new file mode 100755
index 0000000..776ae76
--- /dev/null
+++ b/support/install.sh
@@ -0,0 +1,262 @@
+#!/bin/sh
+# Install the base Jerboa toolchain from SourceHut release artifacts.
+#
+# End-user install:
+#   curl -fsSL https://git.sr.ht/~lisp/jerboa/blob/master/support/install.sh | bash
+#
+# This installs the Jerboa multicall binary plus the jmcp, jlsp, jerbuild, and
+# jpkg command links. Use jpkg after this to install additional binary packages.
+
+set -eu
+
+usage() {
+    cat >&2 <<'EOF'
+Usage: install.sh [--version VERSION] [--bindir DIR] [--prefix DIR]
+
+Installs the base Jerboa toolchain from SourceHut release artifacts.
+
+Options:
+  --version VERSION   Install a tag such as v0.2.3, or latest
+  --bindir DIR        Install commands directly into DIR
+  --prefix DIR        Install commands into DIR/bin
+  -h, --help          Show this help
+
+Environment:
+  JERBOA_VERSION         Version tag to install, or latest (default: latest)
+  JERBOA_INSTALL_DIR     Bin directory to install into (default: $HOME/.local/bin)
+  PREFIX                 Prefix used when JERBOA_INSTALL_DIR is unset
+  JERBOA_RELEASE_REPO    SourceHut repo path (default: ~lisp/jerboa)
+  JERBOA_RELEASE_ORIGIN  SourceHut origin (default: https://git.sr.ht)
+  JERBOA_RELEASE_BASE    Complete release base URL override
+  JERBOA_RELEASE_TARGET  Target override, e.g. linux-amd64
+EOF
+}
+
+error() {
+    echo "ERROR: $*" >&2
+    exit 1
+}
+
+need_value() {
+    [ "${2:-}" ] || error "$1 requires a value"
+}
+
+version=${JERBOA_VERSION:-latest}
+bindir=${JERBOA_INSTALL_DIR:-}
+prefix=${PREFIX:-}
+
+while [ "$#" -gt 0 ]; do
+    case "$1" in
+        --version)
+            need_value "$1" "${2:-}"
+            version=$2
+            shift 2
+            ;;
+        --bindir)
+            need_value "$1" "${2:-}"
+            bindir=$2
+            shift 2
+            ;;
+        --prefix)
+            need_value "$1" "${2:-}"
+            prefix=$2
+            shift 2
+            ;;
+        -h|--help)
+            usage
+            exit 0
+            ;;
+        *)
+            error "unknown option: $1"
+            ;;
+    esac
+done
+
+if [ -z "$bindir" ]; then
+    if [ -n "$prefix" ]; then
+        bindir="${prefix%/}/bin"
+    elif [ -n "${HOME:-}" ]; then
+        bindir="$HOME/.local/bin"
+    else
+        error "HOME is unset; set JERBOA_INSTALL_DIR or PREFIX"
+    fi
+fi
+
+repo=${JERBOA_RELEASE_REPO:-~lisp/jerboa}
+origin=${JERBOA_RELEASE_ORIGIN:-https://git.sr.ht}
+repo_url="${origin%/}/$repo"
+
+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
+        error "need curl, fetch, or wget to download Jerboa"
+    fi
+}
+
+download_stdout() {
+    src=$1
+    if command -v curl >/dev/null 2>&1; then
+        curl -fsSL "$src"
+    elif command -v fetch >/dev/null 2>&1; then
+        fetch -q -o - "$src"
+    elif command -v wget >/dev/null 2>&1; then
+        wget -q -O - "$src"
+    else
+        error "need curl, fetch, or wget to download Jerboa"
+    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
+        error "need sha256sum, shasum, or sha256"
+    fi
+}
+
+highest_version() {
+    awk '
+        $0 ~ /^v[0-9]+\.[0-9]+\.[0-9]+$/ {
+            split(substr($0, 2), part, ".")
+            major = part[1] + 0
+            minor = part[2] + 0
+            patch = part[3] + 0
+            if (!seen ||
+                major > best_major ||
+                (major == best_major && minor > best_minor) ||
+                (major == best_major && minor == best_minor && patch > best_patch)) {
+                seen = 1
+                best = $0
+                best_major = major
+                best_minor = minor
+                best_patch = patch
+            }
+        }
+        END {
+            if (seen) {
+                print best
+            } else {
+                exit 1
+            }
+        }
+    '
+}
+
+resolve_latest_version() {
+    if command -v git >/dev/null 2>&1; then
+        git ls-remote --tags "$repo_url" 'v[0-9]*' \
+            | awk '$2 ~ /^refs\/tags\/v[0-9]+\.[0-9]+\.[0-9]+$/ { sub(/^refs\/tags\//, "", $2); print $2 }' \
+            | highest_version
+    else
+        download_stdout "${repo_url}/refs" \
+            | sed -n 's|.*href="[^"]*/refs/\(v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)".*|\1|p' \
+            | highest_version
+    fi
+}
+
+detect_target() {
+    if [ "${JERBOA_RELEASE_TARGET:-}" ]; then
+        echo "$JERBOA_RELEASE_TARGET"
+        return
+    fi
+
+    os=$(uname -s)
+    arch=$(uname -m)
+    case "$os-$arch" in
+        Darwin-arm64) echo macos-arm64 ;;
+        Linux-x86_64|Linux-amd64) echo linux-amd64 ;;
+        Linux-aarch64|Linux-arm64) echo linux-arm64 ;;
+        FreeBSD-amd64|FreeBSD-x86_64) echo freebsd-amd64 ;;
+        *) error "unsupported platform: $os $arch" ;;
+    esac
+}
+
+install_links() {
+    for link in jmcp jlsp jerbuild jpkg; do
+        ln -sf jerboa "$bindir/$link"
+    done
+}
+
+if [ "$version" = latest ]; then
+    version=$(resolve_latest_version) || error "could not resolve latest Jerboa release"
+fi
+
+case "$version" in
+    v[0-9]*.[0-9]*.[0-9]*) ;;
+    *) error "version must be latest or a tag like v0.2.3" ;;
+esac
+
+target=$(detect_target)
+case "$target" in
+    macos-arm64|linux-amd64|linux-arm64|freebsd-amd64) ;;
+    *) error "unsupported release target: $target" ;;
+esac
+
+if [ -x "$bindir/jerboa" ]; then
+    installed=$("$bindir/jerboa" --version 2>/dev/null || true)
+    case "$installed" in
+        *"${version#v}"*)
+            mkdir -p "$bindir"
+            install_links
+            echo "Jerboa $version is already installed in $bindir"
+            exit 0
+            ;;
+    esac
+fi
+
+file="jerboa-${version}-${target}.tar.gz"
+base=${JERBOA_RELEASE_BASE:-${repo_url}/refs/download/${version}}
+url="${base%/}/${file}"
+sum_url="${url}.sha256"
+
+tmp=$(mktemp -d "${TMPDIR:-/tmp}/jerboa-install.XXXXXX")
+trap 'rm -rf "$tmp"' EXIT HUP INT TERM
+
+archive="$tmp/$file"
+sum_file="$tmp/$file.sha256"
+
+echo "Installing Jerboa $version for $target"
+echo "Downloading $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" ] || error "archive did not contain bin/jerboa"
+
+mkdir -p "$bindir"
+cp "$root/bin/jerboa" "$bindir/jerboa"
+chmod 0755 "$bindir/jerboa"
+install_links
+
+echo "Installed Jerboa $version to $bindir"
+echo "Installed commands: jerboa jmcp jlsp jerbuild jpkg"
+
+case ":${PATH:-}:" in
+    *":$bindir:"*) ;;
+    *)
+        echo "Add $bindir to PATH before running jerboa or jpkg."
+        ;;
+esac