Add a backup-destination plugin kind that receives each verified archive, set up by staff under Server settings → Backups #325
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#325
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The DAV improvements ask the plugin to "backup postulo data (on the server/admin side)" (postulo/postulo-dav#6), and the core gives a plugin no way to.
write_backup(src/postulo/core/backup.py:325-402) writes and verifies one.tar.gzinPOSTULO_BACKUP_DIR, and calls nothing afterwards. No plugin kind is about backups:StorePlugin.puttakes one person's document, the connected kinds hang off aConnection, whose owner is required, and the only instance-level plugin configuration is the transports' columns onSiteSettings(src/postulo/core/models.py:1004-1021). #242 (open, 0.6.0) plans the Backups section of Server settings (list, back up now, download, delete, schedule, retention, metrics) but names no destination, and leaves passphrase encryption undecided (§3). Its dependencies (#219, #220, #221, #234) are closed; this lands with it or after.Proposal
1. The kind
A backup-destination kind with its own entry-point group (say
postulo.backups) and a small protocol onpostulo.plugins.api: the usualconfig_fields(),validate()andtest(), plussend(archive, manifest, config), returning a reference (an id of the plugin's choosing and a label) and raising on failure;list(config), the references of the archives it holds there;delete(reference, config).archiveis the verified archive as it leaves (encrypted, §4), read in chunks, with its name and size;manifestis plain values, for naming and logging, never written beside the archive in clear.Like a transport it is the operator's plumbing: not a connected kind, and not under the per-person plugin policy (#95;
UNGOVERNED_KINDSinsrc/postulo/plugins/policy.py).A kind touches
registry.GROUPS(which the installer reads,src/postulo/plugins/installing.py:120-122),LABELSandTONESinsrc/postulo/plugins/kinds.py, and the table of kinds in Writing a plugin.2. When it runs
After an archive verifies, from the schedule or Back up now, a send to the destination is queued. The scheduler sends and retries it with a growing wait, as it sends document copies (
src/postulo/documents/archiving.py: a claim, a lease, a cap on attempts), not as an errand, which never retries (#247). An upload can take far longer than a document copy, so it must neither delay the next pass's reminders nor outlive its own claim. Remote retention runs only after a send is confirmed, keeps the newest N (its own count, defaulting to the local one; or none deleted, for a credential that cannot delete), and deletes only whatlist()returned. A failed send never deletes or rotates the local archive.3. What staff see
Staff choose the destination in the Backups section, on a form drawn from its
FieldSpecs as a transport's is (src/postulo/core/server_forms.py:560); secrets are encrypted under the same key as connections' and transports', masked, and kept when left blank. Each archive on the list says where it went: sent (when, and the remote name), waiting, or failed in the plugin's words. A failed send shows on Overview as a failed run does (#242 §5). Metrics beside #242's, such aspostulo_backup_offsite_last_success_timestamp_secondsandpostulo_backup_offsite_failures_total.4. Encryption
The archive holds everyone's data in clear; only the stored secrets (connections', transports', the mail password) are encrypted, under a key it leaves out. #242 §3 must be settled, here or there, before any archive leaves the machine; its never-stored passphrase cannot serve a scheduled send. An age recipient can: only the public key is kept on the instance, the identity stays with the operator, and the wiki already names age. Restoring then starts with decrypting by hand.
5. Out of scope
Restoring from a destination: download the archive by hand and restore as today (#242 §6).
Decisions
SiteSettingslike the transports' (plugin name, JSON configuration, encrypted secrets); (b) a staff-ownedConnection; (c) a small staff-only table. Recommended: (a), for one destination. (b) is anOwnedModel, deleted with its owner's account (on_delete=CASCADE), so an administrator leaving would silently stop off-site backups. (c) if several destinations are wanted later.manage.py backup. Recommended: an archive written intoPOSTULO_BACKUP_DIRqueues a send, with--no-sendto skip; one written elsewhere does not.Security
Staff only: every new URL answers 404 to anyone else. A log line for each send and each remote delete (archive, destination, outcome), and one naming who changed the destination. A destination's requests go through
api.client(), so the destination policy applies.Needed by
postulo/postulo-dav#6.
Done when
postulo.plugins.apieach have their line in Writing a plugin (tests/test_wiki_surface.py, which also wants the new group named), and are covered by the publishedpostulo.plugins.testingcheck andtests/test_plugin_surface.py.tests/test_page_coverage.py) and the isolation sweep (tests/security/test_isolation_sweep.py); the metrics are on Health, metrics and logs.From the list of DAV plugin improvements of 2026-09-30.