Backups: the docs understate losing the secret key, the archive leaves out plugins, and there is no safe restore in a container #234

Closed
opened 2026-09-15 21:23:30 +00:00 by tiagoagueda · 0 comments
Owner

Backup and restore are well built for SQLite. The gaps are in what the archive holds and in how to restore safely. Found in the 2026-09-15 code audit.

1. Losing the secret key loses more than sessions

Backups and your data (lines ~159-160) says losing POSTULO_SECRET_KEY "will not lose your data, but it will log everyone out". Unless POSTULO_FIELD_KEY is set, that key is the material every connection secret (plugins/secrets.py:25) and the Web Push signing key (plugins/browser/webpush.py) are derived from. Configuration (lines ~341-342) already says so; the backup page contradicts it.

2. The archive omits what a restore needs

write_backup archives only the database and media (core/backup.py:220-254). It leaves out:

  • /app/data/plugins: the record plus the installed packages;
  • any fingerprint of the field key.

A restore onto a fresh instance keeps connection rows whose plugins and secrets are gone, and says nothing.

3. There is no safe restore procedure in a container

  • The wiki's restore steps say to restore onto an empty instance and "run nothing else", but give only a uv run command.
  • In Docker the only route is exec into a running web container, with gunicorn and the scheduler live while load_database overwrites the database (backup.py:154-163) or pg_restore --clean runs (:175-189).
  • Media not in the archive is never pruned (:375-386).
  • Restoring by hand says to put postulo.sqlite3 back but never mentions deleting the stale -wal and -shm files that WAL mode (#206) leaves beside it.

Proposal

  • Fix the wiki sentence about the secret key.
  • Include the plugins record (or the whole plugins directory) in the archive, and store a hash of the active field key in the manifest. restore warns loudly on a mismatch.
  • Document a container restore: docker compose stop postulo scheduler, then docker compose run --rm -e POSTULO_SKIP_MIGRATE=1 postulo python manage.py restore ….
  • restore refuses while other database connections or the scheduler are active, unless --force.
  • Add the WAL sidecar step to the manual instructions.
  • A test that restores into a fresh data directory and finds the plugins record and a matching key fingerprint.
Backup and restore are well built for SQLite. The gaps are in what the archive holds and in how to restore safely. Found in the 2026-09-15 code audit. ## 1. Losing the secret key loses more than sessions *Backups and your data* (lines ~159-160) says losing `POSTULO_SECRET_KEY` "will not lose your data, but it will log everyone out". Unless `POSTULO_FIELD_KEY` is set, that key is the material every connection secret (`plugins/secrets.py:25`) and the Web Push signing key (`plugins/browser/webpush.py`) are derived from. *Configuration* (lines ~341-342) already says so; the backup page contradicts it. ## 2. The archive omits what a restore needs `write_backup` archives only the database and media (`core/backup.py:220-254`). It leaves out: - `/app/data/plugins`: the record plus the installed packages; - any fingerprint of the field key. A restore onto a fresh instance keeps connection rows whose plugins and secrets are gone, and says nothing. ## 3. There is no safe restore procedure in a container - The wiki's restore steps say to restore onto an empty instance and "run nothing else", but give only a `uv run` command. - In Docker the only route is `exec` into a running web container, with gunicorn and the scheduler live while `load_database` overwrites the database (`backup.py:154-163`) or `pg_restore --clean` runs (`:175-189`). - Media not in the archive is never pruned (`:375-386`). - *Restoring by hand* says to put `postulo.sqlite3` back but never mentions deleting the stale `-wal` and `-shm` files that WAL mode (#206) leaves beside it. ## Proposal - Fix the wiki sentence about the secret key. - Include the plugins record (or the whole plugins directory) in the archive, and store a hash of the active field key in the manifest. `restore` warns loudly on a mismatch. - Document a container restore: `docker compose stop postulo scheduler`, then `docker compose run --rm -e POSTULO_SKIP_MIGRATE=1 postulo python manage.py restore …`. - `restore` refuses while other database connections or the scheduler are active, unless `--force`. - Add the WAL sidecar step to the manual instructions. - A test that restores into a fresh data directory and finds the plugins record and a matching key fingerprint.
tiagoagueda added this to the 0.4.0 milestone 2026-09-15 21:33:28 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Postulo/postulo#234
No description provided.