Remove the logging daemon and launchd agent
ober
a120a3c8b46658066641be494192824b69b62b82
--- a/Makefile +++ b/Makefile @@ -21,10 +21,8 @@ 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 launchd-install launchd-uninstall +.PHONY: all build binary run run-tui test install clean help vendor-deps tui-shim log-shim .DEFAULT_GOAL := help all: binary @@ -84,34 +82,6 @@ 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 "" @@ -123,8 +93,6 @@ 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 @@ -13,7 +13,6 @@ Bidirectional bridge between Signal and your shell: jerboa-signal send [-a +PHONE] RECIPIENT MESSAGE # send one message, exit jerboa-signal listen [-a +PHONE] # stream inbound as NDJSON jerboa-signal tui [-a +PHONE] # terminal UI shell -jerboa-signal log [-a +PHONE] # headless: capture all to an encrypted DB ``` `-a` is required only if multiple Signal accounts are linked to signal-cli. @@ -122,50 +121,8 @@ sqlcipher ~/.local/share/jerboa-signal/messages-+15551234567.db \ "PRAGMA key='your passphrase'; SELECT timestamp,direction,sender,kind,body FROM messages;" ``` -### Always-on logging daemon - -The TUI only logs while it is open. To capture messages around the clock, run -the headless logger, which connects to signal-cli in receive mode and appends -every event to the same encrypted database until stopped: - -```sh -export JERBOA_SIGNAL_DB_KEY='your passphrase' # required when headless -jerboa-signal log # prints a line per captured event -``` - -Run it under launchd/`nohup`/tmux for 24/7 capture. It reads the passphrase from -`JERBOA_SIGNAL_DB_KEY`, or prompts if started attached to a terminal. Each event -is committed immediately, so killing the daemon never loses logged messages. - -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. +Logging happens only while the TUI is open — messages that arrive while it is +closed are not captured. ## Architecture @@ -186,9 +143,8 @@ Modules: - `signal/cmd-send.ss` — `send` subcommand - `signal/cmd-listen.ss` — `listen` subcommand - `signal/cmd-tui.ss` — TUI command entry point; see `docs/TUI_PLAN.md` -- `signal/cmd-log.ss` — always-on headless logging daemon (`log` subcommand) - `signal/store.ss` — persistent local state (deleted-conversation list) -- `signal/capture.ss` — notification → encrypted log row (shared by TUI + daemon) +- `signal/capture.ss` — normalizes a notification into an encrypted log row - `signal/logdb.ss` + `signal/log_shim.c` — FFI to the SQLCipher encrypted log - `signal/tui/` — termbox2 FFI and the first terminal shell deleted file mode 100755 --- a/dist/jerboa-signal-logd.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/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 deleted file mode 100644 --- a/dist/org.linbsd.jerboa-signal.log.plist.in +++ /dev/null @@ -1,47 +0,0 @@ -<?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> deleted file mode 100644 --- a/signal/cmd-log.ss +++ /dev/null @@ -1,145 +0,0 @@ -#!chezscheme -;;; signal/cmd-log -- always-on headless message logger. -;;; -;;; Connects to signal-cli in receive mode and appends every event to the -;;; encrypted SQLCipher log, so messages are kept even if the sender later -;;; deletes them ("delete for everyone"). Runs until the stream closes or it is -;;; interrupted. Each insert is its own committed transaction, so an abrupt kill -;;; never loses already-logged messages. -;;; -;;; Passphrase: JERBOA_SIGNAL_DB_KEY if set (for unattended/launchd use), -;;; otherwise prompt when attached to a terminal. - -(library (signal cmd-log) - (export cmd-log) - - (import (except (chezscheme) - make-hash-table hash-table? - sort sort! - printf fprintf - path-extension path-absolute? - with-input-from-string with-output-to-string - iota 1+ 1- - partition - make-date make-time) - (except (jerboa prelude) meta atom?) - (std misc process) - (signal rpc-actor) - (signal store) - (signal logdb) - (signal capture)) - - (def (cmd-log account) - (let* ([acct (or account "default")] - [logdb (open-log-or-exit acct)] - [actor (start-signal-actor account "on-connection")]) - (dynamic-wind - (lambda () (void)) - (lambda () - (announce acct) - (confirm-backend actor) - (log-loop logdb acct actor 0)) - (lambda () - (stop-signal-actor actor) - (logdb-close logdb) - (display "jerboa-signal log: stopped.\n"))))) - - (def (open-log-or-exit acct) - (unless (logdb-available?) - (die "encrypted logging unavailable; build it with: brew install sqlcipher && make log-shim")) - (let ([key (resolve-key)]) - (unless (and key (not (string=? key ""))) - (die "no passphrase; set JERBOA_SIGNAL_DB_KEY or run attached to a terminal")) - (ensure-store-dir!) - (let ([db (logdb-open (messages-store-path acct) key)]) - (unless db - (die "could not open encrypted log (wrong passphrase, or corrupt file)")) - db))) - - (def (resolve-key) - (let ([env (getenv "JERBOA_SIGNAL_DB_KEY")]) - (cond - [(and env (not (string=? env ""))) env] - [(tty? (current-input-port)) - (logdb-prompt-passphrase - "jerboa-signal log: passphrase for encrypted message log: ")] - [else #f]))) - - (def (announce acct) - (display "jerboa-signal log: capturing to ") - (display (messages-store-path acct)) - (newline) - (display " receive mode on-connection; press Ctrl-C to stop.\n") - (flush-output-port (current-output-port))) - - (def (confirm-backend actor) - (guard (e [#t (void)]) - (let ([v (actor-call actor "version" #f)]) - (when (hashtable? v) - (display " signal-cli ") - (display (hashtable-ref v "version" "unknown")) - (newline) - (flush-output-port (current-output-port)))))) - - ;; Poll the actor's event stream, log every notification, and keep a running - ;; total. Exits when signal-cli closes the stream. - (def (log-loop logdb acct actor total) - (cond - [(signal-actor-closed? actor) - (display "jerboa-signal log: signal-cli stream closed.\n") - total] - [else - (let ([n (handle-events! logdb acct (actor-drain-events actor) total)]) - (sleep-ms 150) - (log-loop logdb acct actor n))])) - - (def (handle-events! logdb acct events total) - (let loop ([evs events] [count total]) - (cond - [(null? evs) count] - [else - (let ([ev (car evs)] - [rest (cdr evs)]) - (cond - [(notification-event? ev) - (let ([summary (capture-notification! logdb acct (cadr ev))] - [n (+ count 1)]) - (report n summary) - (loop rest n))] - [(tagged? ev 'error) - (display "jerboa-signal log: signal-cli error: ") - (display (event-detail ev)) - (newline) - (flush-output-port (current-output-port)) - (loop rest count)] - [else (loop rest count)]))]))) - - (def (notification-event? ev) - (and (tagged? ev 'notification) (pair? (cdr ev)))) - - (def (tagged? ev tag) - (and (pair? ev) (eq? (car ev) tag))) - - (def (report n summary) - (display "[") - (display n) - (display "] ") - (display (if (string? summary) summary "event")) - (newline) - (flush-output-port (current-output-port))) - - (def (event-detail ev) - (if (pair? (cdr ev)) - (let ([d (cadr ev)]) (if (string? d) d (object->string d))) - "?")) - - (def (object->string x) - (let ([p (open-output-string)]) (display x p) (get-output-string p))) - - (def (die msg) - (display "jerboa-signal log: " (current-error-port)) - (display msg (current-error-port)) - (newline (current-error-port)) - (exit 1)) - - ) ;; end library --- a/signal/main.ss +++ b/signal/main.ss @@ -35,8 +35,7 @@ (import (except (jerboa prelude) meta atom?) (signal cmd-send) (signal cmd-listen) - (signal cmd-tui) - (signal cmd-log)) + (signal cmd-tui)) (def *version* "0.1.0") @@ -48,7 +47,6 @@ Subcommands: send [-a +PHONE] RECIPIENT MESSAGE Send a Signal message listen [-a +PHONE] Stream inbound envelopes as NDJSON tui [-a +PHONE] Start the terminal UI - log [-a +PHONE] Capture all messages to an encrypted log version Print version help Print this help @@ -103,15 +101,6 @@ linking once before using this tool: (exit 2)] [else (cmd-tui account)]))] - [(string=? sub "log") - (let-values ([(account args) (parse-account-flag rest)]) - (cond - [(not (null? args)) - (display "jerboa-signal: log takes no positional arguments\n" - (current-error-port)) - (exit 2)] - [else - (cmd-log account)]))] [else (display "jerboa-signal: unknown subcommand: " (current-error-port)) (display sub (current-error-port))