Add cross-platform build targets

ober

106d38abdb78e71d7583075b96fea3f6bd2b2165

diff --git a/.jerbuild.freebsd-amd64 b/.jerbuild.freebsd-amd64
new file mode 100644
index 0000000..a8353e7
--- /dev/null
+++ b/.jerbuild.freebsd-amd64
@@ -0,0 +1,46 @@
+;; jerbuild build config for jerboa-shell (jsh) — native FreeBSD amd64.
+;;
+;; Build on a FreeBSD amd64 host with:
+;;   jerbuild build --config .jerbuild.freebsd-amd64
+;;
+;; This uses only the installed jerbuild bundle, cc, and cargo. It deliberately
+;; has no source-checkout, cross-compiler, or absolute runtime-path dependency.
+
+(entry  "jsh-generated.ss")
+(output "jsh-freebsd")
+
+(cc "cc")
+
+;; FreeBSD libc has dlopen, no separate -ldl. -lutil is for openpty.
+;; Chez's expeditor needs termcap, and the packaged Chez kernel references
+;; GNU libiconv from /usr/local/lib.
+(os-libs "-lm -lpthread -lutil -ltermcap -L/usr/local/lib -liconv")
+
+(libdirs "_jerbuild-stage"   ; stdlib patches — must shadow bundle copies
+         "src"
+         "vendor/jerboa-coreutils/lib"
+         "vendor/jerboa-awk/lib"
+         "vendor/jerboa-sed/lib"
+         "vendor/jerboa-aws/lib"
+         "../jerboa-wormhole"
+         "vendor/jerboa-yubikey/lib"
+         "vendor/jerboa-ssh/lib"
+         "vendor/jerboa-fuse/lib")
+
+;; FFI shim — same shim used by the macOS + Linux builds. Embed crypto symbols
+;; come from the staged jerboa-native-rs archive.
+(extra-sources "ffi-shim.c")
+
+;; Rust archive staged by support/stage-for-jerbuild-freebsd.sh. No coreutils
+;; archive is linked on FreeBSD yet; freebsd-main.c provides weak stubs.
+(extra-archives
+  "_jerbuild-stage/libjerboa_native.a"
+  "_jerbuild-stage/libjerboa_ssh.a"
+  "_jerbuild-stage/libjerboa_fuse.a")
+
+(extra-ldflags "-Wl,--export-dynamic")
+
+;; FreeBSD-specific main.c generated by the pre-build hook.
+(main-c "_jerbuild-stage/freebsd-main.c")
+
+(pre-build "support/stage-for-jerbuild-freebsd.sh")
diff --git a/.jerbuild.linux-amd64 b/.jerbuild.linux-amd64
new file mode 100644
index 0000000..33b9335
--- /dev/null
+++ b/.jerbuild.linux-amd64
@@ -0,0 +1,47 @@
+;; jerbuild build config for jerboa-shell (jsh) — native Linux amd64.
+;;
+;; Build on a Linux amd64 host with:
+;;   jerbuild build --config .jerbuild.linux-amd64
+;;
+;; This intentionally uses only the installed jerbuild bundle, cc, and cargo.
+;; There is no Jerboa source checkout, cross compiler, or absolute runtime path.
+
+(entry  "jsh-generated.ss")
+(output "jsh-linux-amd64")
+
+(cc "cc")
+
+(os-libs "-lm -ldl -lpthread -ltinfo")
+
+(libdirs "_jerbuild-stage"   ; stdlib patches — must shadow bundle copies
+         "src"
+         "vendor/jerboa-coreutils/lib"
+         "vendor/jerboa-awk/lib"
+         "vendor/jerboa-sed/lib"
+         "vendor/jerboa-aws/lib"
+         "../jerboa-wormhole"
+         "vendor/jerboa-yubikey/lib"
+         "vendor/jerboa-ssh/lib"
+         "vendor/jerboa-fuse/lib")
+
+;; FFI shim — same shim used by the macOS build. Embed crypto symbols come
+;; from the staged jerboa-native-rs archive.
+(extra-sources "ffi-shim.c")
+
+;; Rust archives staged by support/stage-for-jerbuild-linux.sh.
+(extra-archives
+  "_jerbuild-stage/libjerboa_native.a"
+  "_jerbuild-stage/libjsh_coreutils.a"
+  "_jerbuild-stage/libjerboa_ssh.a"
+  "_jerbuild-stage/libjerboa_fuse.a")
+
+;; --export-dynamic + Sforeign_symbol registrations in main.c are how Chez
+;; foreign-procedure finds symbols. --allow-multiple-definition tolerates
+;; duplicate Rust runtime symbols across the two static archives.
+(extra-ldflags "-Wl,--export-dynamic"
+               "-Wl,--allow-multiple-definition")
+
+;; Linux-specific main.c is generated by the pre-build hook.
+(main-c "_jerbuild-stage/linux-main.c")
+
+(pre-build "support/stage-for-jerbuild-linux.sh")
diff --git a/.jerbuild.linux-amd64-native b/.jerbuild.linux-amd64-native
new file mode 100644
index 0000000..34b83fc
--- /dev/null
+++ b/.jerbuild.linux-amd64-native
@@ -0,0 +1,35 @@
+;; Compatibility alias for older local commands. The canonical native Linux
+;; config is .jerbuild.linux-amd64.
+
+(entry  "jsh-generated.ss")
+(output "jsh-linux-amd64")
+
+(cc "cc")
+
+(os-libs "-lm -ldl -lpthread -ltinfo")
+
+(libdirs "_jerbuild-stage"
+         "src"
+         "vendor/jerboa-coreutils/lib"
+         "vendor/jerboa-awk/lib"
+         "vendor/jerboa-sed/lib"
+         "vendor/jerboa-aws/lib"
+         "../jerboa-wormhole"
+         "vendor/jerboa-yubikey/lib"
+         "vendor/jerboa-ssh/lib"
+         "vendor/jerboa-fuse/lib")
+
+(extra-sources "ffi-shim.c")
+
+(extra-archives
+  "_jerbuild-stage/libjerboa_native.a"
+  "_jerbuild-stage/libjsh_coreutils.a"
+  "_jerbuild-stage/libjerboa_ssh.a"
+  "_jerbuild-stage/libjerboa_fuse.a")
+
+(extra-ldflags "-Wl,--export-dynamic"
+               "-Wl,--allow-multiple-definition")
+
+(main-c "_jerbuild-stage/linux-main.c")
+
+(pre-build "support/stage-for-jerbuild-linux.sh")
diff --git a/Makefile b/Makefile
index 6833707..30a21ef 100644
--- a/Makefile
+++ b/Makefile
@@ -15,6 +15,35 @@ JERBUILD ?= $(shell if [ -x ./jerbuild ]; then echo ./jerbuild; \
 	else echo "$(JERBOA_TOOL_DIR)/jerbuild"; fi)
 JH = $(shell "$(JERBUILD)" --jerboa-home 2>/dev/null)
 
+ifneq ($(JERBOA_FEATURES),)
+JSH_FEATURES ?= $(JERBOA_FEATURES)
+export JSH_FEATURES
+endif
+JSH_OUTPUT ?= jsh-macos
+VENDOR ?= $(CURDIR)/vendor
+COREUTILS ?= $(VENDOR)/jerboa-coreutils/lib
+JERBOA_AWK ?= $(VENDOR)/jerboa-awk/lib
+JERBOA_SED ?= $(VENDOR)/jerboa-sed/lib
+JERBOA_AWS ?= $(VENDOR)/jerboa-aws/lib
+JERBOA_WORMHOLE ?= $(VENDOR)/jerboa-wormhole
+JERBOA_NATIVE_RS_DIR ?= $(VENDOR)/jerboa-native-rs
+JERBOA_NATIVE_HOST_LIB ?= $(JERBOA_NATIVE_RS_DIR)/target/release/libjerboa_native$(SO_EXT)
+JSH_NATIVE_NO_SQLITE_FEATURES ?= tls crypto
+JERBOA_NATIVE_HOST_FEATURES ?= $(JSH_NATIVE_NO_SQLITE_FEATURES)
+JSH_CROSS_FEATURES ?= coreutils
+JERBOA_SSH_REPO ?= $(VENDOR)/jerboa-ssh
+JSQLITE_REPO ?= $(VENDOR)/jsqlite
+JSQLITE_URL ?= https://git.sr.ht/~lisp/jsqlite
+JSQLITE_DIR ?= $(JSQLITE_REPO)/src
+JSQLITE_STAGE ?= _jerbuild-stage/jsqlite
+JERBOA_CRYPTO_REPO ?= $(VENDOR)/jerboa-crypto
+JERBOA_FUSE_REPO ?= $(VENDOR)/jerboa-fuse
+JERBOA_YUBIKEY_REPO ?= $(VENDOR)/jerboa-yubikey
+JERBOA_YUBIKEY_LIB ?= $(JERBOA_YUBIKEY_REPO)/lib
+VENDOR_STAGED_LIBS_SCRIPT ?= tools/stage-vendor-libs.sh
+VENDOR_OVERRIDES_SCRIPT ?= tools/apply-vendor-overrides.sh
+JSH_EMBED ?= $(shell if [ -d $(HOME)/.embed ]; then echo $(HOME)/.embed; else echo embed; fi)
+
 UNAME_S := $(shell uname -s)
 HOST_ARCH := $(shell uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
 ifeq ($(UNAME_S),Darwin)
@@ -31,6 +60,8 @@ endif
 FFI_LIB = libjsh-ffi$(SO_EXT)
 
 LIBDIRS = src:$(JH)/lib
+LIBDIRS_JSH = _jerbuild-stage:src:$(COREUTILS):$(JERBOA_AWK):$(JERBOA_SED):$(JERBOA_AWS):$(JERBOA_WORMHOLE):$(JH)/lib
+JEXEC = $(JERBUILD) exec --libdirs "$(LIBDIRS_JSH)"
 DIST_DIR ?= $(CURDIR)/dist
 RELEASE_EVIDENCE_DIR ?= $(DIST_DIR)/release-evidence
 TIMING_EVIDENCE_DIR ?= $(DIST_DIR)/timing-evidence
@@ -59,14 +90,35 @@ OILS_COMMIT ?= 15de8fd779569e6e3a9f5fcbfc00e7df0ebe0380
 OILS_TREE ?= f95f91004abcfdb82dd413e6ef2a7c71591f23d6
 SH_SPEC := python3 $(CURDIR)/test/run_spec.py
 BASH := /bin/bash
-JSH ?= $(if $(filter Darwin,$(UNAME_S)),./jsh-macos,./jsh)
-
-.PHONY: ensure-jerboa-tools ensure-shell-extras extras jerboa binary jsh jsh-macos macos run security test test-binary test-native \
+HOST_OS := $(UNAME_S)
+HOST_TERMUX := $(shell if [ "$$(uname -s)" = Linux ] && { [ -n "$$TERMUX_VERSION" ] || [ -d /data/data/com.termux ]; }; then echo 1; fi)
+JSH ?= $(if $(HOST_TERMUX),./jsh-android,$(if $(filter Darwin,$(HOST_OS)),./jsh-macos,$(if $(filter FreeBSD,$(HOST_OS)),./jsh-freebsd-$(HOST_ARCH),./jsh-linux-$(HOST_ARCH))))
+PODMAN ?= podman
+
+.PHONY: all build ensure-jerboa-tools ensure-jerboa-shell ensure-shell-extras ensure-jsqlite stage-jsqlite extras ffi jerboa compile binary jsh jsh-compile jsh-run jsh-binary \
+        jsh-macos jsh-macos-base jsh-macos-full macos jsh-jerbuild \
+        jsh-macos-minimal configure features features-all features-minimal show-features \
+        check-freebsd-amd64-host jsh-freebsd jsh-freebsd-base jsh-freebsd-full freebsd freebsd-amd64 freebsd-arm64 jsh-freebsd-amd64 jsh-freebsd-arm64 \
+        jsh-freebsd-minimal \
+        linux linux-amd64 linux-arm64 linux-amd64-hardened linux-arm64-hardened \
+        jsh-linux-amd64 jsh-linux-arm64 jsh-linux-amd64-hardened jsh-linux-arm64-hardened \
+        linux-amd64-refresh-yubikey linux-arm64-refresh-yubikey test-linux test-linux-amd64 jsh-linux-native \
+        rust-musl-target-amd64 rust-musl-target-arm64 chez-cross-amd64 chez-cross-arm64 \
+        rust-coreutils-linux-amd64 rust-coreutils-linux-arm64 jerboa-native-linux-amd64 jerboa-native-linux-arm64 \
+        rust-wireguard-host rust-wireguard-linux-amd64 rust-wireguard-linux-arm64 \
+        jerboa-native-host vendor-staged-libs check-vendor-staged-libs-fresh jerboa-yubikey-lib check-yubikey-lib-fresh \
+        yubikey-native-linux-amd64 yubikey-native-linux-arm64 vendor-deps vendor-deps-linux vendor-native-rs vendor-native-rs-pruned \
+        check-android-host jsh-android jsh-android-native jsh-android-cross jsh-android-minimal jsh-android-full android \
+        musl gen-embed embed-crypto gen-certs verify-harden install install-binary install-shims install-hooks learn-codex-shim \
+        upload-macos-libs check-platforms run security test test-binary test-native test-mux test-mux-screen test-mux-tcp test-security-policy \
         test-native-sanitize test-security-regressions adversarial-corpus audit \
         sbom reproducibility-report timing-evidence verify release-evidence \
         compat compat-smoke compat-tier0 compat-tier1 compat-tier2 compat-one compat-range compat-debug \
         bench clean help
 
+all: binary
+build: binary
+
 ensure-jerboa-tools:
 	@set -e; \
 	if [ -n "$(JERBUILD)" ] && [ -x "$(JERBUILD)" ] && "$(JERBUILD)" --jerboa-home >/dev/null 2>&1; then \
@@ -76,6 +128,9 @@ ensure-jerboa-tools:
 	  sh support/ensure-jerboa.sh "$(JERBOA_VERSION)" "$(JERBOA_TOOL_DIR)"; \
 	fi
 
+ensure-jerboa-shell:
+	@echo "=== ensure-jerboa-shell compatibility target: sources are already in this repo ==="
+
 ensure-shell-extras:
 	@if [ ! -f "$(JERBOA_SHELL_EXTRAS_DIR)/Makefile" ]; then \
 		echo "=== Fetching jerboa-shell-extras ==="; \
@@ -93,6 +148,21 @@ extras: ensure-shell-extras
 		JSH_EMBED="$(JERBOA_SHELL_EXTRAS_DIR)/embed" \
 		EXTRAS_OUTPUT="$(CURDIR)/jsh-extras"
 
+ensure-jsqlite:
+	@if [ ! -f "$(JSQLITE_DIR)/jsqlite/api.ss" ]; then \
+		mkdir -p "$(VENDOR)"; \
+		git clone --depth 1 "$(JSQLITE_URL)" "$(JSQLITE_REPO)"; \
+	fi
+	@test -f "$(JSQLITE_DIR)/jsqlite/api.ss"
+
+stage-jsqlite: ensure-jsqlite
+	@rm -rf "$(JSQLITE_STAGE)"
+	@mkdir -p "$(JSQLITE_STAGE)"
+	@cp -a "$(JSQLITE_DIR)/jsqlite/." "$(JSQLITE_STAGE)/"
+	@find "$(JSQLITE_STAGE)" -type f \( -name '*.so' -o -name '*.wpo' \
+		-o -name '*.ta6le' -o -name '*.ta6fb' \
+		-o -name '*.tarm64osx' -o -name '*.tx86_64osx' \) -delete 2>/dev/null || true
+
 ffi-shim-symbols.list: ffi-shim.c tools/extract-ffi-symbols.sh Makefile
 	@echo "=== Regenerating FFI symbol whitelist from ffi-shim.c ==="
 	@tools/extract-ffi-symbols.sh --ffi-shim > $@.tmp && mv $@.tmp $@
@@ -101,6 +171,8 @@ $(FFI_LIB): ffi-shim.c
 	@echo "=== Building interpreted FFI shim: $@ ==="
 	$(CC) $(FFI_CFLAGS) $(SHARED_CFLAGS) -o $@ ffi-shim.c $(HARDEN_LDFLAGS)
 
+ffi: $(FFI_LIB)
+
 jerboa: ensure-jerboa-tools
 	@echo "=== Cleaning stale compiled artifacts ==="
 	@find src -type f \( -name '*.so' -o -name '*.wpo' -o -name '*.tarm64osx' -o -name '*.tx86_64osx' -o -name '*.ta6le' -o -name '*.ta6fb' \) -delete 2>/dev/null || true
@@ -117,12 +189,539 @@ jsh-macos: ensure-jerboa-tools ffi-shim-symbols.list jerboa
 	@shasum -a 256 jsh-macos > jsh-macos.sha256
 
 macos: jsh-macos
-binary: jsh-macos
-
-jsh: binary
-	@cp -f jsh-macos jsh
+binary: jsh-binary
+
+jsh-jerbuild: jsh-macos
+
+jsh-macos-base:
+	@$(MAKE) jsh-macos JSH_FEATURES=none
+	@cp -f jsh-macos jsh-macos-base
+	@shasum -a 256 jsh-macos-base > jsh-macos-base.sha256
+
+jsh-macos-full:
+	@$(MAKE) jsh-macos JSH_FEATURES=all
+	@cp -f jsh-macos jsh-macos-full
+	@shasum -a 256 jsh-macos-full > jsh-macos-full.sha256
+
+jsh-macos-minimal:
+	@echo "=== jsh-macos-minimal is a compatibility alias; building with JSH_FEATURES=none ==="
+	@$(MAKE) jsh-macos-base
+
+features-all:
+	@echo "=== features-all compatibility target: use JSH_FEATURES=all make <target> ==="
+
+configure features: features-all
+
+features-minimal:
+	@echo "=== features-minimal compatibility target: use JSH_FEATURES=none make <target> ==="
+
+show-features:
+	@echo "JSH_FEATURES=$${JSH_FEATURES:-$${JERBOA_FEATURES:-all/default by target}}"
+
+compile: ensure-jerboa-tools stage-jsqlite jerboa gen-embed
+	@echo "=== Compiling .sls -> .so ==="
+	$(JERBUILD) exec --libdirs "$(LIBDIRS_JSH)" build-all.ss
+
+jsh-compile: ensure-jerboa-tools stage-jsqlite ffi vendor-deps jerboa gen-embed
+	@echo "=== Compiling src/jsh/*.sls -> .so ==="
+	$(JERBUILD) exec --libdirs "$(LIBDIRS_JSH)" build-jsh.ss
+
+jsh-binary:
+	@case "$$(uname -s)" in \
+	  Darwin)  $(MAKE) jsh-macos ;; \
+	  Linux)   if [ -n "$$TERMUX_VERSION" ] || [ -d /data/data/com.termux ]; then \
+	             echo "ERROR: Android/Termux build target is not present in this repo" >&2; exit 1; \
+	           elif command -v musl-gcc >/dev/null 2>&1; then \
+	             echo "=== Building static musl jsh-linux-$(HOST_ARCH) (native) ===" && \
+	             $(MAKE) ffi-shim-symbols.list ffi jsh-compile && \
+	             JSH_FEATURES="$(JSH_FEATURES)" ./build-jsh-musl.sh && \
+	             cp -f jsh-musl jsh-linux-$(HOST_ARCH) && rm -f jsh-musl jsh-musl.sha256 && \
+	             { sha256sum jsh-linux-$(HOST_ARCH) 2>/dev/null || shasum -a 256 jsh-linux-$(HOST_ARCH); } > jsh-linux-$(HOST_ARCH).sha256 && \
+	             ls -lh jsh-linux-$(HOST_ARCH); \
+	           else $(MAKE) jsh-linux-native; fi ;; \
+	  FreeBSD) $(MAKE) jsh-freebsd-amd64 ;; \
+	  *)       echo "Unsupported platform: $$(uname -s)" >&2; exit 1 ;; \
+	esac
+
+jsh: jsh-binary
+	@echo "=== jsh binary ready ==="
+	@case "$$(uname -s)" in \
+	  Darwin)  cp -f jsh-macos jsh ;; \
+	  Linux)   cp -f jsh-linux-$(HOST_ARCH) jsh 2>/dev/null || cp -f jsh-linux-native jsh ;; \
+	  FreeBSD) cp -f jsh-freebsd-$(HOST_ARCH) jsh 2>/dev/null || cp -f jsh-freebsd jsh ;; \
+	esac
 	@ls -lh jsh
 
+jsh-run: stage-jsqlite
+	$(JSH_FFI_ENV) $(JEXEC) jsh.ss
+
+musl: ensure-jerboa-tools
+	@echo "=== Testing musl module ==="
+	@tmp="$$(mktemp /tmp/jsh-musl-check.XXXXXX.ss)"; \
+	  printf '%s\n' \
+	    '#!chezscheme' \
+	    '(import (jerboa build musl))' \
+	    '(display "musl-gcc: ")' \
+	    '(display (if (musl-available?) "available" "not found"))' \
+	    '(newline)' \
+	    '(when (musl-available?)' \
+	    '  (let ([status (validate-musl-setup)])' \
+	    '    (display "Status: ")' \
+	    '    (display (car status))' \
+	    '    (display " - ")' \
+	    '    (display (cdr status))' \
+	    '    (newline)))' > "$$tmp"; \
+	  $(JERBUILD) exec --libdirs "$(JH)/lib" "$$tmp" || true; \
+	  rm -f "$$tmp"
+
+gen-embed: ensure-jerboa-tools vendor-native-rs-pruned
+	@if [ -f src/jsh/embed-data.sls ] && grep -q 'embed-encrypted? #t' src/jsh/embed-data.sls; then \
+		echo "=== Skipping gen-embed (encrypted embed-data.sls already exists) ==="; \
+	elif [ -t 0 ]; then \
+		echo "=== Generating embed-data.sls (from $(JSH_EMBED)/) ==="; \
+		$(MAKE) jerboa-native-host; \
+		JSH_BUILD_ROOT="$(CURDIR)" JSH_EMBED_DIR="$(abspath $(JSH_EMBED))" \
+		JERBOA_NATIVE_LIB="$(abspath $(JERBOA_NATIVE_HOST_LIB))" \
+		JSH_DEV_NATIVE=1 $(JEXEC) gen-embed.ss; \
+	elif [ ! -f src/jsh/embed-data.sls ]; then \
+		echo "=== Generating empty embed-data.sls (batch mode, no TTY) ==="; \
+		mkdir -p src/jsh; \
+		printf '#!chezscheme\n(library (jsh embed-data)\n  (export %%embed-file-table %%embed-encrypted? %%embed-salt %%record-pubkey)\n  (import (chezscheme))\n  (define %%embed-encrypted? #f)\n  (define %%embed-salt #vu8())\n  (define %%record-pubkey #f)\n  (define %%embed-file-table (make-hashtable string-hash string=?))\n)\n' > src/jsh/embed-data.sls; \
+	else \
+		echo "=== Skipping gen-embed (batch mode, using existing embed-data.sls) ==="; \
+	fi
+
+jerboa-native-host: vendor-native-rs-pruned
+	@echo "=== Ensuring host libjerboa_native$(SO_EXT) exists for gen-embed (--features $(JERBOA_NATIVE_HOST_FEATURES)) ==="
+	cd $(JERBOA_NATIVE_RS_DIR) && \
+	    RUSTC="$$(rustup which rustc 2>/dev/null || command -v rustc)" \
+	    "$$(rustup which cargo 2>/dev/null || command -v cargo)" build \
+	        --release --no-default-features --features "$(JERBOA_NATIVE_HOST_FEATURES)"
+	@if command -v nm >/dev/null 2>&1 && nm -g "$(JERBOA_NATIVE_HOST_LIB)" 2>/dev/null | grep -Eq 'jerboa_sqlite_|sqlite3_'; then \
+	    echo "ERROR: native SQLite symbols found in $(JERBOA_NATIVE_HOST_LIB); jsh must use jsqlite" >&2; exit 1; \
+	fi
+	@test -f $(JERBOA_NATIVE_HOST_LIB)
+
+gen-certs:
+	@mkdir -p $(HOME)/.embed/tls
+	@echo "=== Generating self-signed TLS certs ==="
+	openssl req -x509 -newkey ed25519 -noenc -days 365 \
+	  -subj '/CN=jsh-mux' -addext 'subjectAltName=IP:0.0.0.0' \
+	  -keyout $(HOME)/.embed/tls/key.pem -out $(HOME)/.embed/tls/cert.pem 2>/dev/null
+	@echo "  cert: $(HOME)/.embed/tls/cert.pem"
+	@echo "  key:  $(HOME)/.embed/tls/key.pem"
+	@openssl x509 -in $(HOME)/.embed/tls/cert.pem -fingerprint -sha256 -noout 2>/dev/null || true
+
+embed-crypto:
+	@echo "=== embed-crypto: now served by libjerboa_native (W-1 / L-1) -- no-op ==="
+
+check-freebsd-amd64-host:
+	@case "$$(uname -s)-$$(uname -m)" in \
+	  FreeBSD-x86_64|FreeBSD-amd64) ;; \
+	  *) echo "ERROR: .jerbuild.freebsd-amd64 must run on FreeBSD amd64" >&2; exit 1 ;; \
+	esac
+
+jsh-freebsd: check-freebsd-amd64-host ensure-jerboa-tools ensure-jsqlite ffi-shim-symbols.list vendor-deps jerboa vendor-staged-libs gen-embed
+	@echo "=== Building jsh for FreeBSD with jerbuild ==="
+	JERBUILD="$(JERBUILD)" JSH_FEATURES="$(JSH_FEATURES)" $(JERBUILD) build --config .jerbuild.freebsd-amd64
+
+jsh-freebsd-base:
+	@$(MAKE) jsh-freebsd JSH_FEATURES=none
+
+jsh-freebsd-full:
+	@$(MAKE) jsh-freebsd JSH_FEATURES=all
+
+jsh-freebsd-minimal:
+	@echo "=== jsh-freebsd-minimal is a compatibility alias; building with JSH_FEATURES=none ==="
+	@$(MAKE) jsh-freebsd-base
+
+freebsd-amd64:
+	@case "$$(uname -s)-$$(uname -m)" in \
+	  FreeBSD-x86_64|FreeBSD-amd64) $(MAKE) jsh-freebsd ;; \
+	  *) echo "ERROR: build freebsd-amd64 natively on a FreeBSD amd64 host (no Podman cross-build)." >&2; exit 1 ;; \
+	esac
+	@cp -f jsh-freebsd jsh-freebsd-amd64
+	@ls -lh jsh-freebsd-amd64
+	@file jsh-freebsd-amd64
+
+freebsd-arm64:
+	@echo "ERROR: obersh does not provide a FreeBSD arm64 build path yet; only freebsd-amd64 is available." >&2
+	@exit 1
+
+freebsd: freebsd-amd64
+jsh-freebsd-amd64: freebsd-amd64
+jsh-freebsd-arm64: freebsd-arm64
+
+JERBOA_HOME ?= $(VENDOR)/jerboa
+SCHEME ?= $(JERBOA_HOME)/.chez/bin/scheme
+XC_LIBDIRS = lib:src:_jerbuild-stage:$(VENDOR)/jerboa/lib:$(VENDOR)/jerboa-ssh/lib:$(VENDOR)/jerboa-crypto/src:$(COREUTILS):$(JERBOA_AWK):$(JERBOA_SED):$(JERBOA_AWS):$(JERBOA_WORMHOLE):$(VENDOR)/jerboa-fuse/lib:$(VENDOR)/jerboa-ssl/src:$(VENDOR)/jerboa/src:$(VENDOR)/jerboa-fuse/src:$(JERBOA_YUBIKEY_LIB)
+XC_NATIVE_RS_DIR ?= $(if $(wildcard $(VENDOR)/jerboa-native-rs/Cargo.toml),$(VENDOR)/jerboa-native-rs,$(JERBOA_HOME)/jerboa-native-rs)
+XC_NATIVE_MUSL_AMD64_LIB = $(XC_NATIVE_RS_DIR)/target/x86_64-unknown-linux-musl/release/libjerboa_native.a
+XC_NATIVE_MUSL_ARM64_LIB = $(XC_NATIVE_RS_DIR)/target/aarch64-unknown-linux-musl/release/libjerboa_native.a
+JERBOA_NATIVE_FEATURES ?= $(JSH_NATIVE_NO_SQLITE_FEATURES)
+XC_YUBIKEY_NATIVE_DIR ?= $(JERBOA_YUBIKEY_REPO)/yubikey-native
+XC_YUBIKEY_NATIVE_MUSL_AMD64_LIB = $(XC_YUBIKEY_NATIVE_DIR)/target/x86_64-unknown-linux-musl/release/libyubikey_native.a
+XC_YUBIKEY_NATIVE_MUSL_ARM64_LIB = $(XC_YUBIKEY_NATIVE_DIR)/target/aarch64-unknown-linux-musl/release/libyubikey_native.a
+XC_WIPE = rm -f jsh-generated.so jsh-generated.wpo jsh-libs.boot jsh-linux-amd64.wp.so jsh-linux-arm64.wp.so 2>/dev/null; for d in $$(printf '%s' "$(XC_LIBDIRS)" | tr ':' ' '); do [ -d "$$d" ] && find "$$d" -type f \( -name '*.so' -o -name '*.wpo' \) -delete 2>/dev/null; done; true
+
+rust-musl-target-amd64:
+	@for dir in . "$(XC_YUBIKEY_NATIVE_DIR)"; do \
+	    [ -d "$$dir" ] || continue; \
+	    (cd "$$dir" && rustup target list --installed 2>/dev/null | grep -qx x86_64-unknown-linux-musl) || \
+	      { echo "=== rustup target add x86_64-unknown-linux-musl ($$dir) ==="; \
+	        (cd "$$dir" && rustup target add x86_64-unknown-linux-musl); }; \
+	done
+
+rust-musl-target-arm64:
+	@for dir in . "$(XC_YUBIKEY_NATIVE_DIR)"; do \
+	    [ -d "$$dir" ] || continue; \
+	    (cd "$$dir" && rustup target list --installed 2>/dev/null | grep -qx aarch64-unknown-linux-musl) || \
+	      { echo "=== rustup target add aarch64-unknown-linux-musl ($$dir) ==="; \
+	        (cd "$$dir" && rustup target add aarch64-unknown-linux-musl); }; \
+	done
+
+chez-cross-amd64: vendor-deps-linux
+	@test -d $(JERBOA_HOME)/.chez-cross-ta6le || { \
+	    command -v x86_64-linux-musl-gcc >/dev/null 2>&1 || { echo "ERROR: x86_64-linux-musl-gcc not on PATH (brew install FiloSottile/musl-cross/musl-cross --with-x86_64)" >&2; exit 1; }; \
+	    echo "=== Building ta6le cross Chez in $(JERBOA_HOME) (one-time) ==="; \
+	    $(MAKE) -C $(JERBOA_HOME) chez-cross CHEZ_TARGET_MACHINE=ta6le CROSS_CC=x86_64-linux-musl-gcc; \
+	}
+
+chez-cross-arm64: vendor-deps-linux
+	@test -d $(JERBOA_HOME)/.chez-cross-tarm64le || { \
+	    command -v aarch64-linux-musl-gcc >/dev/null 2>&1 || { echo "ERROR: aarch64-linux-musl-gcc not on PATH (brew install FiloSottile/musl-cross/musl-cross --with-aarch64)" >&2; exit 1; }; \
+	    echo "=== Building tarm64le cross Chez in $(JERBOA_HOME) (one-time) ==="; \
+	    $(MAKE) -C $(JERBOA_HOME) chez-cross CHEZ_TARGET_MACHINE=tarm64le CROSS_CC=aarch64-linux-musl-gcc; \
+	}
+
+rust-coreutils-linux-amd64: rust-musl-target-amd64
+	@command -v x86_64-linux-musl-gcc >/dev/null 2>&1 || { echo "ERROR: x86_64-linux-musl-gcc not on PATH (brew install FiloSottile/musl-cross/musl-cross --with-x86_64)" >&2; exit 1; }
+	@if [ ! -f rust-coreutils/target/x86_64-unknown-linux-musl/release/libjsh_coreutils.a ]; then \
+	    echo "=== Building Rust coreutils for x86_64 Linux musl ==="; \
+	    RUSTC="$$(rustup which rustc 2>/dev/null || command -v rustc)" \
+	    CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-linux-musl-gcc \
+	      "$$(rustup which cargo 2>/dev/null || command -v cargo)" build \
+	        --manifest-path rust-coreutils/Cargo.toml --release --target x86_64-unknown-linux-musl; \
+	else echo "=== libjsh_coreutils.a (x86_64-musl) already built ==="; fi
+
+rust-coreutils-linux-arm64: rust-musl-target-arm64
+	@command -v aarch64-linux-musl-gcc >/dev/null 2>&1 || { echo "ERROR: aarch64-linux-musl-gcc not on PATH (brew install FiloSottile/musl-cross/musl-cross --with-aarch64)" >&2; exit 1; }
+	@if [ ! -f rust-coreutils/target/aarch64-unknown-linux-musl/release/libjsh_coreutils.a ]; then \
+	    echo "=== Building Rust coreutils for aarch64 Linux musl ==="; \
+	    RUSTC="$$(rustup which rustc 2>/dev/null || command -v rustc)" \
+	    CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc \
+	      "$$(rustup which cargo 2>/dev/null || command -v cargo)" build \
+	        --manifest-path rust-coreutils/Cargo.toml --release --target aarch64-unknown-linux-musl; \
+	else echo "=== libjsh_coreutils.a (aarch64-musl) already built ==="; fi
+
+vendor-staged-libs: vendor-deps
+	@VENDOR="$(VENDOR)" \
+	  MAKE="$(MAKE)" \
+	  JERBUILD="$(JERBUILD)" \
+	  JERBOA_SSH_REPO="$(JERBOA_SSH_REPO)" \
+	  JERBOA_FUSE_REPO="$(JERBOA_FUSE_REPO)" \
+	  JERBOA_YUBIKEY_REPO="$(JERBOA_YUBIKEY_REPO)" \
+	  $(VENDOR_STAGED_LIBS_SCRIPT) stage
+
+check-vendor-staged-libs-fresh: vendor-deps
+	@VENDOR="$(VENDOR)" \
+	  MAKE="$(MAKE)" \
+	  JERBUILD="$(JERBUILD)" \
+	  JERBOA_SSH_REPO="$(JERBOA_SSH_REPO)" \
+	  JERBOA_FUSE_REPO="$(JERBOA_FUSE_REPO)" \
+	  JERBOA_YUBIKEY_REPO="$(JERBOA_YUBIKEY_REPO)" \
+	  $(VENDOR_STAGED_LIBS_SCRIPT) check
+
+jerboa-yubikey-lib: vendor-staged-libs
+check-yubikey-lib-fresh: check-vendor-staged-libs-fresh
+
+jerboa-native-linux-amd64: rust-musl-target-amd64 vendor-native-rs-pruned
+	@command -v x86_64-linux-musl-gcc >/dev/null 2>&1 || { echo "ERROR: x86_64-linux-musl-gcc not on PATH (brew install FiloSottile/musl-cross/musl-cross --with-x86_64)" >&2; exit 1; }
+	@echo "=== Building jerboa-native-rs for x86_64 Linux musl (--features $(JERBOA_NATIVE_FEATURES)) ==="
+	cd $(XC_NATIVE_RS_DIR) && \
+	    RUSTC="$$(rustup which rustc 2>/dev/null || command -v rustc)" \
+	    CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-linux-musl-gcc \
+	      "$$(rustup which cargo 2>/dev/null || command -v cargo)" build \
+	        --release --target x86_64-unknown-linux-musl --no-default-features --features "$(JERBOA_NATIVE_FEATURES)"
+	@if command -v nm >/dev/null 2>&1 && nm -g "$(XC_NATIVE_MUSL_AMD64_LIB)" 2>/dev/null | grep -Eq 'jerboa_sqlite_|sqlite3_'; then \
+	    echo "ERROR: native SQLite symbols found in $(XC_NATIVE_MUSL_AMD64_LIB); jsh must use jsqlite" >&2; exit 1; \
+	fi
+	@test -f $(XC_NATIVE_MUSL_AMD64_LIB)
+
+jerboa-native-linux-arm64: rust-musl-target-arm64 vendor-native-rs-pruned
+	@command -v aarch64-linux-musl-gcc >/dev/null 2>&1 || { echo "ERROR: aarch64-linux-musl-gcc not on PATH (brew install FiloSottile/musl-cross/musl-cross --with-aarch64)" >&2; exit 1; }
+	@echo "=== Building jerboa-native-rs for aarch64 Linux musl (--features $(JERBOA_NATIVE_FEATURES)) ==="
+	cd $(XC_NATIVE_RS_DIR) && \
+	    RUSTC="$$(rustup which rustc 2>/dev/null || command -v rustc)" \
+	    CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc \
+	      "$$(rustup which cargo 2>/dev/null || command -v cargo)" build \
+	        --release --target aarch64-unknown-linux-musl --no-default-features --features "$(JERBOA_NATIVE_FEATURES)"
+	@if command -v nm >/dev/null 2>&1 && nm -g "$(XC_NATIVE_MUSL_ARM64_LIB)" 2>/dev/null | grep -Eq 'jerboa_sqlite_|sqlite3_'; then \
+	    echo "ERROR: native SQLite symbols found in $(XC_NATIVE_MUSL_ARM64_LIB); jsh must use jsqlite" >&2; exit 1; \
+	fi
+	@test -f $(XC_NATIVE_MUSL_ARM64_LIB)
+
+yubikey-native-linux-amd64: rust-musl-target-amd64
+	@command -v x86_64-linux-musl-gcc >/dev/null 2>&1 || { echo "ERROR: x86_64-linux-musl-gcc not on PATH (brew install FiloSottile/musl-cross/musl-cross --with-x86_64)" >&2; exit 1; }
+	@echo "=== Building yubikey-native (nusb, no FIDO2) for x86_64 Linux musl ==="
+	cd $(XC_YUBIKEY_NATIVE_DIR) && \
+	    RUSTC="$$(rustup which rustc 2>/dev/null || command -v rustc)" \
+	    CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-linux-musl-gcc \
+	      "$$(rustup which cargo 2>/dev/null || command -v cargo)" build \
+	        --release --target x86_64-unknown-linux-musl --no-default-features
+	@test -f $(XC_YUBIKEY_NATIVE_MUSL_AMD64_LIB)
+
+yubikey-native-linux-arm64: rust-musl-target-arm64
+	@command -v aarch64-linux-musl-gcc >/dev/null 2>&1 || { echo "ERROR: aarch64-linux-musl-gcc not on PATH (brew install FiloSottile/musl-cross/musl-cross --with-aarch64)" >&2; exit 1; }
+	@echo "=== Building yubikey-native (nusb, no FIDO2) for aarch64 Linux musl ==="
+	cd $(XC_YUBIKEY_NATIVE_DIR) && \
+	    RUSTC="$$(rustup which rustc 2>/dev/null || command -v rustc)" \
+	    CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc \
+	      "$$(rustup which cargo 2>/dev/null || command -v cargo)" build \
+	        --release --target aarch64-unknown-linux-musl --no-default-features
+	@test -f $(XC_YUBIKEY_NATIVE_MUSL_ARM64_LIB)
+
+linux-amd64: stage-jsqlite vendor-deps-linux jerboa gen-embed chez-cross-amd64 rust-coreutils-linux-amd64 jerboa-native-linux-amd64 vendor-staged-libs yubikey-native-linux-amd64
+	@echo "=== Wiping stale per-arch compiled artifacts (.so/.wpo/boot) ==="
+	@$(XC_WIPE)
+	@features="$(JSH_CROSS_FEATURES)"; \
+	    echo "=== Cross-building jsh-linux-amd64 (x86_64 Linux static, JSH_FEATURES=$$features) ==="; \
+	    JERBOA_HOME=$(JERBOA_HOME) TARGET_ARCH=amd64 JSH_FEATURES="$$features" JERBOA_NATIVE_FEATURES="$(JERBOA_NATIVE_FEATURES)" JERBOA_NATIVE_A="$(XC_NATIVE_MUSL_AMD64_LIB)" YUBIKEY_NATIVE_A="$(XC_YUBIKEY_NATIVE_MUSL_AMD64_LIB)" $(SCHEME) -q --libdirs "$(XC_LIBDIRS)" --script build-jsh-cross.ss
+	@echo "=== jsh-linux-amd64 ready ==="
+	@ls -lh jsh-linux-amd64
+	@file jsh-linux-amd64
+
+linux-arm64: stage-jsqlite vendor-deps-linux jerboa gen-embed chez-cross-arm64 rust-coreutils-linux-arm64 jerboa-native-linux-arm64 vendor-staged-libs yubikey-native-linux-arm64
+	@echo "=== Wiping stale per-arch compiled artifacts (.so/.wpo/boot) ==="
+	@$(XC_WIPE)
+	@features="$(JSH_CROSS_FEATURES)"; \
+	    echo "=== Cross-building jsh-linux-arm64 (aarch64 Linux static, JSH_FEATURES=$$features) ==="; \
+	    JERBOA_HOME=$(JERBOA_HOME) TARGET_ARCH=arm64 JSH_FEATURES="$$features" JERBOA_NATIVE_FEATURES="$(JERBOA_NATIVE_FEATURES)" JERBOA_NATIVE_A="$(XC_NATIVE_MUSL_ARM64_LIB)" YUBIKEY_NATIVE_A="$(XC_YUBIKEY_NATIVE_MUSL_ARM64_LIB)" $(SCHEME) -q --libdirs "$(XC_LIBDIRS)" --script build-jsh-cross.ss
+	@echo "=== jsh-linux-arm64 ready ==="
+	@ls -lh jsh-linux-arm64
+	@file jsh-linux-arm64
+
+jsh-linux-native: ensure-jerboa-tools ensure-jsqlite ffi-shim-symbols.list vendor-deps jerboa vendor-staged-libs gen-embed
+	@case "$$(uname -s)-$$(uname -m)" in \
+	  Linux-x86_64|Linux-amd64) ;; \
+	  *) echo "ERROR: .jerbuild.linux-amd64 must run on Linux amd64" >&2; exit 1 ;; \
+	esac
+	@echo "=== Building native Linux amd64 jsh with jerbuild ==="
+	JERBUILD="$(JERBUILD)" JSH_FEATURES="$(JSH_FEATURES)" $(JERBUILD) build --config .jerbuild.linux-amd64
+
+linux: linux-amd64
+jsh-linux-amd64: linux-amd64
+jsh-linux-arm64: linux-arm64
+
+linux-amd64-hardened:
+	@echo "ERROR: hardened Linux builds require the newer obersh cross driver with OUTPUT_NAME/JSH_EMBED_OBFUSCATE support." >&2
+	@exit 1
+
+linux-arm64-hardened:
+	@echo "ERROR: hardened Linux builds require the newer obersh cross driver with OUTPUT_NAME/JSH_EMBED_OBFUSCATE support." >&2
+	@exit 1
+
+jsh-linux-amd64-hardened: linux-amd64-hardened
+jsh-linux-arm64-hardened: linux-arm64-hardened
+
+linux-amd64-refresh-yubikey:
+	@$(MAKE) linux-amd64 VENDOR_REFRESH=jerboa-yubikey
+
+linux-arm64-refresh-yubikey:
+	@$(MAKE) linux-arm64 VENDOR_REFRESH=jerboa-yubikey
+
+test-linux-amd64:
+	@test -x ./jsh-linux-amd64 || { echo "ERROR: ./jsh-linux-amd64 not built; run 'make linux-amd64' first" >&2; exit 1; }
+	@$(PODMAN) --version >/dev/null 2>&1 || { echo "ERROR: Podman not available via PODMAN=$(PODMAN)" >&2; exit 1; }
+	@echo "=== test-linux-amd64: alpine:3 x86_64 ==="
+	$(PODMAN) run --rm --platform linux/amd64 -v "$$PWD:/work" -w /work docker.io/library/alpine:3 \
+	    ./jsh-linux-amd64 -c 'echo hello world; echo $$((2 + 3)); for i in 1 2 3; do echo "i=$$i"; done'
+
+test-linux: test-linux-amd64
+
+check-android-host:
+	@case "$$(uname -s)" in \
+	  Linux) if [ -n "$$TERMUX_VERSION" ] || [ -d /data/data/com.termux ]; then :; \
+	         else echo "ERROR: Android targets require Termux/Android support, which is not present in this repo." >&2; exit 1; fi ;; \
+	  *) echo "ERROR: Android targets require Termux/Android support, which is not present in this repo." >&2; exit 1 ;; \
+	esac
+
+jsh-android jsh-android-native jsh-android-cross jsh-android-minimal jsh-android-full android: check-android-host
+	@echo "ERROR: Android build scripts are not present in this repo; use obersh for Android targets." >&2
+	@exit 1
+
+rust-wireguard-host rust-wireguard-linux-amd64 rust-wireguard-linux-arm64:
+	@echo "ERROR: WireGuard native library sources are not present in this repo; use obersh for WireGuard targets." >&2
+	@exit 1
+
+VENDOR_REPOS ?= jerboa-coreutils jerboa-awk jerboa-sed jerboa-aws jerboa-yubikey jerboa-ssh jerboa-fuse jerboa-wormhole
+VENDOR_REFRESH ?=
+LINUX_VENDOR_REPOS = jerboa jerboa-coreutils jerboa-awk jerboa-sed jerboa-aws jerboa-yubikey jerboa-ssh jerboa-fuse jerboa-wormhole
+
+vendor-deps-linux: VENDOR_REPOS := $(LINUX_VENDOR_REPOS)
+vendor-deps-linux: vendor-deps
+
+vendor-deps: ensure-jsqlite vendor-native-rs-pruned
+	@echo "=== Vendoring dependencies into vendor/ ==="
+	@mkdir -p vendor
+	@for repo in $(VENDOR_REPOS); do \
+		needs_clone=0; \
+		case " $(VENDOR_REFRESH) " in \
+			*" $$repo "*|*" all "*) \
+				echo "  (refresh) $$repo -- removing existing clone"; \
+				rm -rf "vendor/$$repo"; \
+				needs_clone=1; \
+				;; \
+		esac; \
+		if [ ! -d "vendor/$$repo" ]; then \
+			needs_clone=1; \
+		elif [ ! -f "vendor/$$repo/README.md" ] && [ ! -f "vendor/$$repo/Makefile" ] && [ ! -f "vendor/$$repo/Cargo.toml" ]; then \
+			echo "  (incomplete) $$repo -- re-cloning"; \
+			rm -rf "vendor/$$repo"; \
+			needs_clone=1; \
+		fi; \
+		if [ "$$needs_clone" = "1" ]; then \
+			echo "  Cloning $$repo..."; \
+			git clone --depth 1 "https://git.sr.ht/~lisp/$$repo" "vendor/$$repo" 2>&1 | tail -1; \
+			rm -rf "vendor/$$repo/.git"; \
+		else \
+			echo "  (exists) $$repo"; \
+		fi; \
+	done
+	@if [ -f "$(JERBOA_AWK)/jerboa-awk/runtime.sls" ]; then \
+		perl -0pi -e 's/  \(define \*regex-cache\* \(make-regex-cache\)\)\n\n  \(define \(make-regex-cache\)\n    \(make-hashtable equal-hash equal\?\)\)/  (define (make-regex-cache)\n    (make-hashtable equal-hash equal?))\n\n  (define *regex-cache* (make-regex-cache))/m' \
+			"$(JERBOA_AWK)/jerboa-awk/runtime.sls"; \
+	fi
+	@VENDOR="$(VENDOR)" JERBOA_SSH_REPO="$(JERBOA_SSH_REPO)" bash "$(VENDOR_OVERRIDES_SCRIPT)" ssh
+	@echo "=== Vendor complete ==="
+
+vendor-native-rs: $(JERBOA_NATIVE_RS_DIR)
+
+vendor-native-rs-pruned: vendor-native-rs
+	@echo "=== Pruning unused native Rust packages from vendored jerboa-native-rs ==="
+	@test -f "$(JERBOA_NATIVE_RS_DIR)/Cargo.toml"
+	@perl -0pi -e 's/^libc = "=0\.2\.[0-9]+"/libc = "0.2"/m; s/^(duckdb|rusqlite|postgres|wasmi|getrandom|rscap|mozjs|mozjs_sys)\s*=\s*\{[^\n]*\}\n//mg; s/^(duckdb_feat|sqlite|postgres_feat|wasm|pcap|spidermonkey)\s*=\s*\[[^\n]*\]\n//mg; s/,\s*"(duckdb_feat|sqlite|postgres_feat|wasm|pcap|spidermonkey)"//g; s/"(duckdb_feat|sqlite|postgres_feat|wasm|pcap|spidermonkey)"\s*,\s*//g; s/"(duckdb_feat|sqlite|postgres_feat|wasm|pcap|spidermonkey)"//g' "$(JERBOA_NATIVE_RS_DIR)/Cargo.toml"
+	@if [ -f "$(JERBOA_NATIVE_RS_DIR)/src/lib.rs" ]; then \
+		perl -0pi -e 's/\n#\[cfg\(feature = "(duckdb_feat|sqlite|postgres_feat|wasm|pcap|spidermonkey)"\)\]\nmod (duckdb_native|sqlite|postgres_native|wasm|pcap_capture|wasm_sm);\n/\n/g' "$(JERBOA_NATIVE_RS_DIR)/src/lib.rs"; \
+		perl -0pi -e 's/\n#\[cfg\(feature = "crypto"\)\]\nmod ed25519;\n/\n/g' "$(JERBOA_NATIVE_RS_DIR)/src/lib.rs"; \
+	fi
+	@rm -f "$(JERBOA_NATIVE_RS_DIR)/src/duckdb_native.rs" "$(JERBOA_NATIVE_RS_DIR)/src/sqlite.rs" \
+		"$(JERBOA_NATIVE_RS_DIR)/src/postgres_native.rs" "$(JERBOA_NATIVE_RS_DIR)/src/wasm.rs" \
+		"$(JERBOA_NATIVE_RS_DIR)/src/pcap_capture.rs" "$(JERBOA_NATIVE_RS_DIR)/src/ed25519.rs" \
+		"$(JERBOA_NATIVE_RS_DIR)/src/wasm_sm.rs"
+	@VENDOR="$(VENDOR)" JERBOA_NATIVE_RS_DIR="$(JERBOA_NATIVE_RS_DIR)" bash "$(VENDOR_OVERRIDES_SCRIPT)" native
+	@if grep -R -n -E 'duckdb|duckdb_feat|libduckdb|DUCKDB' "$(JERBOA_NATIVE_RS_DIR)/Cargo.toml" "$(JERBOA_NATIVE_RS_DIR)/src" | grep -v -E '^[^:]+:[0-9]+:[[:space:]]*#'; then \
+		echo "ERROR: DuckDB references remain in vendored jerboa-native-rs" >&2; exit 1; \
+	fi
+	@if grep -R -n -E 'rusqlite|sqlite3|jerboa_sqlite_|^sqlite\s*=|feature = "sqlite"' "$(JERBOA_NATIVE_RS_DIR)/Cargo.toml" "$(JERBOA_NATIVE_RS_DIR)/src" | grep -v -E '^[^:]+:[0-9]+:[[:space:]]*#'; then \
+		echo "ERROR: native SQLite references remain in vendored jerboa-native-rs" >&2; exit 1; \
+	fi
+
+$(JERBOA_NATIVE_RS_DIR):
+	@mkdir -p vendor
+	@echo "=== Sparse-cloning jerboa-native-rs from the jerboa monorepo ==="
+	@tmp="vendor/.jerboa-native-rs-monorepo"; \
+	  rm -rf "$$tmp"; \
+	  git clone --depth 1 --filter=blob:none --sparse https://git.sr.ht/~lisp/jerboa "$$tmp"; \
+	  git -C "$$tmp" sparse-checkout set jerboa-native-rs; \
+	  mv "$$tmp/jerboa-native-rs" "$(JERBOA_NATIVE_RS_DIR)"; \
+	  rm -rf "$$tmp"
+
+verify-harden: binary
+	@echo "=== Verifying binary hardening ($(JSH)) ==="
+	@echo "--- Symbol check (should show 'stripped') ---"
+	@(file $(JSH) | grep -qE 'stripped|no section header') && echo "  PASS: binary is stripped" || echo "  FAIL: binary not stripped"
+	@echo "--- Build path leak check ---"
+	@if strings $(JSH) | grep -q "$(HOME)"; then \
+		echo "  WARN: home directory path found in binary"; \
+	else \
+		echo "  PASS: no home directory paths leaked"; \
+	fi
+	@echo "--- Integrity hash check ---"
+	@if [ -f $(JSH).sha256 ]; then \
+		echo "  PASS: $(JSH).sha256 exists ($$(wc -c < $(JSH).sha256) bytes)"; \
+	else \
+		echo "  FAIL: $(JSH).sha256 not found"; \
+	fi
+	@echo "--- Runtime hardening check (JSH_DEV=1 to skip) ---"
+	@JSH_DEV=1 $(JSH) -c 'echo ok' >/dev/null 2>&1 && echo "  PASS: binary runs in dev mode" || echo "  FAIL: binary doesn't run"
+	@echo "=== Hardening verification complete ==="
+
+check-platforms:
+	@tools/check-platforms.sh
+
+test-security-policy: security
+
+test-mux:
+	@echo "ERROR: mux test scripts are not present in this repo; use obersh for mux tests." >&2
+	@exit 1
+
+test-mux-screen:
+	@echo "ERROR: mux screen comparison scripts are not present in this repo; use obersh for mux tests." >&2
+	@exit 1
+
+test-mux-tcp:
+	@echo "ERROR: mux TCP test scripts are not present in this repo; use obersh for mux tests." >&2
+	@exit 1
+
+MACOS_LIBS_TAG ?= macos-libs-v1
+MACOS_ARCH := $(shell uname -m | sed 's/arm64/arm64/;s/x86_64/x86_64/')
+
+upload-macos-libs:
+	@echo "ERROR: upload-macos-libs requires rust-coreutils/native archive assets that are not present in this repo; use obersh for that release helper." >&2
+	@exit 1
+
+PREFIX ?= $(HOME)/.local
+SHIM_DIR ?= $(HOME)/.jsh/shims
+
+install: install-binary install-shims
+
+install-binary: $(if $(JSH_BIN),,jsh)
+	@set -e; \
+	bin="$(JSH_BIN)"; \
+	if [ -z "$$bin" ]; then \
+	  case "$$(uname -s)" in \
+	    Darwin)  bin=./jsh-macos ;; \
+	    FreeBSD) bin=./jsh-freebsd ;; \
+	    Linux)   case "$$(uname -m)" in \
+	               x86_64|amd64)  bin=./jsh-linux-amd64 ;; \
+	               aarch64|arm64) bin=./jsh-linux-arm64 ;; \
+	               *) echo "no native jsh binary for Linux $$(uname -m)" >&2; exit 1 ;; \
+	             esac ;; \
+	    *) echo "no native jsh binary for $$(uname -s) -- set JSH_BIN=" >&2; exit 1 ;; \
+	  esac; \
+	fi; \
+	if [ ! -x "$$bin" ]; then \
+	  echo "$$bin not built -- run the matching build target first" >&2; \
+	  exit 1; \
+	fi; \
+	mkdir -p "$(PREFIX)/bin"; \
+	install -m 755 "$$bin" "$(PREFIX)/bin/jsh"; \
+	echo "installed $$bin -> $(PREFIX)/bin/jsh"
+
+install-shims:
+	@set -e; \
+	mkdir -p "$(SHIM_DIR)"; \
+	chmod 700 "$(SHIM_DIR)"; \
+	count=0; \
+	for f in shims/*; do \
+	  case "$$f" in *.md|*/README*) continue ;; esac; \
+	  [ -f "$$f" ] || continue; \
+	  install -m 755 "$$f" "$(SHIM_DIR)/$$(basename "$$f")"; \
+	  count=$$((count+1)); \
+	done; \
+	echo "installed $$count shim(s) -> $(SHIM_DIR)"
+
+install-hooks:
+	@if [ ! -d .git ]; then echo "not a git checkout -- cannot install hooks" >&2; exit 1; fi
+	@mkdir -p .git/hooks
+	@ln -sf ../../tools/pre-commit .git/hooks/pre-commit
+	@echo "Installed pre-commit hook -> tools/pre-commit"
+
+learn-codex-shim:
+	@echo "ERROR: tools/jsh-learn-policy is not present in this repo; use obersh for learn-codex-shim." >&2
+	@exit 1
+
 run: ensure-jerboa-tools $(FFI_LIB) jerboa
 	$(JSH_FFI_ENV) $(JERBUILD) exec --libdirs "$(LIBDIRS)" jsh.ss
 
@@ -290,16 +889,44 @@ compat-debug: $(OILS_DIR)
 clean:
 	find src -type f \( -name '*.so' -o -name '*.wpo' -o -name '*.sls' -o -name '*.tarm64osx' -o -name '*.tx86_64osx' -o -name '*.ta6le' -o -name '*.ta6fb' \) -delete 2>/dev/null || true
 	rm -rf jsh-src _jerbuild-stage
-	rm -f jsh jsh-macos jsh-macos.sha256 ffi-shim-symbols.list libjsh-ffi.dylib libjsh-ffi.so
+	rm -f jsh jsh-macos jsh-macos.sha256 jsh-macos-base jsh-macos-base.sha256 jsh-macos-full jsh-macos-full.sha256
+	rm -f jsh-linux-amd64 jsh-linux-amd64.sha256 jsh-linux-amd64-main.c jsh-linux-amd64.wp.so
+	rm -f jsh-linux-arm64 jsh-linux-arm64.sha256 jsh-linux-arm64-main.c jsh-linux-arm64.wp.so
+	rm -f jsh-freebsd jsh-freebsd.sha256 jsh-freebsd-amd64 jsh-freebsd-amd64.sha256
+	rm -f jsh-musl jsh-musl.sha256 jsh-generated.ss jsh-generated.so jsh-generated.wpo jsh.boot jsh-libs.boot
+	rm -f petite_boot.h scheme_boot.h jsh_libs_boot.h jsh_program.h
+	rm -f ffi-shim-symbols.list libjsh-ffi.dylib libjsh-ffi.so libjsh-ffi-musl.so
 	rm -rf dist
 
 help:
 	@echo "jsh — compact Jerboa shell"
 	@echo ""
-	@echo "Build:"
+	@echo "Build (native = host os/arch; cross = build another platform from any host):"
+	@echo "  make build            Alias for make binary"
+	@echo "  make binary           Native build for this host"
+	@echo "  make jsh              Native binary for this host, copied to ./jsh"
 	@echo "  make jsh-macos        Build the macOS binary"
-	@echo "  make jsh              Build and copy the native binary to ./jsh"
-	@echo "  make extras           Select and build optional feature bundles"
+	@echo "  make jsh-macos-base   macOS base build (core shell only)"
+	@echo "  make jsh-macos-full   macOS full build (all features)"
+	@echo "  make macos            Alias for jsh-macos"
+	@echo "  make linux-amd64      Cross: linux/amd64 static ELF"
+	@echo "  make linux-arm64      Cross: linux/arm64 static ELF"
+	@echo "  make linux-amd64-hardened  Present for obersh compatibility; unsupported here"
+	@echo "  make linux-arm64-hardened  Present for obersh compatibility; unsupported here"
+	@echo "  make linux            Alias for linux-amd64"
+	@echo "  make jsh-linux-amd64  Alias for linux-amd64"
+	@echo "  make jsh-linux-arm64  Alias for linux-arm64"
+	@echo "  make freebsd-amd64    freebsd/amd64 (native on a FreeBSD amd64 host)"
+	@echo "  make freebsd-arm64    Explicit unsupported target; obersh has no arm64 path yet"
+	@echo "  make freebsd          Alias for freebsd-amd64"
+	@echo "  make jsh-freebsd      FreeBSD binary via jerbuild"
+	@echo "  make jsh-freebsd-base FreeBSD base build (core shell only)"
+	@echo "  make jsh-freebsd-full FreeBSD full build (all features)"
+	@echo "  make android          Present for obersh compatibility; unsupported here"
+	@echo "  make extras           Select and build optional feature bundles (docs/extras.md)"
+	@echo "  make vendor-deps      Clone deps into vendor/ for self-contained builds"
+	@echo "  make gen-embed        Regenerate embedded file data"
+	@echo "  make show-features    Show current JSH_FEATURES/JERBOA_FEATURES selection"
 	@echo ""
 	@echo "Test:"
 	@echo "  make security         Release security metadata and secret scan"
@@ -313,7 +940,15 @@ help:
 	@echo "  make verify           Tests, adversarial corpus, audit, timing evidence"
 	@echo "  make compat           Oils POSIX shell compatibility report"
 	@echo "  make compat-one SPEC=name"
+	@echo "  make test-linux-amd64 Optional smoke-run jsh-linux-amd64 under alpine via Podman"
 	@echo "  make bench            Shell benchmarks"
 	@echo ""
+	@echo "Install:"
+	@echo "  make install          Build/install jsh + shims"
+	@echo "  make install-binary   Install just the platform-native jsh binary"
+	@echo "  make install-shims    Install shims into ~/.jsh/shims"
+	@echo ""
 	@echo "Other:"
+	@echo "  make verify-harden    Verify binary hardening"
+	@echo "  make gen-certs        Generate TLS certs for mux"
 	@echo "  make clean            Remove generated artifacts"
diff --git a/build-all.ss b/build-all.ss
new file mode 100644
index 0000000..5c851ec
--- /dev/null
+++ b/build-all.ss
@@ -0,0 +1,18 @@
+#!chezscheme
+;; Build driver: imports all modules to trigger Chez compilation.
+;; Generates .so + .wpo files for the platform-specific binary builds
+;; (build-jsh-{macos,musl,freebsd,android}.ss).
+(parameterize ([compile-imported-libraries #t]
+               [generate-wpo-files #t]
+               [optimize-level 3]
+               [generate-inspector-information #f])
+  (eval '(import
+    (jsh ast) (jsh registry) (jsh macros) (jsh util)
+    (jsh environment) (jsh lexer) (jsh arithmetic) (jsh glob)
+    (jsh fuzzy) (jsh history) (jsh parser) (jsh functions)
+    (jsh signals) (jsh expander) (jsh redirect) (jsh control)
+    (jsh jobs) (jsh builtins) (jsh pipeline) (jsh executor)
+    (jsh completion) (jsh prompt) (jsh lineedit) (jsh fzf)
+    (jsh script) (jsh startup) (jsh main) (jsh stage)
+    (jsh worm))
+  (interaction-environment)))
diff --git a/build-jsh-cross.ss b/build-jsh-cross.ss
new file mode 100644
index 0000000..1acd9f6
--- /dev/null
+++ b/build-jsh-cross.ss
@@ -0,0 +1,1255 @@
+#!chezscheme
+;;; build-jsh-cross.ss — Cross-compile jsh from macOS to Linux musl
+;;;
+;;; Usage:
+;;;   JERBOA_HOME=vendor/jerboa TARGET_ARCH=amd64 scheme \
+;;;     --libdirs <libs> --script build-jsh-cross.ss
+;;;
+;;; TARGET_ARCH selects the cross target:
+;;;   amd64 (default) — chez ta6le    + x86_64-linux-musl-gcc  → jsh-linux-amd64
+;;;   arm64           — chez tarm64le + aarch64-linux-musl-gcc → jsh-linux-arm64
+;;;
+;;; Cross-compile analogue of build-jsh-musl.ss that runs on a non-Linux host.
+;;; Uses:
+;;;   - $JERBOA_HOME/.chez-cross-<machine>/   — cross-built Chez install
+;;;   - $JERBOA_HOME/build/chez/xc-<machine>/s/xpatch — host compiler emit mode
+;;;   - <toolchain>-linux-musl-gcc            — C compile + final static link
+;;;
+;;; Produces: jsh-linux-{amd64,arm64}  (static Linux ELF, no shared library deps)
+;;;
+;;; This is the simpler "WPO as program" pattern: jsh.ss + all imports are
+;;; bundled into one .so via compile-whole-program, loaded at runtime via
+;;; Sscheme_program — no jsh.boot bundling required.
+
+(import (chezscheme))
+
+;; ── Params ──────────────────────────────────────────────────────────────────
+(define jerboa-home
+  (or (getenv "JERBOA_HOME")
+      (format "~a/vendor/jerboa" (current-directory))))
+
+(define target-arch (or (getenv "TARGET_ARCH") "amd64"))
+
+;; Per-arch dispatch:
+;;   (arch chez-machine c-toolchain rust-target output-name strip-tool)
+(define arch-table
+  '(("amd64" "ta6le"    "x86_64-linux-musl-gcc"  "x86_64-unknown-linux-musl"  "jsh-linux-amd64" "x86_64-linux-musl-strip")
+    ("arm64" "tarm64le" "aarch64-linux-musl-gcc" "aarch64-unknown-linux-musl" "jsh-linux-arm64" "aarch64-linux-musl-strip")))
+
+(define arch-row
+  (or (assoc target-arch arch-table)
+      (error 'build-jsh-cross
+             (format "unknown TARGET_ARCH (expected amd64|arm64): ~a" target-arch))))
+
+(define chez-machine (list-ref arch-row 1))
+(define rust-target  (list-ref arch-row 3))
+
+(define cross-prefix (format "~a/.chez-cross-~a" jerboa-home chez-machine))
+(define xpatch       (format "~a/build/chez/xc-~a/s/xpatch" jerboa-home chez-machine))
+(define cross-cc     (or (getenv "CROSS_CC") (list-ref arch-row 2)))
+(define cross-strip  (list-ref arch-row 5))
+
+(define output       (list-ref arch-row 4))
+(define source-script "jsh.ss")
+(define entry-script "jsh-generated.ss")
+(define ffi-shim     "ffi-shim.c")
+
+;; Feature resolution — derive *enabled-features* from JSH_FEATURES env var.
+;; JERBOA_FEATURES is accepted as a compatibility alias for CLI ergonomics.
+;;   ""/"none" → '()  (base build)
+;;   "all"     → all known optional features
+;;   "foo,bar" → '(foo bar)
+(define cross-supported-all-features
+  '(coreutils mux ssh aws worm vault yubikey record sandbox cage rl profiler proxy procwatch embed pass))
+
+(define cross-unsupported-features
+  '())
+
+(define (cross-filter-supported-features features)
+  (let loop ([xs features] [out '()])
+    (cond
+      [(null? xs) (reverse out)]
+      [(memq (car xs) cross-unsupported-features)
+       (printf "    warning: feature ~a is not supported by build-jsh-cross; omitting from manifest~n"
+               (car xs))
+       (loop (cdr xs) out)]
+      [else (loop (cdr xs) (cons (car xs) out))])))
+
+(define *enabled-features*
+  (let ([env (or (getenv "JSH_FEATURES") (getenv "JERBOA_FEATURES") "")])
+    (cond
+      [(or (string=? env "") (string=? env "none")) '()]
+      [(string=? env "all")
+       cross-supported-all-features]
+      [else
+       (cross-filter-supported-features
+        (let split ([i 0] [start 0] [acc '()])
+          (cond
+            [(= i (string-length env))
+             (let ([s (substring env start i)])
+               (if (string=? s "") (reverse acc)
+                   (reverse (cons (string->symbol s) acc))))]
+            [(char=? (string-ref env i) #\,)
+             (let ([s (substring env start i)])
+               (split (+ i 1) (+ i 1)
+                      (if (string=? s "") acc (cons (string->symbol s) acc))))]
+            [else (split (+ i 1) start acc)])))])))
+
+(define (feature-enabled? feature)
+  (memq feature *enabled-features*))
+
+(define (sh-quote s)
+  (let ([out (open-output-string)])
+    (display "'" out)
+    (let loop ([i 0])
+      (when (< i (string-length s))
+        (let ([ch (string-ref s i)])
+          (if (char=? ch #\')
+              (display "'\\''" out)