Context
docs/self-host.md:62-71 documents the cold-backup procedure as docker compose down then tar czf the data volume.
Problem / Observation
With journal_mode=WAL, a graceful shutdown via SIGTERM should checkpoint the WAL. However, the shutdown sequence in src/index.ts:18-29 only calls api.close() / admin.close() / services.store.close() — better-sqlite3's close() does perform a checkpoint, but there's no explicit PRAGMA wal_checkpoint(TRUNCATE) call to guarantee the .db-wal and .db-shm sidecars are removed/empty before the tar runs. If the operator runs tar while the container exited uncleanly (SIGKILL via docker compose down -t 0), the tar captures the .db plus possibly stale WAL.
Worse: the documented hot-backup path is unimplemented and merely says "use litestream integration that will land in M7" — so today operators have no online backup option.
Proposed approach
- Add an explicit
db.pragma("wal_checkpoint(TRUNCATE)") call in Store.close() (src/store/db.ts).
- Document the explicit checkpoint guarantee in docs/self-host.md.
- Provide a
npm run backup / docker exec helper that runs sqlite3 /data/keyfount.db '.backup /data/snap.db' for an online consistent backup, even without litestream.
- Add an integration test that performs a backup mid-write and restores it.
Acceptance criteria
Context
docs/self-host.md:62-71 documents the cold-backup procedure as
docker compose downthentar czfthe data volume.Problem / Observation
With
journal_mode=WAL, a graceful shutdown via SIGTERM should checkpoint the WAL. However, the shutdown sequence in src/index.ts:18-29 only callsapi.close()/admin.close()/services.store.close()—better-sqlite3'sclose()does perform a checkpoint, but there's no explicitPRAGMA wal_checkpoint(TRUNCATE)call to guarantee the.db-waland.db-shmsidecars are removed/empty before the tar runs. If the operator runstarwhile the container exited uncleanly (SIGKILL viadocker compose down -t 0), the tar captures the .db plus possibly stale WAL.Worse: the documented hot-backup path is unimplemented and merely says "use
litestreamintegration that will land in M7" — so today operators have no online backup option.Proposed approach
db.pragma("wal_checkpoint(TRUNCATE)")call inStore.close()(src/store/db.ts).npm run backup/ docker exec helper that runssqlite3 /data/keyfount.db '.backup /data/snap.db'for an online consistent backup, even without litestream.Acceptance criteria