Add launchd LaunchAgent for the always-on logger (macOS)
ober
68245b4b5df0a56305d549c1129bca118ef1503f
--- 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." --- 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 ``` new file mode 100755 --- /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 new file mode 100644 --- /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>