build: static main.c (JERBOA_STATIC) + per-platform FFI symbol generation

ober

c4caee7a283bef3779a572b555fd4f0fd11001f7

diff --git a/.gitignore b/.gitignore
index 07d23d3..08f6c9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,6 @@ secmon-agent.sha256
 secmon-program.c
 secmon-agent.boot
 jerboa-stage/
+
+# generated FFI symbol list (build artifact)
+support/ffi-symbols.gen
diff --git a/.jerbuild b/.jerbuild
index 2164dd3..aeb1fe2 100644
--- a/.jerbuild
+++ b/.jerbuild
@@ -3,10 +3,12 @@
 (entry "bin/agent.ss")
 (output "secmon-agent")
 (libdirs "lib")
-;; Statically register the linked-in jerboa-native symbols via Sforeign_symbol
-;; so the (std crypto native-rust) + (std db sqlite-native) FFI calls resolve
-;; in this static binary (dlsym(RTLD_DEFAULT) can't see dead-stripped archive
-;; symbols). The list also forces those archive members to be linked in.
-(ffi-symbols "support/ffi-symbols.list")
+;; Static binary: a custom main.c sets JERBOA_STATIC=1 so the bundled std
+;; modules use the linked-in jerboa-native symbols (registered below via
+;; Sforeign_symbol) instead of dlopen of libjerboa_native.{so,dylib}, which
+;; does not exist in a static build. The list is regenerated per-platform by
+;; the Makefile (support/gen-ffi-symbols.sh) before the final link.
+(main-c "support/static-main.c")
+(ffi-symbols "support/ffi-symbols.gen")
 (rust-crates
   ("@bundle/jerboa-native-rs/Cargo.toml" features: "crypto,sqlite"))
diff --git a/Makefile b/Makefile
index 20b3fae..a09b2c1 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,7 @@ endif
 
 LIBDIRS := --libdirs lib:$(JH)/lib
 JEXEC   := $(JERBUILD) exec $(LIBDIRS)
+NATIVE_A := $(JH)/jerboa-native-rs/target/release/libjerboa_native.a
 BIN     := secmon-agent
 BIN_DIR := $(HOME)/.local/bin
 
@@ -32,9 +33,14 @@ help:
 	@echo "  make test              Run the test suite"
 
 # Standalone native binary via .jerbuild (entry bin/agent.ss -> secmon-agent).
-# Cargo-builds jerboa-native (crypto+sqlite) from jerbuild's bundled crate and
-# registers its FFI symbols (support/ffi-symbols.list) into the static binary.
+# Two passes: the first cargo-builds jerboa-native (crypto,sqlite) into
+# jerbuild's cache; we then regenerate the FFI symbol list from that archive
+# (platform-correct) and relink. A static main.c sets JERBOA_STATIC=1 so the
+# std modules use the registered symbols instead of dlopen.
 binary:
+	@touch support/ffi-symbols.gen
+	$(JERBUILD) build
+	sh support/gen-ffi-symbols.sh
 	$(JERBUILD) build
 
 build: binary
diff --git a/support/ffi-symbols.list b/support/ffi-symbols.list
deleted file mode 100644
index a1672aa..0000000
--- a/support/ffi-symbols.list
+++ /dev/null
@@ -1,97 +0,0 @@
-# FFI symbols statically registered via Sforeign_symbol() in the secmon-agent binary.
-# One symbol per line. Consumed by .jerbuild (ffi-symbols); jerbuild registers them
-# so the (std crypto native-rust) + (std db sqlite-native) FFI calls resolve in the
-# static binary (dlsym(RTLD_DEFAULT) can't see dead-stripped archive symbols).
-# Generated from libjerboa_native.a (features: crypto,sqlite).
-
-jerboa_aead_open
-jerboa_aead_seal
-jerboa_antidebug_check_all
-jerboa_antidebug_check_breakpoint
-jerboa_antidebug_check_ld_preload
-jerboa_antidebug_check_tracer
-jerboa_antidebug_ptrace
-jerboa_antidebug_timing_check
-jerboa_aproc_close
-jerboa_aproc_dup
-jerboa_aproc_killpg
-jerboa_aproc_set_nonblock
-jerboa_aproc_spawn
-jerboa_aproc_spawn_pty
-jerboa_aproc_wait4
-jerboa_argon2id_hash
-jerboa_argon2id_verify
-jerboa_chacha20_open
-jerboa_chacha20_seal
-jerboa_deflate
-jerboa_freebsd_is_traced
-jerboa_freebsd_process_count
-jerboa_gunzip
-jerboa_gzip
-jerboa_hkdf_sha256
-jerboa_hmac_sha256
-jerboa_hmac_sha256_verify
-jerboa_inflate
-jerboa_integrity_hash_file
-jerboa_integrity_hash_region
-jerboa_integrity_hash_self
-jerboa_integrity_sign_verify
-jerboa_integrity_verify_hash
-jerboa_kill_probe
-jerboa_last_error
-jerboa_md5
-jerboa_mlockall
-jerboa_pbkdf2_derive
-jerboa_pbkdf2_verify
-jerboa_prctl_set_name
-jerboa_proc_self_exe
-jerboa_random_bytes
-jerboa_regex_captures
-jerboa_regex_compile
-jerboa_regex_compile_ex
-jerboa_regex_find
-jerboa_regex_find_at
-jerboa_regex_free
-jerboa_regex_group_count
-jerboa_regex_is_match
-jerboa_regex_replace_all
-jerboa_scrypt
-jerboa_secure_alloc
-jerboa_secure_free
-jerboa_secure_random_fill
-jerboa_secure_wipe
-jerboa_setproctitle
-jerboa_sha1
-jerboa_sha256
-jerboa_sha384
-jerboa_sha512
-jerboa_socks5_server_port
-jerboa_socks5_server_start
-jerboa_socks5_server_stats
-jerboa_socks5_server_stop
-jerboa_sqlite_bind_blob
-jerboa_sqlite_bind_double
-jerboa_sqlite_bind_int
-jerboa_sqlite_bind_null
-jerboa_sqlite_bind_text
-jerboa_sqlite_changes
-jerboa_sqlite_close
-jerboa_sqlite_column_blob
-jerboa_sqlite_column_count
-jerboa_sqlite_column_double
-jerboa_sqlite_column_int
-jerboa_sqlite_column_name
-jerboa_sqlite_column_text
-jerboa_sqlite_column_type
-jerboa_sqlite_errmsg
-jerboa_sqlite_exec
-jerboa_sqlite_finalize
-jerboa_sqlite_last_insert_rowid
-jerboa_sqlite_open
-jerboa_sqlite_prepare
-jerboa_sqlite_reset
-jerboa_sqlite_step
-jerboa_timing_safe_equal
-jerboa_x25519_diffie_hellman
-jerboa_x25519_generate_keypair
-jerboa_x25519_public_from_private
diff --git a/support/gen-ffi-symbols.sh b/support/gen-ffi-symbols.sh
new file mode 100755
index 0000000..0ad50bf
--- /dev/null
+++ b/support/gen-ffi-symbols.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Regenerate support/ffi-symbols.gen from the jerboa-native archive that
+# jerbuild cargo-builds into its bundle cache. Run between the two `jerbuild
+# build` passes so the static binary registers exactly the jerboa_* symbols
+# present for THIS platform + feature set (e.g. epoll on Linux, kqueue on
+# macOS). The output is .gitignored — it is a build artifact, not source.
+set -e
+JH="$(jerbuild --jerboa-home 2>/dev/null)"
+A="$JH/jerboa-native-rs/target/release/libjerboa_native.a"
+OUT="$(dirname "$0")/ffi-symbols.gen"
+if [ ! -f "$A" ]; then
+  echo "gen-ffi-symbols: $A not found (run 'jerbuild build' once first)" >&2
+  : > "$OUT"
+  exit 0
+fi
+{
+  echo "# AUTOGENERATED by support/gen-ffi-symbols.sh from libjerboa_native.a."
+  echo "# jerboa-native FFI symbols registered via Sforeign_symbol() (see .jerbuild)."
+  echo ""
+  nm -gjU "$A" 2>/dev/null | sed 's/^_//' | grep -E '^jerboa_[A-Za-z0-9_]+$' | sort -u
+} > "$OUT"
+echo "gen-ffi-symbols: wrote $(grep -c '^jerboa_' "$OUT") symbols to $OUT"