Add jerbuild target builds

ober

5b18cdd7b9d9d6b04ce581426e2d9db0ce1d8c63

diff --git a/.jerbuild b/.jerbuild
new file mode 100644
index 0000000..8d27a60
--- /dev/null
+++ b/.jerbuild
@@ -0,0 +1,41 @@
+;; Native host build config for jerboa-emacs TUI.
+;;
+;; Build with:
+;;   JERBUILD=/path/to/jerbuild jerbuild build
+;;
+;; The pre-build hook uses the standalone jerbuild bundle to transpile this
+;; repo and vendored dependencies. It does not require an installed full Jerboa
+;; runtime or a sibling Jerboa source checkout.
+
+(entry  "main.ss")
+(output "jemacs")
+
+(requires "jerbuild" "cc" "pkg-config" "libpcre2" "libvterm")
+
+(libdirs "lib"
+         "vendor/jerboa-shell/_jerbuild-stage"
+         "vendor/jerboa-shell/src"
+         "vendor/jerboa-shell/vendor/jerboa-coreutils/lib"
+         "vendor/jerboa-shell/vendor/jerboa-awk/lib"
+         "vendor/jerboa-shell/vendor/jerboa-sed/lib"
+         "vendor/jerboa-shell/vendor/jerboa-ssh/src"
+         "vendor/jerboa-shell/vendor/jerboa-sqlite/src"
+         "vendor/jerboa-shell/vendor/jerboa-crypto/src"
+         "vendor/jerboa-shell/vendor/jerboa-fuse/lib"
+         "vendor/jerboa-pcre2/lib"
+         "vendor/jerboa-scintilla/lib")
+
+(main-c "support/jemacs-jerbuild-main.c")
+
+(ffi-symbols "support/jemacs-jerbuild-symbols.list")
+
+(extra-sources "support/repl_shim.c"
+               "vendor/jerboa-shell/ffi-shim.c"
+               "vendor/jerboa-shell/vendor/jerboa-coreutils/support/libcoreutils.c"
+               "support/jsh_coreutils_stubs.c"
+               "support/jsh_embed_crypto_stubs.c"
+               "support/jerboa_native_stubs.c"
+               "support/jemacs_ssh_agent_stubs.c"
+               "support/jemacs_tls_stubs.c")
+
+(pre-build "support/stage-for-jerbuild.sh")
diff --git a/.jerbuild.freebsd-amd64 b/.jerbuild.freebsd-amd64
new file mode 100644
index 0000000..08701c7
--- /dev/null
+++ b/.jerbuild.freebsd-amd64
@@ -0,0 +1,47 @@
+;; Native FreeBSD amd64 build config for jerboa-emacs TUI.
+;;
+;; Build on a FreeBSD amd64 host with:
+;;   JERBUILD=/path/to/jerbuild jerbuild build --config .jerbuild.freebsd-amd64
+;;
+;; This uses installed/project-local jerbuild, cc, pkg-config, and vendored
+;; project dependencies. It has no dependency on an installed full Jerboa
+;; runtime or sibling Jerboa source checkout.
+
+(entry  "main.ss")
+(output "jemacs-freebsd-amd64")
+
+(cc "cc")
+
+(requires "jerbuild" "cc" "pkg-config" "libpcre2" "libvterm")
+
+;; FreeBSD has dlopen in libc. -lutil covers forkpty/openpty, termcap backs
+;; Chez's expeditor, and GNU libiconv is commonly under /usr/local/lib.
+(os-libs "-lm -lpthread -lutil -ltermcap -L/usr/local/lib -liconv")
+
+(libdirs "lib"
+         "vendor/jerboa-shell/_jerbuild-stage"
+         "vendor/jerboa-shell/src"
+         "vendor/jerboa-shell/vendor/jerboa-coreutils/lib"
+         "vendor/jerboa-shell/vendor/jerboa-awk/lib"
+         "vendor/jerboa-shell/vendor/jerboa-sed/lib"
+         "vendor/jerboa-shell/vendor/jerboa-ssh/src"
+         "vendor/jerboa-shell/vendor/jerboa-sqlite/src"
+         "vendor/jerboa-shell/vendor/jerboa-crypto/src"
+         "vendor/jerboa-shell/vendor/jerboa-fuse/lib"
+         "vendor/jerboa-pcre2/lib"
+         "vendor/jerboa-scintilla/lib")
+
+(main-c "support/jemacs-jerbuild-main.c")
+
+(ffi-symbols "support/jemacs-jerbuild-symbols.list")
+
+(extra-sources "support/repl_shim.c"
+               "vendor/jerboa-shell/ffi-shim.c"
+               "vendor/jerboa-shell/vendor/jerboa-coreutils/support/libcoreutils.c"
+               "support/jsh_coreutils_stubs.c"
+               "support/jsh_embed_crypto_stubs.c"
+               "support/jerboa_native_stubs.c"
+               "support/jemacs_ssh_agent_stubs.c"
+               "support/jemacs_tls_stubs.c")
+
+(pre-build "support/stage-for-jerbuild.sh")
diff --git a/.jerbuild.linux-amd64 b/.jerbuild.linux-amd64
new file mode 100644
index 0000000..cd3a6cf
--- /dev/null
+++ b/.jerbuild.linux-amd64
@@ -0,0 +1,45 @@
+;; Native Linux amd64 build config for jerboa-emacs TUI.
+;;
+;; Build on a Linux amd64 host with:
+;;   JERBUILD=/path/to/jerbuild jerbuild build --config .jerbuild.linux-amd64
+;;
+;; This uses installed/project-local jerbuild, cc, pkg-config, and vendored
+;; project dependencies. It has no dependency on an installed full Jerboa
+;; runtime or sibling Jerboa source checkout.
+
+(entry  "main.ss")
+(output "jemacs-linux-amd64")
+
+(cc "cc")
+
+(requires "jerbuild" "cc" "pkg-config" "libpcre2" "libvterm")
+
+(os-libs "-lm -ldl -lpthread -lutil -lncurses")
+
+(libdirs "lib"
+         "vendor/jerboa-shell/_jerbuild-stage"
+         "vendor/jerboa-shell/src"
+         "vendor/jerboa-shell/vendor/jerboa-coreutils/lib"
+         "vendor/jerboa-shell/vendor/jerboa-awk/lib"
+         "vendor/jerboa-shell/vendor/jerboa-sed/lib"
+         "vendor/jerboa-shell/vendor/jerboa-ssh/src"
+         "vendor/jerboa-shell/vendor/jerboa-sqlite/src"
+         "vendor/jerboa-shell/vendor/jerboa-crypto/src"
+         "vendor/jerboa-shell/vendor/jerboa-fuse/lib"
+         "vendor/jerboa-pcre2/lib"
+         "vendor/jerboa-scintilla/lib")
+
+(main-c "support/jemacs-jerbuild-main.c")
+
+(ffi-symbols "support/jemacs-jerbuild-symbols.list")
+
+(extra-sources "support/repl_shim.c"
+               "vendor/jerboa-shell/ffi-shim.c"
+               "vendor/jerboa-shell/vendor/jerboa-coreutils/support/libcoreutils.c"
+               "support/jsh_coreutils_stubs.c"
+               "support/jsh_embed_crypto_stubs.c"
+               "support/jerboa_native_stubs.c"
+               "support/jemacs_ssh_agent_stubs.c"
+               "support/jemacs_tls_stubs.c")
+
+(pre-build "support/stage-for-jerbuild.sh")
diff --git a/.jerbuild.linux-arm64 b/.jerbuild.linux-arm64
new file mode 100644
index 0000000..26056a0
--- /dev/null
+++ b/.jerbuild.linux-arm64
@@ -0,0 +1,45 @@
+;; Native Linux arm64/aarch64 build config for jerboa-emacs TUI.
+;;
+;; Build on a Linux arm64 host with:
+;;   JERBUILD=/path/to/jerbuild jerbuild build --config .jerbuild.linux-arm64
+;;
+;; This uses installed/project-local jerbuild, cc, pkg-config, and vendored
+;; project dependencies. It has no dependency on an installed full Jerboa
+;; runtime or sibling Jerboa source checkout.
+
+(entry  "main.ss")
+(output "jemacs-linux-arm64")
+
+(cc "cc")
+
+(requires "jerbuild" "cc" "pkg-config" "libpcre2" "libvterm")
+
+(os-libs "-lm -ldl -lpthread -lutil -lncurses")
+
+(libdirs "lib"
+         "vendor/jerboa-shell/_jerbuild-stage"
+         "vendor/jerboa-shell/src"
+         "vendor/jerboa-shell/vendor/jerboa-coreutils/lib"
+         "vendor/jerboa-shell/vendor/jerboa-awk/lib"
+         "vendor/jerboa-shell/vendor/jerboa-sed/lib"
+         "vendor/jerboa-shell/vendor/jerboa-ssh/src"
+         "vendor/jerboa-shell/vendor/jerboa-sqlite/src"
+         "vendor/jerboa-shell/vendor/jerboa-crypto/src"
+         "vendor/jerboa-shell/vendor/jerboa-fuse/lib"
+         "vendor/jerboa-pcre2/lib"
+         "vendor/jerboa-scintilla/lib")
+
+(main-c "support/jemacs-jerbuild-main.c")
+
+(ffi-symbols "support/jemacs-jerbuild-symbols.list")
+
+(extra-sources "support/repl_shim.c"
+               "vendor/jerboa-shell/ffi-shim.c"
+               "vendor/jerboa-shell/vendor/jerboa-coreutils/support/libcoreutils.c"
+               "support/jsh_coreutils_stubs.c"
+               "support/jsh_embed_crypto_stubs.c"
+               "support/jerboa_native_stubs.c"
+               "support/jemacs_ssh_agent_stubs.c"
+               "support/jemacs_tls_stubs.c")
+
+(pre-build "support/stage-for-jerbuild.sh")
diff --git a/Makefile b/Makefile
index d916279..f1b222f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,24 @@
 VENDOR    ?= $(CURDIR)/vendor
 
+# Standalone jerbuild toolchain. This mirrors the bootstrap path used by
+# jerboa-code/jerboa-shell: prefer project-local tools, then sibling Jerboa,
+# then PATH, and otherwise fetch a release into .jerboa/bin.
+JERBOA_VERSION ?= v0.2.0
+JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin
+JERBUILD_TOOL ?= $(shell if [ -x ./jerbuild ] && [ -x ./jerboa ]; then echo ./jerbuild; \
+	elif [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ] && [ -x "$(JERBOA_TOOL_DIR)/jerboa" ]; then echo "$(JERBOA_TOOL_DIR)/jerbuild"; \
+	elif [ -x ../jerboa/dist/jerbuild ] && [ -x ../jerboa/dist/jerboa ]; then echo ../jerboa/dist/jerbuild; \
+	elif [ -x ../jerboa/jerbuild ]; then echo ../jerboa/jerbuild; \
+	elif command -v jerbuild >/dev/null 2>&1 && command -v jerboa >/dev/null 2>&1; then command -v jerbuild; \
+	else echo "$(JERBOA_TOOL_DIR)/jerbuild"; fi)
+JH = $(shell "$(JERBUILD_TOOL)" --jerboa-home 2>/dev/null)
+SSH ?= ssh
+SCP ?= scp
+LINUX_BUILD_HOST ?= linux.cons.io
+LINUX_BUILD_ROOT ?= ~/mine/jerboa-emacs
+LINUX_ARM64_BUILD_HOST ?=
+LINUX_ARM64_BUILD_ROOT ?= $(LINUX_BUILD_ROOT)
+
 JERBOA_REPO        ?= https://git.sr.ht/~lisp/jerboa
 JERBOA_SHELL_REPO  ?= https://git.sr.ht/~lisp/jerboa-shell
 JERBOA_PCRE2_REPO    ?= https://git.sr.ht/~lisp/jerboa-pcre2
@@ -122,7 +141,7 @@ SCHEME_CHEZ_DIR = $(shell if [ -n "$(SCHEME_MT)" ]; then ls -d "$(SCHEME_PREFIX)
 CHEZ_DIR ?= $(SCHEME_CHEZ_DIR)
 export CHEZ_DIR
 
-.PHONY: all vendor-deps vendor-update check-pcre2-system-deps check-vterm-system-deps deps ensure-vendor-jerboa-tools scintilla-vendor-deps scintilla-vendor-clean build rebuild run test-tier0 test-tier2 test-tier3 test-tier4 test-tier5 test-org test-extra test clean clean-generated \
+.PHONY: all vendor-deps vendor-update check-pcre2-system-deps check-vterm-system-deps deps ensure-vendor-jerboa-tools ensure-jerboa-tools build-jerbuild jemacs-jerbuild jerbuild-check scintilla-vendor-deps scintilla-vendor-clean build rebuild run test-tier0 test-tier2 test-tier3 test-tier4 test-tier5 test-org test-extra test clean clean-generated \
         test-org-duration test-org-element test-org-fold test-org-footnote \
         test-org-lint test-org-num test-org-property test-org-src test-org-tempo \
         test-vtscreen test-debug-repl test-qt test-qt-e2e build-qt binary-qt \
@@ -130,6 +149,8 @@ export CHEZ_DIR
         podman-deps ensure-podman-deps static-qt static-tui clean-podman check-podman check-root \
         linux-static-qt-podman linux-static-tui-podman \
         build-jemacs-qt-static build-jemacs-tui-static binary macos \
+        linux linux-amd64 linux-arm64 jemacs-linux-amd64 jemacs-linux-arm64 freebsd freebsd-amd64 jemacs-freebsd-amd64 \
+        jemacs-linux-amd64-local jemacs-linux-arm64-local jemacs-linux-amd64-remote jemacs-linux-arm64-remote \
         linux-tui linux-tui-local \
         linux-qt linux-qt-local \
         stress-run stress-run-static stress-test stress-burn stress-burn-static \
@@ -143,14 +164,20 @@ all:
 	@echo "Available targets:"
 	@echo "  vendor-deps    Clone required dependencies into vendor/"
 	@echo "  ensure-vendor-jerboa-tools Build vendor/jerboa/dist/{jerboa,jerbuild}"
+	@echo "  ensure-jerboa-tools Ensure standalone project-local jerboa/jerbuild are available"
 	@echo "  scintilla-vendor-deps Fetch/build Scintilla, Lexilla, and termbox archives"
 	@echo "  build          Translate src/*.ss → lib/*.sls (incremental)"
+	@echo "  build-jerbuild Translate src/*.ss → lib/*.sls using standalone jerbuild"
 	@echo "  rebuild        Force full retranslation"
 	@echo "  run            Build and run TUI editor"
 	@echo "  run-qt         Build and run Qt editor"
 	@echo "  binary         Build TUI binary (./jemacs) — embeds Scheme, links system libs"
+	@echo "  jemacs-jerbuild Build host TUI launcher via 'jerbuild build' (.jerbuild)"
 	@echo "  static-tui     Build fully static TUI binary via Podman (./jemacs)"
 	@echo "  static-qt      Build static jemacs-qt binary via Podman"
+	@echo "  linux-amd64    Build Linux amd64 TUI artifact via jerbuild (local Linux or SSH)"
+	@echo "  linux-arm64    Build Linux arm64 TUI artifact via jerbuild on a Linux arm64 host"
+	@echo "  freebsd-amd64  Build FreeBSD amd64 TUI artifact via jerbuild on FreeBSD"
 	@echo "  macos          Build native jemacs-qt binary for macOS"
 	@echo "  test           Full test suite (all tiers + org)"
 	@echo "  test-functional  250 dispatch-chain integration tests"
@@ -289,6 +316,41 @@ ensure-vendor-jerboa-tools: vendor-deps
 	  $(MAKE) -C $(JERBOA) jerboa; \
 	fi
 
+ensure-jerboa-tools:
+	@set -e; \
+	if [ -x "$(JERBUILD_TOOL)" ] && "$(JERBUILD_TOOL)" --jerboa-home >/dev/null 2>&1; then \
+	  echo "=== Using Jerboa toolchain: $(JERBUILD_TOOL) ==="; \
+	elif [ -x ./jerbuild ] && [ -x ./jerboa ] && ./jerbuild --jerboa-home >/dev/null 2>&1; then \
+	  echo "=== Using project-local ./jerbuild ==="; \
+	elif [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ] && [ -x "$(JERBOA_TOOL_DIR)/jerboa" ] && "$(JERBOA_TOOL_DIR)/jerbuild" --jerboa-home >/dev/null 2>&1; then \
+	  echo "=== Using downloaded Jerboa toolchain: $(JERBOA_TOOL_DIR) ==="; \
+	elif [ -x ../jerboa/dist/jerbuild ] && [ -x ../jerboa/dist/jerboa ] && ../jerboa/dist/jerbuild --jerboa-home >/dev/null 2>&1; then \
+	  echo "=== Using sibling Jerboa build: ../jerboa/dist/jerbuild ==="; \
+	elif [ -x ../jerboa/jerbuild ] && ../jerboa/jerbuild --jerboa-home >/dev/null 2>&1; then \
+	  echo "=== Using sibling Jerboa build: ../jerboa/jerbuild ==="; \
+	elif command -v jerbuild >/dev/null 2>&1 && command -v jerboa >/dev/null 2>&1 && jerbuild --jerboa-home >/dev/null 2>&1; then \
+	  echo "=== Using Jerboa toolchain from PATH: $$(command -v 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_TOOL)" --jerboa-home >/dev/null || { \
+	  echo "ERROR: Jerboa toolchain is unavailable; set JERBUILD_TOOL=/path/to/jerbuild or JERBOA_VERSION=<tag>"; \
+	  exit 1; \
+	}
+
+build-jerbuild: ensure-jerboa-tools vendor-deps
+	JERBUILD="$(JERBUILD_TOOL)" support/stage-for-jerbuild.sh
+
+jerbuild-check: ensure-jerboa-tools
+	@for cfg in .jerbuild .jerbuild.linux-amd64 .jerbuild.linux-arm64 .jerbuild.freebsd-amd64; do \
+	  echo "=== $$cfg ==="; \
+	  "$(JERBUILD_TOOL)" check --config "$$cfg"; \
+	done
+
+jemacs-jerbuild: ensure-jerboa-tools vendor-deps repl_shim.$(SHLIB_EXT) pty_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_scintilla_shim.$(SHLIB_EXT) jerboa_pcre2_shim.$(SHLIB_EXT)
+	JERBUILD="$(JERBUILD_TOOL)" "$(JERBUILD_TOOL)" build --config .jerbuild
+
 # Generate lib/jerboa-emacs/*.sls from src/jerboa-emacs/*.ss (incremental)
 build: deps
 	$(JERBUILD) src/ lib/
@@ -784,6 +846,67 @@ ensure-podman-deps: check-podman
 # Builds only jemacs-qt itself (~5-10 min). Output: ./jemacs-qt (static ELF).
 static-qt: linux-static-qt-podman
 
+linux: linux-amd64
+
+linux-amd64:
+	@case "$$(uname -s)-$$(uname -m)" in \
+	  Linux-x86_64|Linux-amd64) $(MAKE) jemacs-linux-amd64-local ;; \
+	  *) $(MAKE) jemacs-linux-amd64-remote ;; \
+	esac
+
+jemacs-linux-amd64-local: ensure-jerboa-tools vendor-deps
+	@case "$$(uname -s)-$$(uname -m)" in \
+	  Linux-x86_64|Linux-amd64) ;; \
+	  *) echo "ERROR: run locally on Linux amd64, or use 'make jemacs-linux-amd64-remote LINUX_BUILD_HOST=host'." >&2; exit 1 ;; \
+	esac
+	JERBUILD="$(JERBUILD_TOOL)" "$(JERBUILD_TOOL)" build --config .jerbuild.linux-amd64
+	@ls -lh jemacs-linux-amd64
+	@file jemacs-linux-amd64
+
+jemacs-linux-amd64-remote:
+	$(SSH) $(LINUX_BUILD_HOST) 'cd $(LINUX_BUILD_ROOT) && git pull --ff-only && make linux-amd64'
+	$(SCP) $(LINUX_BUILD_HOST):$(LINUX_BUILD_ROOT)/jemacs-linux-amd64 ./jemacs-linux-amd64
+	@ls -lh jemacs-linux-amd64
+	@file jemacs-linux-amd64
+
+linux-arm64:
+	@case "$$(uname -s)-$$(uname -m)" in \
+	  Linux-aarch64|Linux-arm64) $(MAKE) jemacs-linux-arm64-local ;; \
+	  *) test -n "$(LINUX_ARM64_BUILD_HOST)" || { echo "ERROR: run on Linux arm64 or set LINUX_ARM64_BUILD_HOST for SSH target-host build." >&2; exit 1; }; \
+	     $(MAKE) jemacs-linux-arm64-remote ;; \
+	esac
+
+jemacs-linux-arm64-local: ensure-jerboa-tools vendor-deps
+	@case "$$(uname -s)-$$(uname -m)" in \
+	  Linux-aarch64|Linux-arm64) ;; \
+	  *) echo "ERROR: run locally on Linux arm64, or set LINUX_ARM64_BUILD_HOST and use make linux-arm64." >&2; exit 1 ;; \
+	esac
+	JERBUILD="$(JERBUILD_TOOL)" "$(JERBUILD_TOOL)" build --config .jerbuild.linux-arm64
+	@ls -lh jemacs-linux-arm64
+	@file jemacs-linux-arm64
+
+jemacs-linux-arm64-remote:
+	$(SSH) $(LINUX_ARM64_BUILD_HOST) 'cd $(LINUX_ARM64_BUILD_ROOT) && git pull --ff-only && make linux-arm64'
+	$(SCP) $(LINUX_ARM64_BUILD_HOST):$(LINUX_ARM64_BUILD_ROOT)/jemacs-linux-arm64 ./jemacs-linux-arm64
+	@ls -lh jemacs-linux-arm64
+	@file jemacs-linux-arm64
+
+jemacs-linux-amd64: linux-amd64
+jemacs-linux-arm64: linux-arm64
+
+freebsd: freebsd-amd64
+
+freebsd-amd64: ensure-jerboa-tools vendor-deps
+	@case "$$(uname -s)-$$(uname -m)" in \
+	  FreeBSD-amd64|FreeBSD-x86_64) ;; \
+	  *) echo "ERROR: build freebsd-amd64 natively on a FreeBSD amd64 host." >&2; exit 1 ;; \
+	esac
+	JERBUILD="$(JERBUILD_TOOL)" "$(JERBUILD_TOOL)" build --config .jerbuild.freebsd-amd64
+	@ls -lh jemacs-freebsd-amd64
+	@file jemacs-freebsd-amd64
+
+jemacs-freebsd-amd64: freebsd-amd64
+
 clean-podman: check-podman
 	-$(PODMAN) run --rm $(PODMAN_RUN_FLAGS) -v $(CURDIR):/src:z alpine sh -c "rm -rf /src/jemacs-qt /src/jemacs-qt.boot /src/qt-main.so /src/qt-main.wpo /src/jemacs-qt-all.so 2>/dev/null; true"
 
diff --git a/README.md b/README.md
index 74cb229..d576b3b 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,49 @@ All commands run from the project root.
 toolchain at `vendor/jerboa/dist/{jerboa,jerbuild}`. The editor vendors and
 builds its native Scheme/FFI dependencies under `vendor/`.
 
+For systems that have the standalone Jerboa release toolchain but no full
+Jerboa runtime checkout, use:
+
+```bash
+make ensure-jerboa-tools
+make build-jerbuild
+make jerbuild-check
+```
+
+`ensure-jerboa-tools` follows the same convention as `jerboa-code` and
+`jerboa-shell`: it prefers project-local tools, a sibling `../jerboa` build, or
+`PATH`, and otherwise downloads `jerboa-${JERBOA_VERSION}` into `.jerboa/bin`.
+The `.jerbuild*` configs describe native target-host builds for macOS/Linux/
+FreeBSD without requiring an installed full Jerboa runtime.
+
+### Cross and target builds
+
+Linux and FreeBSD target artifacts are built with `jerbuild` on the target OS.
+From macOS, the Linux amd64 target uses SSH by default:
+
+```bash
+make linux-amd64   # writes ./jemacs-linux-amd64
+```
+
+The default SSH host is `linux.cons.io`; override it with
+`LINUX_BUILD_HOST=host` and `LINUX_BUILD_ROOT=/path/to/jerboa-emacs`.
+
+Linux arm64 and FreeBSD follow the `jerboa-shell` pattern and are built on a
+matching target host:
+
+```bash
+make linux-arm64   # on Linux arm64, or set LINUX_ARM64_BUILD_HOST
+make freebsd-amd64 # writes ./jemacs-freebsd-amd64
+```
+
+Native target-host jerbuild configs are also available:
+
+```bash
+jerbuild build --config .jerbuild.linux-amd64
+jerbuild build --config .jerbuild.linux-arm64
+jerbuild build --config .jerbuild.freebsd-amd64
+```
+
 ### Vendor dependencies
 
 ```bash
diff --git a/build-binary.ss b/build-binary.ss
index 5fd23ba..8347bce 100644
--- a/build-binary.ss
+++ b/build-binary.ss
@@ -49,6 +49,13 @@
         (if (eof-object? line) default line))
       default)))
 
+(define (env-nonempty name)
+  (let ((v (getenv name)))
+    (and v (not (string=? v "")) v)))
+
+(define (env-or-shell name cmd default)
+  (or (env-nonempty name) (shell-output cmd default)))
+
 ;; --- Locate Chez install directory ---
 (define chez-dir
   (or (getenv "CHEZ_DIR")
@@ -133,6 +140,11 @@
   (let ((v (getenv "JEMACS_STATIC")))
     (and v (not (string=? v "0")) (not (string=? v "")))))
 
+(define cc (or (env-nonempty "CC") "gcc"))
+(define cxx (or (env-nonempty "CXX") "g++"))
+(define cflags (or (env-nonempty "CFLAGS") "-O2"))
+(define output-name (or (env-nonempty "JEMACS_OUTPUT") "jemacs"))
+
 ;; Check critical dependencies
 (for-each
   (lambda (path label)
@@ -265,9 +277,9 @@
 (printf "[5/6] Compiling C sources...~n")
 
 ;; pcre2 shim
-(let* ((pcre2-cflags (shell-output "pkg-config --cflags libpcre2-8" "-I/usr/include"))
-       (cmd (format "gcc -c -O2 -o jemacs-pcre2-shim.o ~a/jerboa_pcre2_shim.c ~a -Wall 2>&1"
-                    pcre2-root pcre2-cflags)))
+(let* ((pcre2-cflags (env-or-shell "PCRE2_CFLAGS" "pkg-config --cflags libpcre2-8" "-I/usr/include"))
+       (cmd (format "~a -c ~a -o jemacs-pcre2-shim.o ~a/jerboa_pcre2_shim.c ~a -Wall 2>&1"
+                    cc cflags pcre2-root pcre2-cflags)))
   (unless (= 0 (system cmd))
     (display "Error: pcre2 shim compilation failed\n")
     (exit 1)))
@@ -275,28 +287,30 @@
 ;; Static-only C sources
 (when jemacs-static?
   ;; jsh FFI shim
-  (let* ((cmd (format "gcc -c -O2 -o jemacs-jsh-ffi.o ~a/ffi-shim.c -Wall 2>&1"
-                      jsh-root)))
+  (let* ((cmd (format "~a -c ~a -o jemacs-jsh-ffi.o ~a/ffi-shim.c -Wall 2>&1"
+                      cc cflags jsh-root)))
     (unless (= 0 (system cmd))
       (display "Error: jsh ffi-shim.c compilation failed\n")
       (exit 1)))
 
   ;; libcoreutils.c (terminal/raw-mode symbols)
-  (let* ((cmd (format "gcc -c -O2 -o jemacs-libcoreutils.o ~a -Wall 2>&1"
-                      libcoreutils-c)))
+  (let* ((cmd (format "~a -c ~a -o jemacs-libcoreutils.o ~a -Wall 2>&1"
+                      cc cflags libcoreutils-c)))
     (unless (= 0 (system cmd))
       (display "Error: libcoreutils.c compilation failed\n")
       (exit 1)))
 
   ;; jsh coreutils stubs (Rust uutils symbols — stubs for static binary)
-  (let* ((cmd "gcc -c -O2 -o jemacs-jsh-coreutils-stubs.o support/jsh_coreutils_stubs.c -Wall 2>&1"))
+  (let* ((cmd (format "~a -c ~a -o jemacs-jsh-coreutils-stubs.o support/jsh_coreutils_stubs.c -Wall 2>&1"
+                      cc cflags)))
     (unless (= 0 (system cmd))
       (display "Error: jsh_coreutils_stubs.c compilation failed\n")
       (exit 1)))
 
   ;; jerboa native Rust crypto stubs. The TUI binary does not ship
   ;; libjerboa_native, but some vendored modules bind these symbols at load.
-  (let* ((cmd "gcc -c -O2 -o jemacs-jerboa-native-stubs.o support/jerboa_native_stubs.c -Wall 2>&1"))
+  (let* ((cmd (format "~a -c ~a -o jemacs-jerboa-native-stubs.o support/jerboa_native_stubs.c -Wall 2>&1"
+                      cc cflags)))
     (unless (= 0 (system cmd))
       (display "Error: jerboa_native_stubs.c compilation failed\n")
       (exit 1)))
@@ -307,8 +321,8 @@
   (let* ((embed-src (format "~a/embed-crypto.c" jsh-root))
          (fallback-src "support/jsh_embed_crypto_stubs.c")
          (src (if (file-exists? embed-src) embed-src fallback-src))
-         (cmd (format "gcc -c -O2 -o jemacs-embed-crypto.o ~a -I~a -Wall 2>&1"
-                      src jsh-root)))
+         (cmd (format "~a -c ~a -o jemacs-embed-crypto.o ~a -I~a -Wall 2>&1"
+                      cc cflags src jsh-root)))
     (unless (= 0 (system cmd))
       (display "Error: embed-crypto.c compilation failed\n")
       (exit 1)))
@@ -321,34 +335,37 @@
         (fprintf out "void jerboa_ssh_agent_stop(void) {}~n")
         (fprintf out "int jerboa_ssh_agent_is_running(void) { return 0; }~n"))
       'replace)
-    (let ((cmd "gcc -c -O2 -o jemacs-ssh-agent-stub.o jemacs-ssh-agent-stub.c -Wall 2>&1"))
+    (let ((cmd (format "~a -c ~a -o jemacs-ssh-agent-stub.o jemacs-ssh-agent-stub.c -Wall 2>&1"
+                       cc cflags)))
       (unless (= 0 (system cmd))
         (display "Error: ssh-agent stub compilation failed\n")
         (exit 1))))
 
   ;; pty shim
-  (let* ((cmd "gcc -c -O2 -o jemacs-pty-shim.o support/pty_shim.c -Wall 2>&1"))
+  (let* ((cmd (format "~a -c ~a -o jemacs-pty-shim.o support/pty_shim.c -Wall 2>&1"
+                      cc cflags)))
     (unless (= 0 (system cmd))
       (display "Error: pty_shim.c compilation failed\n")
       (exit 1)))
 
   ;; vterm shim
-  (let* ((cmd "gcc -c -O2 -o jemacs-vterm-shim.o support/vterm_shim.c -Wall 2>&1"))
+  (let* ((cmd (format "~a -c ~a -o jemacs-vterm-shim.o support/vterm_shim.c -Wall 2>&1"
+                      cc cflags)))
     (unless (= 0 (system cmd))
       (display "Error: vterm_shim.c compilation failed\n")
       (exit 1)))
 
   ;; repl shim
-  (let* ((cmd (format "gcc -c -O2 -o jemacs-repl-shim.o support/repl_shim.c -I~a -Wall 2>&1"
-                       chez-dir)))
+  (let* ((cmd (format "~a -c ~a -o jemacs-repl-shim.o support/repl_shim.c -I~a -Wall 2>&1"
+                       cc cflags chez-dir)))
     (unless (= 0 (system cmd))
       (display "Error: repl_shim.c compilation failed\n")
       (exit 1)))
 
   ;; jerboa landlock shim
   (let* ((jerboa-root (path-parent jerboa-dir))
-         (cmd (format "gcc -c -O2 -o jemacs-jerboa-landlock.o ~a/support/landlock-shim.c -Wall 2>&1"
-                      jerboa-root)))
+         (cmd (format "~a -c ~a -o jemacs-jerboa-landlock.o ~a/support/landlock-shim.c -Wall 2>&1"
+                      cc cflags jerboa-root)))
     (unless (= 0 (system cmd))
       (display "Error: landlock-shim.c compilation failed\n")
       (exit 1)))
@@ -358,8 +375,8 @@
   (let* ((sci-include (format "-I~a/scintilla/include -I~a/scintilla/src -I~a/scintilla/termbox -I~a/scintilla/termbox/termbox_next/src -I~a/lexilla/include"
                                sci-vendor-dir sci-vendor-dir sci-vendor-dir sci-vendor-dir sci-vendor-dir))
          (sci-shim-c  (repo-path "jerboa_scintilla_shim.c"))
-         (cmd (format "gcc -c -O2 -o jemacs-sci-shim.o ~a ~a -Wall 2>&1"
-                      sci-shim-c sci-include)))
+         (cmd (format "~a -c ~a -o jemacs-sci-shim.o ~a ~a -Wall 2>&1"
+                      cc cflags sci-shim-c sci-include)))
     (unless (= 0 (system cmd))
       (display "Error: jerboa_scintilla_shim.c compilation failed\n")
       (exit 1)))
@@ -408,8 +425,8 @@ echo OK"
       (exit 1))
     (let* ((count (shell-output "grep -c 'Sforeign_symbol' tui_static_symbols.c" "0")))
       (printf "  Generated tui_static_symbols.c with ~a symbol registrations~n" count))
-    (let* ((cmd (format "gcc -c -O2 -o tui_static_symbols.o tui_static_symbols.c -I~a -Wall 2>&1"
-                        include-dir)))
+    (let* ((cmd (format "~a -c ~a -o tui_static_symbols.o tui_static_symbols.c -I~a -Wall 2>&1"
+                        cc cflags include-dir)))
       (unless (= 0 (system cmd))
         (display "Error: tui_static_symbols.c compilation failed\n")
         (exit 1)))))
@@ -418,20 +435,20 @@ echo OK"
 ;; Linked with -rdynamic so foreign-procedure finds them via dlopen(NULL).
 (when (not jemacs-static?)
   ;; repl shim (repl_poll, repl_nanosleep, etc.)
-  (let* ((cmd (format "gcc -c -O2 -o jemacs-repl-shim.o support/repl_shim.c -I~a -Wall 2>&1"
-                       chez-dir)))
+  (let* ((cmd (format "~a -c ~a -o jemacs-repl-shim.o support/repl_shim.c -I~a -Wall 2>&1"
+                       cc cflags chez-dir)))
     (unless (= 0 (system cmd))
       (display "Error: repl_shim.c (dynamic) compilation failed\n")
       (exit 1)))
   ;; jsh FFI shim (ffi_file_type, ffi_*, etc.)
-  (let* ((cmd (format "gcc -c -O2 -o jemacs-jsh-ffi.o ~a/ffi-shim.c -Wall 2>&1"
-                      jsh-root)))
+  (let* ((cmd (format "~a -c ~a -o jemacs-jsh-ffi.o ~a/ffi-shim.c -Wall 2>&1"
+                      cc cflags jsh-root)))
     (unless (= 0 (system cmd))
       (display "Error: jsh ffi-shim.c (dynamic) compilation failed\n")
       (exit 1)))
   ;; libcoreutils (coreutils_raw_mode_*, coreutils_terminal_*, etc.)
-  (let* ((cmd (format "gcc -c -O2 -o jemacs-libcoreutils.o ~a -Wall 2>&1"
-                      libcoreutils-c)))
+  (let* ((cmd (format "~a -c ~a -o jemacs-libcoreutils.o ~a -Wall 2>&1"
+                      cc cflags libcoreutils-c)))
     (unless (= 0 (system cmd))
       (display "Error: libcoreutils.c (dynamic) compilation failed\n")
       (exit 1)))
@@ -439,8 +456,8 @@ echo OK"
   (let* ((embed-src (format "~a/embed-crypto.c" jsh-root))
          (fallback-src "support/jsh_embed_crypto_stubs.c")
          (src (if (file-exists? embed-src) embed-src fallback-src))
-         (cmd (format "gcc -c -O2 -o jemacs-embed-crypto.o ~a -I~a -Wall 2>&1"
-                      src jsh-root)))
+         (cmd (format "~a -c ~a -o jemacs-embed-crypto.o ~a -I~a -Wall 2>&1"
+                      cc cflags src jsh-root)))
     (unless (= 0 (system cmd))
       (display "Error: embed-crypto.c (dynamic) compilation failed\n")
       (exit 1)))
@@ -452,17 +469,20 @@ echo OK"
         (fprintf out "void jerboa_ssh_agent_stop(void) {}~n")
         (fprintf out "int jerboa_ssh_agent_is_running(void) { return 0; }~n"))
       'replace)
-    (let ((cmd "gcc -c -O2 -o jemacs-ssh-agent-stub.o jemacs-ssh-agent-stub.c -Wall 2>&1"))
+    (let ((cmd (format "~a -c ~a -o jemacs-ssh-agent-stub.o jemacs-ssh-agent-stub.c -Wall 2>&1"
+                       cc cflags)))
       (unless (= 0 (system cmd))
         (display "Error: ssh-agent stub (dynamic) compilation failed\n")
         (exit 1))))
   ;; jsh coreutils stubs (jsh_* Rust uutils symbols)
-  (let* ((cmd "gcc -c -O2 -o jemacs-jsh-coreutils-stubs.o support/jsh_coreutils_stubs.c -Wall 2>&1"))
+  (let* ((cmd (format "~a -c ~a -o jemacs-jsh-coreutils-stubs.o support/jsh_coreutils_stubs.c -Wall 2>&1"
+                      cc cflags)))
     (unless (= 0 (system cmd))
       (display "Error: jsh_coreutils_stubs.c (dynamic) compilation failed\n")
       (exit 1)))
   ;; jerboa native Rust crypto stubs
-  (let* ((cmd "gcc -c -O2 -o jemacs-jerboa-native-stubs.o support/jerboa_native_stubs.c -Wall 2>&1"))
+  (let* ((cmd (format "~a -c ~a -o jemacs-jerboa-native-stubs.o support/jerboa_native_stubs.c -Wall 2>&1"
+                      cc cflags)))
     (unless (= 0 (system cmd))
       (display "Error: jerboa_native_stubs.c (dynamic) compilation failed\n")
       (exit 1)))
@@ -487,15 +507,16 @@ echo OK"
         (fprintf out "int jerboa_tls_get_fd(uint64_t s) { return -1; }~n")
         (fprintf out "/* jerboa_last_error is provided by jerboa_native_stubs.c. */~n"))
       'replace)
-    (let ((cmd "gcc -c -O2 -o jemacs-tls-stubs.o jemacs-tls-stubs.c -Wall 2>&1"))
+    (let ((cmd (format "~a -c ~a -o jemacs-tls-stubs.o jemacs-tls-stubs.c -Wall 2>&1"
+                       cc cflags)))
       (unless (= 0 (system cmd))
         (display "Error: TLS stubs compilation failed\n")
         (exit 1)))))
 
 ;; jemacs-main.c
 (let* ((static-flag (if jemacs-static? "-DJEMACS_STATIC_BUILD" ""))
-       (cmd (format "gcc -c -O2 ~a -o jemacs-main.o jemacs-main.c -I~a -I. -Wall 2>&1"
-                    static-flag chez-dir)))
+       (cmd (format "~a -c ~a ~a -o jemacs-main.o jemacs-main.c -I~a -I. -Wall 2>&1"
+                    cc cflags static-flag chez-dir)))
   (unless (= 0 (system cmd))
     (display "Error: jemacs-main.c compilation failed\n")
     (exit 1)))
@@ -504,14 +525,16 @@ echo OK"
 (printf "[6/6] Linking native binary...~n")
 (if jemacs-static?
   ;; ─── Static link (Podman musl build) ───────────────────────────────────
-  (let* ((pcre2-libs  (shell-output "pkg-config --static --libs libpcre2-8" "-lpcre2-8"))
-         (ncurses-libs (shell-output "pkg-config --static --libs ncurses" "-lncurses"))
-         (vterm-libs   (shell-output "pkg-config --static --libs vterm" "-lvterm"))
+  (let* ((pcre2-libs  (env-or-shell "PCRE2_LIBS" "pkg-config --static --libs libpcre2-8" "-lpcre2-8"))
+         (ncurses-libs (env-or-shell "NCURSES_LIBS" "pkg-config --static --libs ncurses" "-lncurses"))
+         (vterm-libs   (env-or-shell "VTERM_LIBS" "pkg-config --static --libs vterm" "-lvterm"))
          (sci-a   (format "~a/scintilla/bin/scintilla.a" sci-vendor-dir))
          (lex-a   (format "~a/lexilla/bin/liblexilla.a" sci-vendor-dir))
          (tbx-a   (format "~a/scintilla/termbox/termbox_next/bin/termbox.a" sci-vendor-dir))
          (jsh-coreutils-lib (or (getenv "JSH_COREUTILS_LIB") ""))
-         (cmd (format "g++ -static -Wl,--export-dynamic -o jemacs \
+         (static-ldflags (or (env-nonempty "JEMACS_STATIC_LDFLAGS") "-static -Wl,--export-dynamic"))
+         (os-libs (or (env-nonempty "JEMACS_OS_LIBS") "-lm -ldl -lpthread -luuid -lstdc++"))
+         (cmd (format "~a ~a -o ~a \
 jemacs-main.o jemacs-pcre2-shim.o jemacs-jsh-ffi.o \
 jemacs-libcoreutils.o jemacs-jsh-coreutils-stubs.o \
 jemacs-jerboa-native-stubs.o jemacs-embed-crypto.o jemacs-ssh-agent-stub.o \
@@ -522,11 +545,13 @@ tui_static_symbols.o \
 -L~a -lkernel -llz4 -lz \
 -Wl,--whole-archive ~a ~a ~a -Wl,--no-whole-archive \
 ~a ~a ~a \
--lm -ldl -lpthread -luuid -lstdc++ 2>&1"
+~a 2>&1"
+                      cxx static-ldflags output-name
                       jsh-coreutils-lib
                       chez-dir
                       sci-a lex-a tbx-a
-                      pcre2-libs ncurses-libs vterm-libs)))
+                      pcre2-libs ncurses-libs vterm-libs
+                      os-libs)))
     (printf "  ~a~n" cmd)
     (unless (= 0 (system cmd))
       (display "Error: Static link failed\n")
@@ -534,21 +559,25 @@ tui_static_symbols.o \
   ;; ─── Dynamic link (default local build) ────────────────────────────────
   ;; -rdynamic: export all compiled-in symbols to dlsym(RTLD_DEFAULT) so
   ;; Chez's foreign-procedure finds them via dlopen(NULL)/dlopen("").
-  (let* ((pcre2-libs   (shell-output "pkg-config --libs libpcre2-8" "-lpcre2-8"))
-         (ncurses-libs (shell-output "pkg-config --libs ncurses" "-lncurses"))
+  (let* ((pcre2-libs   (env-or-shell "PCRE2_LIBS" "pkg-config --libs libpcre2-8" "-lpcre2-8"))
+         (ncurses-libs (env-or-shell "NCURSES_LIBS" "pkg-config --libs ncurses" "-lncurses"))
          (iconv-libs   (if darwin? "-liconv" ""))
-         (cmd (format "gcc -rdynamic -o jemacs \
+         (dynamic-ldflags (or (env-nonempty "JEMACS_DYNAMIC_LDFLAGS") "-rdynamic"))
+         (os-libs (or (env-nonempty "JEMACS_OS_LIBS")
+                      (format "-lm -ldl -lpthread ~a" iconv-libs)))
+         (cmd (format "~a ~a -o ~a \
 jemacs-main.o jemacs-pcre2-shim.o jemacs-repl-shim.o \
 jemacs-jsh-ffi.o jemacs-libcoreutils.o jemacs-embed-crypto.o \
 jemacs-ssh-agent-stub.o jemacs-jsh-coreutils-stubs.o \
 jemacs-jerboa-native-stubs.o jemacs-tls-stubs.o \
 -L~a -lkernel -llz4 -lz \
 ~a ~a \
--lm -ldl -lpthread ~a 2>&1"
-                      chez-dir pcre2-libs ncurses-libs iconv-libs)))
+~a 2>&1"
+                      cc dynamic-ldflags output-name
+                      chez-dir pcre2-libs ncurses-libs os-libs)))
     (printf "  ~a~n" cmd)
     (unless (= 0 (system cmd))
       (display "Error: Dynamic link failed\n")
       (exit 1))))
 
-(printf "~nDone! Binary: ./jemacs~n")
+(printf "~nDone! Binary: ./~a~n" output-name)
diff --git a/support/ensure-jerboa.sh b/support/ensure-jerboa.sh
new file mode 100755
index 0000000..c5acc97
--- /dev/null
+++ b/support/ensure-jerboa.sh
@@ -0,0 +1,119 @@
+#!/bin/sh
+# Bootstrap a project-local Jerboa toolchain from SourceHut release artifacts.
+#
+# Usage:
+#   support/ensure-jerboa.sh v0.2.0 .jerboa/bin
+#
+# Override artifact resolution with:
+#   JERBOA_RELEASE_BASE=https://example.org/releases/v0.2.0
+#   JERBOA_RELEASE_REPO=~lisp/jerboa
+#   JERBOA_RELEASE_TARGET=linux-amd64
+
+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_TOOL=/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}"
diff --git a/support/jemacs-jerbuild-main.c b/support/jemacs-jerbuild-main.c
new file mode 100644
index 0000000..bcbdf9d
--- /dev/null
+++ b/support/jemacs-jerbuild-main.c
@@ -0,0 +1,154 @@
+/*
+ * Custom entry point for jemacs binaries built by `jerbuild build`.
+ *
+ * It matches jemacs-main.c's runtime behavior but uses jerbuild's generated
+ * header names: petite_boot.h, scheme_boot.h, program_boot.h, ffi_symbols.h.
+ * The program image is loaded with Sscheme_script so Chez threads keep working.
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <sys/stat.h>
+#ifdef __linux__
+#include <sys/mman.h>
+#elif defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#elif defined(__APPLE__)
+#include <mach-o/dyld.h>
+#endif
+
+#include "scheme.h"
+#include "petite_boot.h"
+#include "scheme_boot.h"
+#include "program_boot.h"
+#include "ffi_symbols.h"
+
+static int write_all(int fd, const unsigned char *data, unsigned int size) {
+    const unsigned char *cursor = data;
+    size_t remaining = size;
+
+    while (remaining > 0) {
+        ssize_t written = write(fd, cursor, remaining);
+        if (written < 0) {
+            if (errno == EINTR)
+                continue;
+            return -1;
+        }
+        if (written == 0)
+            return -1;
+        cursor += written;
+        remaining -= (size_t)written;
+    }
+
+    return 0;
+}
+
+static int resolve_exe(char *buf, size_t size) {
+#ifdef __APPLE__
+    uint32_t len = (uint32_t)size;
+    if (_NSGetExecutablePath(buf, &len) != 0)
+        return 0;
+    {
+        char real_buf[4096];
+        char *resolved = realpath(buf, real_buf);
+        if (resolved) {
+            strncpy(buf, resolved, size - 1);
+            buf[size - 1] = '\0';
+        }
+    }
+    return 1;
+#elif defined(__FreeBSD__)
+    ssize_t len = readlink("/proc/curproc/file", buf, size - 1);
+    if (len > 0) {
+        buf[len] = '\0';
+        return 1;
+    }
+    {
+        int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
+        size_t out_len = size;
+        if (sysctl(mib, 4, buf, &out_len, NULL, 0) == 0 && out_len > 0) {
+            buf[out_len - 1] = '\0';
+            return 1;
+        }
+    }
+    return 0;
+#else
+    ssize_t len = readlink("/proc/self/exe", buf, size - 1);
+    if (len > 0) {
+        buf[len] = '\0';
+        return 1;
+    }
+    return 0;
+#endif
+}
+
+int main(int argc, char *argv[]) {
+    char exe_buf[4096];
+    char exe_dir[4096];
+
+    if (resolve_exe(exe_buf, sizeof(exe_buf))) {
+        setenv("JEMACS_EXE", exe_buf, 1);
+        strncpy(exe_dir, exe_buf, sizeof(exe_dir) - 1);
+        exe_dir[sizeof(exe_dir) - 1] = '\0';
+        char *dir = dirname(exe_dir);
+        if (!getenv("JERBOA_SCINTILLA_LIB"))
+            setenv("JERBOA_SCINTILLA_LIB", dir, 0);
+        if (!getenv("JERBOA_PCRE2_LIB"))
+            setenv("JERBOA_PCRE2_LIB", dir, 0);
+        if (!getenv("JERBOA_EMACS_SUPPORT"))
+            setenv("JERBOA_EMACS_SUPPORT", dir, 0);
+        if (!getenv("JEMACS_BINARY_DIR"))
+            setenv("JEMACS_BINARY_DIR", dir, 0);
+    }
+
+    char prog_path[4096];
+#ifdef __linux__
+    int fd = memfd_create("jemacs-program", MFD_CLOEXEC);
+    if (fd < 0) {
+        perror("memfd_create");
+        return 1;
+    }
+#else
+    const char *tmpdir = getenv("TMPDIR");
+    if (!tmpdir || !tmpdir[0]) tmpdir = "/tmp";
+    snprintf(prog_path, sizeof(prog_path), "%s/jemacs-program-XXXXXX", tmpdir);
+    int fd = mkstemp(prog_path);
+    if (fd < 0) {
+        perror("mkstemp");
+        return 1;
+    }
+#endif
+
+    if (write_all(fd, program_boot_data, program_boot_size) != 0) {
+        perror("write embedded program");
+        close(fd);
+#ifndef __linux__
+        unlink(prog_path);
+#endif
+        return 1;
+    }
+#ifdef __linux__
+    snprintf(prog_path, sizeof(prog_path), "/proc/self/fd/%d", fd);
+#endif
+
+    Sscheme_init(NULL);
+    Sregister_boot_file_bytes("petite", (void *)petite_boot_data, petite_boot_size);
+    Sregister_boot_file_bytes("scheme", (void *)scheme_boot_data, scheme_boot_size);
+    Sbuild_heap(NULL, NULL);
+    register_ffi_symbols();
+
+    int status = Sscheme_script(prog_path, argc, (const char **)argv);
+
+    close(fd);
+#ifndef __linux__
+    unlink(prog_path);
+#endif
+    Sscheme_deinit();
+    return status;
+}
diff --git a/support/jemacs-jerbuild-symbols.list b/support/jemacs-jerbuild-symbols.list
new file mode 100644
index 0000000..387354e
--- /dev/null
+++ b/support/jemacs-jerbuild-symbols.list
@@ -0,0 +1,251 @@
+repl_poll
+repl_nanosleep