Add launchd LaunchAgent for the always-on logger (macOS)

ober

68245b4b5df0a56305d549c1129bca118ef1503f

diff --git a/Makefile b/Makefile
index 99960b7..ac5ec0a 100644
--- a/Makefile
+++ b/Makefile
@@ -21,8 +21,10 @@ endif
 TUI_SHIM := $(TUI_SHIM_DIR)/signal_tui_shim.$(TUI_SHIM_EXT)
 LOG_SHIM := signal_log_shim.$(TUI_SHIM_EXT)
 SQLCIPHER_PREFIX := $(shell brew --prefix sqlcipher 2>/dev/null)
+LAUNCHD_LABEL := org.linbsd.jerboa-signal.log
+LAUNCHD_PLIST := $(HOME)/Library/LaunchAgents/$(LAUNCHD_LABEL).plist
 
-.PHONY: all build binary run run-tui test install clean help vendor-deps tui-shim log-shim
+.PHONY: all build binary run run-tui test install clean help vendor-deps tui-shim log-shim launchd-install launchd-uninstall
 .DEFAULT_GOAL := help
 
 all: binary
@@ -82,6 +84,34 @@ log-shim:
 	    -o $(LOG_SHIM) signal/log_shim.c && echo "Built $(LOG_SHIM)"; \
 	fi
 
+# Install the always-on logger as a launchd LaunchAgent: auto-start at login,
+# restart on crash. The passphrase is read from the macOS Keychain by the
+# wrapper, never stored in the plist.
+launchd-install:
+	$(MAKE) log-shim
+	$(MAKE) install
+	install -m 0755 dist/jerboa-signal-logd.sh $(BIN_DIR)/jerboa-signal-logd
+	mkdir -p $(HOME)/Library/LaunchAgents $(HOME)/Library/Logs
+	sed 's|__HOME__|$(HOME)|g' dist/$(LAUNCHD_LABEL).plist.in > $(LAUNCHD_PLIST)
+	@echo ""
+	@echo "Installed LaunchAgent + wrapper. Two manual steps remain:"
+	@echo "  1) Store the DB passphrase in your Keychain (prompts; nothing on disk):"
+	@echo "       security add-generic-password -a \"$$USER\" -s jerboa-signal-db -w"
+	@echo "  2) Load the agent (starts now and at every login):"
+	@echo "       launchctl load -w $(LAUNCHD_PLIST)"
+	@echo ""
+	@echo "  Logs:  $(HOME)/Library/Logs/jerboa-signal-log.{out,err}.log"
+	@echo "  Stop:  make launchd-uninstall   (or: launchctl unload -w the plist)"
+	@echo "  NOTE:  stop the daemon before using the TUI -- signal-cli allows only"
+	@echo "         one connection per account."
+
+launchd-uninstall:
+	-launchctl unload -w $(LAUNCHD_PLIST) 2>/dev/null || true
+	rm -f $(LAUNCHD_PLIST) $(BIN_DIR)/jerboa-signal-logd
+	@echo "Removed LaunchAgent and wrapper."
+	@echo "Keychain item kept; remove it with:"
+	@echo "  security delete-generic-password -a \"$$USER\" -s jerboa-signal-db"
+
 help:
 	@echo "jerboa-signal -- Signal client over signal-cli"
 	@echo ""
@@ -93,6 +123,8 @@ help:
 	@echo "  install             Install ./jerboa-signal to ~/.local/bin"
 	@echo "  tui-shim            Build the termbox2 TUI shim"
 	@echo "  log-shim            Build the SQLCipher encrypted-logging shim"
+	@echo "  launchd-install     Install the always-on logger as a login agent (macOS)"
+	@echo "  launchd-uninstall   Remove the launchd agent and wrapper"
 	@echo "  clean               Remove build artifacts"
 	@echo ""
 	@echo "Prerequisite: signal-cli must be linked to your Signal account."
diff --git a/README.md b/README.md
index 9964a20..1757c01 100644
--- a/README.md
+++ b/README.md
@@ -141,6 +141,32 @@ Note: signal-cli allows only one connection per account, so the daemon and the
 TUI cannot run at the same time for the same account — use the daemon for
 unattended capture and the TUI for reading/replying.
 
+### Auto-start at login (launchd, macOS)
+
+To run the logger as a LaunchAgent that starts at login and restarts on crash:
+
+```sh
+make launchd-install
+```
+
+This builds and installs the binary, the SQLCipher shim, and a wrapper, then
+writes `~/Library/LaunchAgents/org.linbsd.jerboa-signal.log.plist`. The wrapper
+reads the passphrase from the **macOS Keychain**, so it is never stored in the
+plist (which would otherwise sit in plaintext next to the encrypted database).
+Finish with the two steps it prints:
+
+```sh
+# store the passphrase once (prompts; nothing written to disk or shell history)
+security add-generic-password -a "$USER" -s jerboa-signal-db -w
+# load the agent (starts now and at every login)
+launchctl load -w ~/Library/LaunchAgents/org.linbsd.jerboa-signal.log.plist
+```
+
+Output goes to `~/Library/Logs/jerboa-signal-log.{out,err}.log`. Remove
+everything with `make launchd-uninstall`. Because the daemon holds the single
+signal-cli connection, unload it (`make launchd-uninstall`, or `launchctl
+unload -w` the plist) before using the TUI.
+
 ## Architecture
 
 ```
diff --git a/dist/jerboa-signal-logd.sh b/dist/jerboa-signal-logd.sh
new file mode 100755
index 0000000..76e8164
--- /dev/null
+++ b/dist/jerboa-signal-logd.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# jerboa-signal-logd -- launchd wrapper for the always-on encrypted logger.
+#
+# Fetches the SQLCipher passphrase from the macOS Keychain so the key never sits
+# in the LaunchAgent plist (which lives in ~/Library/LaunchAgents, right next to
+# the encrypted database), then execs `jerboa-signal log`.
+#
+# One-time Keychain setup (prompts for the passphrase; it is not stored in shell
+# history or any plaintext file):
+#   security add-generic-password -a "$USER" -s jerboa-signal-db -w
+set -eu
+
+USER="${USER:-$(id -un)}"
+BIN="${JERBOA_SIGNAL_BIN:-$HOME/.local/bin/jerboa-signal}"
+
+if ! KEY="$(security find-generic-password -a "$USER" -s jerboa-signal-db -w 2>/dev/null)"; then
+  echo "jerboa-signal-logd: missing Keychain item 'jerboa-signal-db'." >&2
+  echo "  create it with:" >&2
+  echo "    security add-generic-password -a \"\$USER\" -s jerboa-signal-db -w" >&2
+  exit 1
+fi
+
+JERBOA_SIGNAL_DB_KEY="$KEY"
+export JERBOA_SIGNAL_DB_KEY
+exec "$BIN" log
diff --git a/dist/org.linbsd.jerboa-signal.log.plist.in b/dist/org.linbsd.jerboa-signal.log.plist.in
new file mode 100644
index 0000000..a7e2efc
--- /dev/null
+++ b/dist/org.linbsd.jerboa-signal.log.plist.in
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
+  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<!-- LaunchAgent for the jerboa-signal always-on encrypted logger.
+     __HOME__ is substituted by `make launchd-install`; do not edit the
+     installed copy in ~/Library/LaunchAgents by hand. -->
+<plist version="1.0">
+<dict>
+  <key>Label</key>
+  <string>org.linbsd.jerboa-signal.log</string>
+
+  <key>ProgramArguments</key>
+  <array>
+    <string>__HOME__/.local/bin/jerboa-signal-logd</string>
+  </array>
+
+  <!-- launchd agents get a minimal PATH; signal-cli (Homebrew) and security
+       must be reachable. -->
+  <key>EnvironmentVariables</key>
+  <dict>
+    <key>PATH</key>
+    <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
+  </dict>
+
+  <key>RunAtLoad</key>
+  <true/>
+
+  <!-- Keep it running: if signal-cli hiccups the daemon exits and launchd
+       restarts it (reconnecting), throttled so a persistent failure does not
+       spin. -->
+  <key>KeepAlive</key>
+  <true/>
+  <key>ThrottleInterval</key>
+  <integer>30</integer>
+
+  <key>WorkingDirectory</key>
+  <string>__HOME__</string>
+
+  <key>StandardOutPath</key>
+  <string>__HOME__/Library/Logs/jerboa-signal-log.out.log</string>
+  <key>StandardErrorPath</key>
+  <string>__HOME__/Library/Logs/jerboa-signal-log.err.log</string>
+
+  <key>ProcessType</key>
+  <string>Background</string>
+</dict>
+</plist>