Use vendored jsqlite

ober

11ee796b0a05fecef2b612ff66f837d95cdf7259

diff --git a/.jerbuild b/.jerbuild
index 1431967..27f0f4b 100644
--- a/.jerbuild
+++ b/.jerbuild
@@ -7,5 +7,5 @@
 
 (entry "signal/main.ss")
 (output "jerboa-signal")
-(libdirs "." "../jsqlite/src")
+(libdirs "." "vendor/jsqlite/src")
 (static-native #t)
diff --git a/Makefile b/Makefile
index 4c3ac7b..4fbbb97 100644
--- a/Makefile
+++ b/Makefile
@@ -8,8 +8,6 @@ JERBUILD ?= $(shell if [ -x ./jerbuild ] && [ -x ./jerboa ]; then \
 	printf '%s\n' ./jerbuild; \
 elif [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ] && [ -x "$(JERBOA_TOOL_DIR)/jerboa" ]; then \
 	printf '%s\n' "$(JERBOA_TOOL_DIR)/jerbuild"; \
-elif [ -x ../jerboa/dist/jerbuild ] && [ -x ../jerboa/dist/jerboa ]; then \
-	printf '%s\n' ../jerboa/dist/jerbuild; \
 elif command -v jerbuild >/dev/null 2>&1 && command -v jerboa >/dev/null 2>&1; then \
 	command -v jerbuild; \
 else \
@@ -17,12 +15,16 @@ else \
 fi)
 JH = $(shell "$(JERBUILD)" --jerboa-home 2>/dev/null)
 
-JSQLITE_SRC ?= $(CURDIR)/../jsqlite/src
+VENDOR ?= $(CURDIR)/vendor
+JSQLITE_REPO ?= $(VENDOR)/jsqlite
+JSQLITE_URL ?= https://git.sr.ht/~lisp/jsqlite
+JSQLITE_SRC ?= $(JSQLITE_REPO)/src
 LIBDIRS = --libdirs $(CURDIR):$(JSQLITE_SRC):$(JH)/lib
 DEFAULT_JERBOA_NATIVE_A = $(JH)/jerboa-native-rs/target/release/libjerboa_native.a
 JERBOA_NATIVE_A ?= $(DEFAULT_JERBOA_NATIVE_A)
+JERBOA_NATIVE_LIB ?= $(JH)/jerboa-native-rs/target/release/libjerboa_native.$(TUI_SHIM_EXT)
 export JERBOA_NATIVE_A
-JEXEC = $(JERBUILD) exec $(LIBDIRS)
+JEXEC = JERBOA_NATIVE_LIB="$(JERBOA_NATIVE_LIB)" $(JERBUILD) exec $(LIBDIRS)
 BIN := jerboa-signal
 BIN_DIR := $(HOME)/.local/bin
 TUI_SHIM_DIR := $(CURDIR)/vendor/termbox2
@@ -33,16 +35,14 @@ else
 TUI_SHIM_EXT := so
 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)
 
-.PHONY: all build binary run run-tui test install install-log-shim clean help vendor-deps tui-shim log-shim ensure-jerboa-tools ensure-jerboa-native
+.PHONY: all build binary run run-tui test install clean help vendor-deps tui-shim ensure-jerboa-tools ensure-jerboa-native ensure-jsqlite
 .DEFAULT_GOAL := help
 
 all: binary
 
 # Standalone native binary via .jerbuild (entry signal/main.ss -> jerboa-signal).
-binary: ensure-jerboa-native
+binary: ensure-jerboa-native ensure-jsqlite
 	$(JERBUILD) build
 
 build: binary
@@ -72,22 +72,19 @@ install: binary
 	test ! -f signal_tui_shim.$(TUI_SHIM_EXT) || install -m 0755 signal_tui_shim.$(TUI_SHIM_EXT) $(BIN_DIR)/signal_tui_shim.$(TUI_SHIM_EXT)
 	@echo "Installed $(BIN) to $(BIN_DIR)/$(BIN)"
 
-install-log-shim: log-shim
-	@if [ -f "$(LOG_SHIM)" ]; then \
-	  mkdir -p $(BIN_DIR); \
-	  install -m 0755 $(LOG_SHIM) $(BIN_DIR)/$(LOG_SHIM); \
-	  echo "Installed legacy $(LOG_SHIM) to $(BIN_DIR)/$(LOG_SHIM)"; \
-	else \
-	  echo "No legacy $(LOG_SHIM) built."; \
-	fi
-
 clean:
 	rm -f $(BIN)
 	rm -f signal_tui_shim.dylib signal_tui_shim.so
-	rm -f signal_log_shim.dylib signal_log_shim.so
 	find signal \( -name '*.so' -o -name '*.wpo' \) -delete 2>/dev/null || true
 
-vendor-deps: vendor/termbox2
+vendor-deps: ensure-jsqlite vendor/termbox2
+
+ensure-jsqlite:
+	@if [ ! -f "$(JSQLITE_SRC)/jsqlite/api.ss" ]; then \
+	  mkdir -p "$(VENDOR)"; \
+	  git clone --depth 1 "$(JSQLITE_URL)" "$(JSQLITE_REPO)"; \
+	fi
+	@test -f "$(JSQLITE_SRC)/jsqlite/api.ss"
 
 vendor/termbox2:
 	mkdir -p vendor
@@ -98,8 +95,6 @@ ensure-jerboa-tools:
 	  echo "=== Using project-local ./jerbuild ==="; \
 	elif [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ] && [ -x "$(JERBOA_TOOL_DIR)/jerboa" ]; then \
 	  echo "=== Using downloaded Jerboa toolchain: $(JERBOA_TOOL_DIR) ==="; \
-	elif [ -x ../jerboa/dist/jerbuild ] && [ -x ../jerboa/dist/jerboa ]; then \
-	  echo "=== Using sibling Jerboa build: ../jerboa/dist/jerbuild ==="; \
 	elif command -v jerbuild >/dev/null 2>&1 && command -v jerboa >/dev/null 2>&1; then \
 	  echo "=== Using Jerboa toolchain from PATH: $$(command -v jerbuild) ==="; \
 	else \
@@ -141,19 +136,6 @@ tui-shim: vendor/termbox2
 	    signal/tui/signal_tui_shim.c
 	cp $(TUI_SHIM) signal_tui_shim.$(TUI_SHIM_EXT)
 
-# Legacy encrypted-logging shim (SQLCipher). New logs use the jsqlite encrypted
-# backend; build this only to open pre-jsqlite SQLCipher log files.
-log-shim:
-	@if [ -z "$(SQLCIPHER_PREFIX)" ]; then \
-	  echo "log-shim: sqlcipher not found; skipping legacy SQLCipher support."; \
-	  echo "          enable legacy logs with: brew install sqlcipher && make log-shim"; \
-	else \
-	  cc -shared -fPIC \
-	    -I$(SQLCIPHER_PREFIX)/include/sqlcipher \
-	    -L$(SQLCIPHER_PREFIX)/lib -lsqlcipher \
-	    -o $(LOG_SHIM) signal/log_shim.c && echo "Built $(LOG_SHIM)"; \
-	fi
-
 help:
 	@echo "jerboa-signal -- Signal client over signal-cli"
 	@echo ""
@@ -163,10 +145,8 @@ help:
 	@echo "  run-tui             Build shim and start the TUI"
 	@echo "  test                Run tests"
 	@echo "  install             Install ./jerboa-signal to ~/.local/bin"
-	@echo "  install-log-shim    Install optional legacy SQLCipher log shim"
 	@echo "  ensure-jerboa-tools Ensure jerboa/jerbuild are available"
 	@echo "  tui-shim            Build the termbox2 TUI shim"
-	@echo "  log-shim            Build the legacy SQLCipher log shim"
 	@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 4b8c16c..2e6664b 100644
--- a/README.md
+++ b/README.md
@@ -24,10 +24,8 @@ jerboa-signal tui [-a +PHONE]                      # terminal UI shell
   Older 0.14.4.1 builds drop current sealed-sender receive envelopes with
   `getServerGuid(...) must not be null`.
 - Jerboa build tools. `make binary`/`make install` use project-local
-  `./jerbuild` first, then `.jerboa/bin`, then `../jerboa/dist`, then `PATH`,
-  and download the matching release artifact if none are available.
-- Optional, for the encrypted message log: `brew install sqlcipher`, then
-  `make log-shim` (without it the TUI runs fine, just without logging)
+  `./jerbuild` first, then `.jerboa/bin`, then `PATH`, and download the
+  matching release artifact if none are available.
 
 ## One-time setup: link as a secondary device
 
@@ -102,10 +100,10 @@ too. Use q, Esc, or Ctrl-C to quit.
 ## Encrypted message log
 
 The TUI can log every message to an encrypted SQLite database so you keep a copy
-even when the sender later deletes it ("delete for everyone"). New logs use
+even when the sender later deletes it ("delete for everyone"). Logs use
 `jsqlite` for the SQLite engine and a Jerboa-native encrypted container around
 the database image; contents and metadata are unreadable without your
-passphrase.
+passphrase. No native SQLite or SQLCipher library is loaded.
 
 Enable it:
 
@@ -113,14 +111,6 @@ Enable it:
 ./jerboa-signal tui           # prompts: passphrase for encrypted message log
 ```
 
-Existing SQLCipher log files are still supported as a legacy fallback. To open
-those old files, install SQLCipher and build the optional shim:
-
-```sh
-brew install sqlcipher
-make log-shim
-```
-
 At startup the TUI asks for a passphrase (echo off). Enter one to log; leave it
 blank to skip logging for that session. To run unattended, set
 `JERBOA_SIGNAL_DB_KEY` instead and the prompt is skipped.
@@ -156,7 +146,6 @@ Modules:
 - `signal/store.ss` — persistent local state (deleted-conversation list)
 - `signal/capture.ss` — normalizes a notification into an encrypted log row
 - `signal/logdb.ss` + `signal/log_crypto.ss` — encrypted jsqlite log backend
-- `signal/log_shim.c` — optional legacy SQLCipher log reader
 - `signal/tui/` — termbox2 FFI and the first terminal shell
 
 ## License
diff --git a/docs/full-port.md b/docs/full-port.md
index 6c46d4d..542a6f6 100644
--- a/docs/full-port.md
+++ b/docs/full-port.md
@@ -303,7 +303,7 @@ These names are placeholders. The implementation must map them to actual current
 
 ## 8. Storage Plan With jsqlite
 
-Use `~/mine/jsqlite` as the SQLite replacement. Do not use C SQLite unless the user explicitly changes this decision.
+Use vendored `jsqlite` as the SQLite replacement. Do not use C SQLite unless the user explicitly changes this decision.
 
 Storage goals:
 
diff --git a/signal/log_shim.c b/signal/log_shim.c
deleted file mode 100644
index 88c01f9..0000000
--- a/signal/log_shim.c
+++ /dev/null
@@ -1,225 +0,0 @@
-/* signal_log_shim.c -- SQLCipher-backed encrypted message log for jerboa-signal.
- *
- * Captures every Signal event as an append-only row so messages survive even
- * when the sender later deletes them ("delete for everyone" / remote delete):
- * a remote-delete is just another row and never touches the original.
- *
- * The database is a real encrypted SQLite file (SQLCipher, AES-256) -- contents
- * AND metadata are unreadable without the key.
- *
- * Build:
- *   cc -shared -fPIC \
- *      -I$(brew --prefix sqlcipher)/include/sqlcipher \
- *      -L$(brew --prefix sqlcipher)/lib -lsqlcipher \
- *      -o signal_log_shim.dylib signal/log_shim.c
- *
- * Self-test:
- *   cc -DSIGNAL_LOG_TEST -I... -L... -lsqlcipher signal/log_shim.c -o /tmp/logtest
- */
-
-#include <sqlite3.h>   /* SQLCipher's sqlite3.h (note the -I path) */
-#include <string.h>
-#include <unistd.h>    /* getpass */
-
-/* SQLCipher exports these, but sqlite3.h only declares them under
- * SQLITE_HAS_CODEC. Declare directly so we link against libsqlcipher's
- * symbols without depending on the header guard. */
-extern int sqlite3_key(sqlite3 *db, const void *pKey, int nKey);
-
-static const char *SCHEMA =
-    "CREATE TABLE IF NOT EXISTS messages ("
-    "  id           INTEGER PRIMARY KEY AUTOINCREMENT,"
-    "  logged_at    INTEGER DEFAULT (strftime('%s','now')),"
-    "  account      TEXT,"
-    "  direction    TEXT,"   /* in | out | system | event */
-    "  conversation TEXT,"   /* direct:<target> | group:<id> */
-    "  sender       TEXT,"
-    "  timestamp    INTEGER,"/* Signal message timestamp (ms), if any */
-    "  kind         TEXT,"   /* data | remote-delete | edit | receipt | typing | ... */
-    "  body         TEXT,"
-    "  raw          TEXT NOT NULL"  /* full raw JSON of the event */
-    ");"
-    "CREATE INDEX IF NOT EXISTS idx_messages_conv ON messages(conversation);"
-    "CREATE INDEX IF NOT EXISTS idx_messages_ts   ON messages(timestamp);";
-
-/* Open (creating if needed) the encrypted DB at `path`, keyed with `key`.
- * Returns a sqlite3* (as void*) or NULL on failure (including a wrong key). */
-void *signal_log_open(const char *path, const char *key) {
-    sqlite3 *db = NULL;
-    char *err = NULL;
-
-    if (sqlite3_open(path, &db) != SQLITE_OK) {
-        if (db) sqlite3_close(db);
-        return NULL;
-    }
-    /* The key must be applied before any other database access. */
-    if (key && key[0] != '\0') {
-        if (sqlite3_key(db, key, (int)strlen(key)) != SQLITE_OK) {
-            sqlite3_close(db);
-            return NULL;
-        }
-    }
-    /* Touch the schema so a wrong key fails here, not mid-insert. */
-    if (sqlite3_exec(db, "SELECT count(*) FROM sqlite_master;",
-                     NULL, NULL, &err) != SQLITE_OK) {
-        if (err) sqlite3_free(err);
-        sqlite3_close(db);
-        return NULL;
-    }
-    if (sqlite3_exec(db, SCHEMA, NULL, NULL, &err) != SQLITE_OK) {
-        if (err) sqlite3_free(err);
-        sqlite3_close(db);
-        return NULL;
-    }
-    (void)sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
-    return (void *)db;
-}
-
-int signal_log_close(void *handle) {
-    if (!handle) return 0;
-    return sqlite3_close((sqlite3 *)handle);
-}
-
-static void bind_text_or_null(sqlite3_stmt *st, int idx, const char *s) {
-    if (s && s[0] != '\0')
-        sqlite3_bind_text(st, idx, s, -1, SQLITE_TRANSIENT);
-    else
-        sqlite3_bind_null(st, idx);
-}
-
-/* Append one row. Text args may be empty (stored as NULL). Returns 0 on success. */
-int signal_log_put(void *handle,
-                   const char *account, const char *direction,
-                   const char *conversation, const char *sender,
-                   long long timestamp, const char *kind,
-                   const char *body, const char *raw) {
-    if (!handle) return -1;
-    sqlite3 *db = (sqlite3 *)handle;
-    static const char *SQL =
-        "INSERT INTO messages"
-        " (account,direction,conversation,sender,timestamp,kind,body,raw)"
-        " VALUES (?,?,?,?,?,?,?,?);";
-    sqlite3_stmt *st = NULL;
-    if (sqlite3_prepare_v2(db, SQL, -1, &st, NULL) != SQLITE_OK)
-        return sqlite3_errcode(db);
-    bind_text_or_null(st, 1, account);
-    bind_text_or_null(st, 2, direction);
-    bind_text_or_null(st, 3, conversation);
-    bind_text_or_null(st, 4, sender);
-    sqlite3_bind_int64(st, 5, (sqlite3_int64)timestamp);
-    bind_text_or_null(st, 6, kind);
-    bind_text_or_null(st, 7, body);
-    sqlite3_bind_text(st, 8, raw ? raw : "", -1, SQLITE_TRANSIENT);
-    int rc = sqlite3_step(st);
-    sqlite3_finalize(st);
-    return (rc == SQLITE_DONE) ? 0 : rc;
-}
-
-/* Total rows logged (verification helper). Returns -1 on error. */
-long long signal_log_count(void *handle) {
-    if (!handle) return -1;
-    sqlite3 *db = (sqlite3 *)handle;
-    sqlite3_stmt *st = NULL;
-    if (sqlite3_prepare_v2(db, "SELECT count(*) FROM messages;", -1, &st, NULL)
-            != SQLITE_OK)
-        return -1;
-    long long n = -1;
-    if (sqlite3_step(st) == SQLITE_ROW)
-        n = (long long)sqlite3_column_int64(st, 0);
-    sqlite3_finalize(st);
-    return n;
-}
-
-/* Begin reading the most recent displayable rows, newest first. Data-like rows
- * with a body and a conversation qualify; receipts, typing indicators and bare
- * events are noise for history. Returns a statement handle
- * (step/text/int/close below) or NULL.
- * Columns: 0 direction, 1 conversation, 2 sender, 3 timestamp, 4 kind, 5 body. */
-void *signal_log_recent(void *handle, long long limit) {
-    if (!handle) return NULL;
-    sqlite3 *db = (sqlite3 *)handle;
-    static const char *SQL =
-        "SELECT direction, conversation, sender, timestamp, kind, body"
-        " FROM messages"
-        " WHERE direction IN ('in','out')"
-        "   AND conversation IS NOT NULL AND conversation <> ''"
-        "   AND body IS NOT NULL AND body <> ''"
-        " ORDER BY id DESC LIMIT ?;";
-    sqlite3_stmt *st = NULL;
-    if (sqlite3_prepare_v2(db, SQL, -1, &st, NULL) != SQLITE_OK)
-        return NULL;
-    sqlite3_bind_int64(st, 1, (sqlite3_int64)(limit > 0 ? limit : 0));
-    return (void *)st;
-}
-
-/* 1 = a row is available, 0 = done (or error -- treated as done). */
-int signal_log_row_step(void *stmt) {
-    if (!stmt) return 0;
-    return sqlite3_step((sqlite3_stmt *)stmt) == SQLITE_ROW ? 1 : 0;
-}
-
-/* Text column of the current row; "" for NULL. Valid until the next step. */
-const char *signal_log_row_text(void *stmt, int col) {
-    if (!stmt) return "";
-    const unsigned char *s = sqlite3_column_text((sqlite3_stmt *)stmt, col);
-    return s ? (const char *)s : "";
-}
-
-long long signal_log_row_int(void *stmt, int col) {
-    if (!stmt) return 0;
-    return (long long)sqlite3_column_int64((sqlite3_stmt *)stmt, col);
-}
-
-int signal_log_row_close(void *stmt) {
-    if (!stmt) return 0;
-    return sqlite3_finalize((sqlite3_stmt *)stmt);
-}
-
-/* Read a passphrase from the controlling terminal with echo disabled.
- * Returns a pointer to a static buffer (copied by the FFI into a Scheme string). */
-const char *signal_log_getpass(const char *prompt) {
-    char *p = getpass(prompt ? prompt : "Passphrase: ");
-    return p ? p : "";
-}
-
-#ifdef SIGNAL_LOG_TEST
-#include <stdio.h>
-int main(void) {
-    const char *path = "/tmp/jerboa-signal-logtest.db";
-    unlink(path);
-    void *db = signal_log_open(path, "correct horse battery staple");
-    if (!db) { printf("FAIL open\n"); return 1; }
-    signal_log_put(db, "+15550001111", "in", "direct:+15550002222",
-                   "Alice", 1717000000000LL, "data", "hello world",
-                   "{\"method\":\"receive\"}");
-    signal_log_put(db, "+15550001111", "in", "direct:+15550002222",
-                   "Alice", 1717000005000LL, "remote-delete", "",
-                   "{\"remoteDelete\":{\"timestamp\":1717000000000}}");
-    long long n = signal_log_count(db);
-    signal_log_close(db);
-
-    /* Reopen with correct key: both rows (incl. the original) still present. */
-    void *db2 = signal_log_open(path, "correct horse battery staple");
-    long long n2 = db2 ? signal_log_count(db2) : -1;
-
-    /* Readback: only the data row qualifies (remote-delete has no body). */
-    int recent = 0, body_ok = 0;
-    void *st = db2 ? signal_log_recent(db2, 10) : NULL;
-    while (st && signal_log_row_step(st)) {
-        recent++;
-        body_ok = strcmp(signal_log_row_text(st, 5), "hello world") == 0
-               && signal_log_row_int(st, 3) == 1717000000000LL;
-    }
-    if (st) signal_log_row_close(st);
-    if (db2) signal_log_close(db2);
-
-    /* Wrong key must fail. */
-    void *db3 = signal_log_open(path, "wrong key");
-    int wrong_rejected = (db3 == NULL);
-    if (db3) signal_log_close(db3);
-
-    printf("rows=%lld reopened=%lld recent=%d body_ok=%d wrong_key_rejected=%d\n",
-           n, n2, recent, body_ok, wrong_rejected);
-    return (n == 2 && n2 == 2 && recent == 1 && body_ok && wrong_rejected) ? 0 : 2;
-}
-#endif
diff --git a/signal/logdb.ss b/signal/logdb.ss
index 56a9d35..366dbe1 100644
--- a/signal/logdb.ss
+++ b/signal/logdb.ss
@@ -2,8 +2,8 @@
 ;;; signal/logdb -- encrypted message log.
 ;;;
 ;;; New logs use a Jerboa-native jsqlite database image stored inside an
-;;; authenticated encrypted container. Existing SQLCipher databases are still
-;;; opened through the legacy shim so old logs do not disappear.
+;;; authenticated encrypted container. No native SQLite or SQLCipher shim is
+;;; loaded.
 
 (library (signal logdb)
   (export logdb-available?
@@ -31,97 +31,6 @@
   (defstruct jlog (path key salt db))
 
   ;; --------------------------------------------------------------------------
-  ;; Legacy SQLCipher shim. This stays only for old messages-*.db files that
-  ;; were created by the previous backend.
-
-  (def *shim-load-state* (box 'untried))
-
-  (def (try-load-shim path)
-    (and (file-exists? path) (load-shared-object path)))
-
-  (def (ensure-shim-loaded!)
-    (cond
-      [(eq? (unbox *shim-load-state*) 'loaded) #t]
-      [(eq? (unbox *shim-load-state*) 'missing) #f]
-      [else
-       (let* ([bin-dir (path-directory (car (command-line)))]
-              [home (or (getenv "HOME") ".")]
-              [loaded?
-               (or (foreign-entry? "signal_log_open")
-                   (guard (e [#t #f])
-                     (or (try-load-shim "signal_log_shim.dylib")
-                         (try-load-shim "signal_log_shim.so")
-                         (try-load-shim (path-join bin-dir "signal_log_shim.dylib"))
-                         (try-load-shim (path-join bin-dir "signal_log_shim.so"))
-                         (try-load-shim (path-join home ".local" "bin"
-                                                   "signal_log_shim.dylib"))
-                         (try-load-shim (path-join home ".local" "bin"
-                                                   "signal_log_shim.so")))))])
-         (set-box! *shim-load-state* (if loaded? 'loaded 'missing))
-         loaded?)]))
-
-  (def (legacy-entry? name)
-    (or (foreign-entry? name)
-        (and (ensure-shim-loaded!) (foreign-entry? name))))
-
-  (def (legacy-available?)
-    (legacy-entry? "signal_log_open"))
-
-  (def (legacy-open path key)
-    (and (legacy-entry? "signal_log_open")
-         (let ([h ((foreign-procedure "signal_log_open" (string string) uptr)
-                   path key)])
-           (and (not (= h 0)) h))))
-
-  (def (legacy-close handle)
-    (when (and handle (legacy-entry? "signal_log_close"))
-      ((foreign-procedure "signal_log_close" (uptr) int) handle)
-      (void)))
-
-  (def (legacy-put handle account direction conversation sender
-                   timestamp kind body raw)
-    (and handle
-         (legacy-entry? "signal_log_put")
-         (= 0 ((foreign-procedure "signal_log_put"
-                  (uptr string string string string integer-64 string string string)
-                  int)
-               handle account direction conversation sender
-               timestamp kind body raw))))
-
-  (def (legacy-count handle)
-    (if (and handle (legacy-entry? "signal_log_count"))
-      ((foreign-procedure "signal_log_count" (uptr) integer-64) handle)
-      -1))
-
-  (def (legacy-recent handle limit)
-    (if (and handle
-             (legacy-entry? "signal_log_recent")
-             (legacy-entry? "signal_log_row_step")
-             (legacy-entry? "signal_log_row_text")
-             (legacy-entry? "signal_log_row_int")
-             (legacy-entry? "signal_log_row_close"))
-      (guard (e [#t '()])
-        (let ([open (foreign-procedure "signal_log_recent" (uptr integer-64) uptr)]
-              [step (foreign-procedure "signal_log_row_step" (uptr) int)]
-              [text (foreign-procedure "signal_log_row_text" (uptr int) string)]
-              [num (foreign-procedure "signal_log_row_int" (uptr int) integer-64)]
-              [close (foreign-procedure "signal_log_row_close" (uptr) int)])
-          (let ([st (open handle limit)])
-            (if (= st 0)
-              '()
-              (dynamic-wind
-                (lambda () (void))
-                (lambda ()
-                  (let loop ([acc '()])
-                    (if (= (step st) 1)
-                      (loop (cons (list (text st 0) (text st 1) (text st 2)
-                                        (num st 3) (text st 4) (text st 5))
-                                  acc))
-                      acc)))
-                (lambda () (close st)))))))
-      '()))
-
-  ;; --------------------------------------------------------------------------
   ;; jsqlite encrypted container.
 
   (def *jlog-magic* (string->utf8 "JSQLITELOGv1\n"))
@@ -309,24 +218,10 @@
   ;; Public API.
 
   (def (logdb-available?)
-    (or (log-crypto-available?) (legacy-available?)))
-
-  (def (backend-env-is? name)
-    (let ([v (getenv "JERBOA_SIGNAL_LOG_BACKEND")])
-      (and v (string-ci=? v name))))
-
-  (def (wrap-legacy h)
-    (and h (make-logdb-handle 'legacy h)))
+    (log-crypto-available?))
 
   (def (logdb-open path key)
-    (cond
-      [(or (backend-env-is? "sqlcipher") (backend-env-is? "legacy"))
-       (wrap-legacy (legacy-open path key))]
-      [(backend-env-is? "jsqlite")
-       (open-jlog path key)]
-      [else
-       (or (open-jlog path key)
-           (wrap-legacy (legacy-open path key)))]))
+    (open-jlog path key))
 
   (def (logdb-close handle)
     (when (logdb-handle? handle)
@@ -336,8 +231,6 @@
            (let ([log (logdb-handle-inner handle)])
              (persist-jlog! log)
              (sqlite-close (jlog-db log))))]
-        [(legacy)
-         (legacy-close (logdb-handle-inner handle))]
         [else (void)])))
 
   (def (logdb-put handle account direction conversation sender
@@ -349,10 +242,6 @@
               (jlog-put (logdb-handle-inner handle)
                         account direction conversation sender
                         timestamp kind body raw)]
-             [(legacy)
-              (legacy-put (logdb-handle-inner handle)
-                          account direction conversation sender
-                          timestamp kind body raw)]
              [else #f]))))
 
   (def (logdb-count handle)
@@ -360,7 +249,6 @@
       (guard (e [#t -1])
         (case (logdb-handle-backend handle)
           [(jsqlite) (jlog-count (logdb-handle-inner handle))]
-          [(legacy) (legacy-count (logdb-handle-inner handle))]
           [else -1]))
       -1))
 
@@ -369,12 +257,11 @@
       (guard (e [#t '()])
         (case (logdb-handle-backend handle)
           [(jsqlite) (jlog-recent (logdb-handle-inner handle) limit)]
-          [(legacy) (legacy-recent (logdb-handle-inner handle) limit)]
           [else '()]))
       '()))
 
   ;; Read a passphrase before termbox takes over the terminal. This avoids
-  ;; depending on the old SQLCipher C shim just to get getpass(3).
+  ;; depending on native terminal helpers.
   (def (logdb-prompt-passphrase prompt)
     (display prompt)
     (flush-output-port (current-output-port))