2 Commits
Author SHA1 Message Date
Lauri Ojansivu 5ef5757c71 Continuous backup: encryption at rest, cloud upload, and a SQLite restore applied on restart.
As decided on 2026-10-03:

- Encryption: with Encrypt on, the built-in engines seal what they store -
  segments, page deltas, bases, blobs and state - with AES-256-GCM from
  Node's crypto. The key is a file the administrator holds outside the
  target and everything the stream reads, never in the database: 64 hex
  characters, or a passphrase stretched with scrypt and the stream's salt.
  stream.json keeps the salt and an HMAC check, so a wrong key is refused
  on save and before a restore; blob names are keyed HMACs; a generation
  records whether it is encrypted and is refused without its key.
- Cloud upload: the target is mirrored to S3/MinIO, Azure or GCS with the
  Attachments storage panes' adapters and credentials. Sealed files go up
  once, and an index or generation file again after the files it names, so
  the remote copy never lists a segment it lacks; retention removals follow,
  a failed upload is retried, and Fetch from cloud fills a lost target.
- SQLite restore on restart: a rebuilt, checked wekan.sqlite is staged in
  <db>/continuous-restore with RESTORE_REQUESTED "continuous". The Docker
  entrypoint, start-wekan.sh and the Snap keep the live database and its WAL
  in continuous-restore/replaced first, then copy the staged one in, before
  FerretDB opens its files. A pending request is never overwritten.

Tests: tests/continuousBackupEncryption.test.cjs, tests/continuousBackupCloud.test.cjs,
tests/continuousBackupRestartRestore.test.cjs (each script's real restore
block in a sandbox), encrypted integration tests on a real replica set and
WAL database, the server methods, and the pane in Chromium and WebKit.

Thanks to xet7 !
2026-10-03 08:52:16 +03:00
Lauri Ojansivu 6b9bc496b1 Add continuous backup to the Admin Panel: stream every change, restore to a moment.
Admin Panel / Attachments / Continuous backup, beside Backup, streams the
database, filesystem attachments, avatars and logs to a target directory
within seconds, so less work is lost between scheduled archives and a
restore can return to a chosen moment. Design: docs/Backup/Continuous-Backup.md.

The built-in engines use only copyfree code - WeKan's own (MIT), Node.js
and its node:sqlite (SQLite, public domain) - and start no other program:

- oplog: tails local.oplog.rs like Meteor's oplog driver, on a MongoDB
  replica set or FerretDB with its oplog; a base archive (the existing
  wekan-instance-backup format), then whole documents and deletions,
  continued from the saved position after a restart, a new generation
  when the oplog has lost it.
- sqlite: SQLite's online backup API on FerretDB's WAL database at an
  interval, storing only the pages that changed.
- files: attachments, avatars and logs by watching and by rescans, each
  content once under its SHA-256, links never followed.

Litestream (Apache-2.0) is an optional engine for SQLite: WeKan writes its
configuration and supervises a binary the administrator installed.

Segments are written whole, flushed and listed in append-only indexes.
A restore checks every index line, segment and blob first and writes
nothing when one is missing or changed; a database restore replays up to
the chosen moment, a file restore puts files back (add-missing or
replace-all), and a SQLite restore builds a checked file beside the stream,
never over the live one. Site administrators only; failures of the
unattended stream show in Admin Panel / Problems.

Tests: tests/continuousBackup.test.cjs; tests/integration/continuousBackup.test.cjs
against a real replica set, a real WAL database, real files and a stand-in
Litestream; server/lib/tests/continuousBackup.tests.js through the methods;
tests/playwright/specs/admin-continuous-backup.e2e.js on Chromium and WebKit.

Thanks to xet7 !
2026-10-03 01:47:42 +03:00