gitsite: verify packaged lifecycle

ober

0d0ae9496a6867a4dca3d3af294db3243124f076

diff --git a/Makefile b/Makefile
index 1a75f2d..6480ffe 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,13 @@ JERBOA_GIT ?= $(CURDIR)/vendor/jerboa-git/native
 SINATRA_SRC ?= $(CURDIR)/vendor/jerboa-sinatra
 
 # Native FFI library path for jerboa-git
-JERBOA_GIT_LIB_PATH ?= $(JERBOA_GIT)/target/release/libjerboa_git_shim.dylib
+UNAME_S := $(shell uname -s)
+ifeq ($(UNAME_S),Darwin)
+SHLIB_EXT := dylib
+else
+SHLIB_EXT := so
+endif
+JERBOA_GIT_LIB_PATH ?= $(JERBOA_GIT)/target/release/libjerboa_git_shim.$(SHLIB_EXT)
 
 BUILD_DIR ?= build
 SRC_STAGE := $(BUILD_DIR)/src
@@ -20,7 +26,7 @@ PORT ?= 8080
 
 BINARY_OUTPUT ?= dist/gitsite
 
-.PHONY: build run run-builds check verify clean binary jerboa-git vendor vendor-jsqlite vendor-sinatra
+.PHONY: build run run-builds check verify test-integration clean binary jerboa-git vendor vendor-jsqlite vendor-sinatra
 
 vendor: vendor-jsqlite vendor-sinatra vendor-git
 
@@ -53,6 +59,9 @@ run-builds: build
 binary: build
 	mkdir -p dist
 	JERBOA_GIT_LIB_PATH="$(JERBOA_GIT_LIB_PATH)" $(JERBUILD) binary --libdirs "$(LIBDIRS)" src/gitsite.ss $(BINARY_OUTPUT)
+	mkdir -p dist/lib
+	cp $(JERBOA_HOME)/lib/libjerboa_native.* dist/lib/
+	cp $(JERBOA_GIT_LIB_PATH) dist/lib/
 
 check: build
 	JERBOA_GIT_LIB_PATH="$(JERBOA_GIT_LIB_PATH)" $(JERBUILD) exec --libdirs "$(LIBDIRS)" tests/smoke.ss
@@ -60,11 +69,11 @@ check: build
 	JERBOA_GIT_LIB_PATH="$(JERBOA_GIT_LIB_PATH)" $(JERBUILD) exec --libdirs "$(LIBDIRS)" tests/test-manifest.ss
 	JERBOA_GIT_LIB_PATH="$(JERBOA_GIT_LIB_PATH)" $(JERBUILD) exec --libdirs "$(LIBDIRS)" tests/test-auth.ss
 
-verify: binary check
-	@echo "verify: binary built and checks pass"
+verify: check test-integration
+	@echo "verify: binary, checks, and integration lifecycle pass"
 
 test-integration: binary
-	JERBOA_GIT_LIB_PATH="$(JERBOA_GIT_LIB_PATH)" tests/test-integration.sh
+	JERBOA_HOME="$(CURDIR)/dist" JERBOA_GIT_LIB_PATH="$(CURDIR)/dist/lib/$(notdir $(JERBOA_GIT_LIB_PATH))" tests/test-integration.sh
 
 clean:
 	rm -rf $(BUILD_DIR) var dist
diff --git a/README.md b/README.md
index 2cce656..d45a990 100644
--- a/README.md
+++ b/README.md
@@ -16,17 +16,19 @@ A minimal SourceHut-style git forge built in Jerboa.
 
 ## Quick Start
 
-### Build Static Binary (Production)
+### Build Release Bundle (Production)
 
 ```bash
 make binary
 ```
 
-Creates `dist/gitsite` — a single static binary with subcommands:
+Creates `dist/gitsite` with native runtime libraries in `dist/lib/`.
+The binary has these subcommands:
 `serve`, `ssh-auth`, `buildd`, `add-user`, `migrate`.
 
-No Jerboa runtime, no SQLite3 library, no Scheme interpreter required
-on the target machine.
+No Jerboa install, SQLite3 library, or Scheme interpreter is required on the
+target machine. Run the binary with `JERBOA_HOME` pointing at the bundled
+`dist` directory so the packaged native runtime can be loaded.
 
 ### Create First User
 
@@ -95,9 +97,12 @@ Edit `etc/gitsite.sexp`:
    sudo useradd -r -m -d /srv/gitsite-build -s /bin/bash gitsite-build
    ```
 
-2. **Install binary**:
+2. **Install release bundle**:
    ```bash
-   sudo install -m 755 dist/gitsite /usr/local/bin/
+   sudo install -d -m 755 /usr/local/libexec/gitsite
+   sudo install -m 755 dist/gitsite /usr/local/libexec/gitsite/
+   sudo cp -R dist/lib /usr/local/libexec/gitsite/
+   sudo ln -sf /usr/local/libexec/gitsite/gitsite /usr/local/bin/gitsite
    ```
 
 3. **Create directories**:
@@ -145,6 +150,8 @@ Edit `etc/gitsite.sexp`:
    Type=simple
    User=gitsite
    WorkingDirectory=/srv/gitsite
+   Environment=JERBOA_HOME=/usr/local/libexec/gitsite
+   Environment=JERBOA_GIT_LIB_PATH=/usr/local/libexec/gitsite/lib/libjerboa_git_shim.so
    ExecStart=/usr/local/bin/gitsite serve
    Restart=on-failure
 
@@ -182,7 +189,10 @@ Edit `etc/gitsite.sexp`:
 For asynchronous build execution:
 
 ```bash
-sudo -u gitsite /usr/local/bin/gitsite buildd &
+sudo -u gitsite \
+  JERBOA_HOME=/usr/local/libexec/gitsite \
+  JERBOA_GIT_LIB_PATH=/usr/local/libexec/gitsite/lib/libjerboa_git_shim.so \
+  /usr/local/bin/gitsite buildd &
 ```
 
 Or create a separate systemd service:
diff --git a/tests/test-integration.sh b/tests/test-integration.sh
index acfe9f6..4b8d34f 100755
--- a/tests/test-integration.sh
+++ b/tests/test-integration.sh
@@ -1,40 +1,83 @@
 #!/bin/sh
 set -eux
 
-# Integration test: clone, push, build lifecycle via gitsite server
-# Requires: gitsite binary built and running on PORT (default 8080)
+# Integration test: create repo, push, clone, submit build lifecycle.
 
 PORT=${PORT:-8080}
 BINARY=${BINARY:-dist/gitsite}
+case "$BINARY" in
+  /*) BINARY_ABS=$BINARY ;;
+  *) BINARY_ABS=$(pwd)/$BINARY ;;
+esac
 TMP=$(mktemp -d /tmp/gitsite-integ.XXXXXX)
-trap 'kill %1 2>/dev/null; rm -rf "$TMP"' EXIT
+COOKIE="$TMP/cookies"
+SERVER_PID=
+trap 'if [ -n "$SERVER_PID" ]; then kill "$SERVER_PID" 2>/dev/null || true; fi; rm -rf "$TMP"' EXIT
 
-# Start gitsite in background
-$BINARY serve &
-sleep 1
+mkdir -p "$TMP/etc"
+cat > "$TMP/etc/gitsite.sexp" <<EOF
+((listen-port $PORT)
+ (listen-address "127.0.0.1")
+ (var-root "$TMP/var")
+ (public-url "http://127.0.0.1:$PORT")
+ (registration "open")
+ (session-secret "integration-test-session-secret-32")
+ (tls-cert #f)
+ (tls-key #f)
+ (builds-mode "sync")
+ (buildd-workers 1)
+ (mode development))
+EOF
 
-# Create a repo via HTTP
-curl -sf -X POST -u admin:token "http://127.0.0.1:$PORT/api/v1/repos" \
-  -d '{"name":"integration-test","owner":"admin"}' > /dev/null
+cd "$TMP"
+"$BINARY_ABS" add-user admin admin@example.invalid password123
 
-# Clone via HTTP
-git clone "http://127.0.0.1:$PORT/git/~admin/integration-test.git" "$TMP/clone"
-echo "ok" > "$TMP/clone/hello.txt"
-git -C "$TMP/clone" add hello.txt
-git -C "$TMP/clone" commit -m "initial commit"
+# Start gitsite in background
+"$BINARY_ABS" serve > "$TMP/server.log" 2>&1 &
+SERVER_PID=$!
+sleep 2
 
-# Push via HTTP
-git -C "$TMP/clone" push origin main
+# Login and create a repo through the implemented web routes.
+curl -sf -c "$COOKIE" "http://127.0.0.1:$PORT/login" > /dev/null
+curl -sf -b "$COOKIE" -c "$COOKIE" -X POST \
+  -d "name=admin&password=password123" \
+  "http://127.0.0.1:$PORT/login" > /dev/null
 
-# Submit a build manifest
-curl -sf -X POST -u admin:token \
-  "http://127.0.0.1:$PORT/api/v1/builds" \
-  -d '{"repo":"~admin/integration-test","sha":"HEAD","manifest":"tasks:\n - build: echo hello"}'
+curl -sf -b "$COOKIE" "http://127.0.0.1:$PORT/new" > "$TMP/new.html"
+CSRF=$(sed -n 's/.*name="csrf" value="\([^"]*\)".*/\1/p' "$TMP/new.html" | head -1)
+curl -sf -b "$COOKIE" -c "$COOKIE" -X POST \
+  -d "name=integration-test&description=integration&visibility=public&csrf=$CSRF" \
+  "http://127.0.0.1:$PORT/new" > /dev/null
 
-# Wait for build
-sleep 2
+# Push test content into the bare repo. HTTPS push is intentionally disabled in
+# gitsite v1; SSH push requires a system sshd fixture, so this local integration
+# test verifies the server observes a real git push into repository storage.
+git init -q "$TMP/src"
+git -C "$TMP/src" config user.email admin@example.invalid
+git -C "$TMP/src" config user.name admin
+printf 'ok\n' > "$TMP/src/README.md"
+cat > "$TMP/src/.build.yml" <<'EOF'
+tasks:
+  - build: echo "build succeeded"
+EOF
+git -C "$TMP/src" add README.md .build.yml
+git -C "$TMP/src" commit -qm "initial commit"
+git -C "$TMP/src" push -q "$TMP/var/repos/admin/integration-test.git" HEAD:refs/heads/main
+
+# Clone via HTTP
+git clone "http://127.0.0.1:$PORT/git/~admin/integration-test.git" "$TMP/clone"
+grep -q "ok" "$TMP/clone/README.md"
 
-# Verify build log exists and shows success
-curl -sf "http://127.0.0.1:$PORT/~admin/integration-test/log" | grep -q "build succeeded"
+# Submit and verify a synchronous build.
+curl -sf -b "$COOKIE" "http://127.0.0.1:$PORT/~admin/integration-test/builds" > "$TMP/builds.html"
+CSRF=$(sed -n 's/.*name="csrf" value="\([^"]*\)".*/\1/p' "$TMP/builds.html" | head -1)
+curl -s -D "$TMP/submit.headers" -o "$TMP/submit.body" -b "$COOKIE" -c "$COOKIE" -X POST \
+  -d "csrf=$CSRF" \
+  "http://127.0.0.1:$PORT/~admin/integration-test/submit"
+JOB_PATH=$(sed -n 's/^[Ll]ocation: \(.*\)$/\1/p' "$TMP/submit.headers" | tr -d '\r' | head -1)
+test -n "$JOB_PATH"
+curl -sf -b "$COOKIE" "http://127.0.0.1:$PORT$JOB_PATH" > "$TMP/job.html"
+grep -q "success" "$TMP/job.html"
+grep -q "build succeeded" "$TMP/var/builds/1.log"
 
 echo "PASS: integration test"