Nextcloud backup target: admin-configured, manual + scheduled runs, in-app restore #103
Labels
No Label
area:auth
area:docs
area:export
area:ops
area:storage
area:supply-chain
auth
backend
blocked
collab
deployment
docs
effort:L
effort:M
effort:S
frontend
plugins
qa
vs-nfd
vs-nfd:blocker
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: stwaidele/dorfteich#103
Loading…
Reference in New Issue
Block a user
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?
Context
Backups today are local-only (nightly sidecar sets on the backups volume, #83) plus a planned host-specific rsync mirror to a private server (#84) that is blocked on network infrastructure and useless to other self-hosters. Dorfteich should instead offer a built-in, admin-configurable off-host backup target that any self-hoster has access to: a Nextcloud.
Scope
SmtpConfigServicepattern from #80), never into the DB. A "test connection" button verifies credentials + folder before saving (like the setup wizard's SMTP test)..tar.gzcontainingdb-<id>.dump,files-<id>.tar.gz, and a manifest) and uploaded via WebDAV — everything needed to rebuild an instance after total loss.BACKUP_RETENTION_DAYS) becomes an admin setting (number of sets/days to keep locally); a separate retention setting governs the Nextcloud side (prune old remote sets, never the newest complete one — same guarantee as #83).deploy/backup/restore.shwith a manually downloaded set) stays as the disaster-recovery fallback for when the app itself is gone — document fetching a set from Nextcloud in the restore runbook.status.json(contract inpackages/shared/src/backup-status.ts), shown on the #86 admin backup card and in the #85 readyz freshness check (warn when the remote copy is stale while a target is configured).Acceptance criteria
instance_settings, exports or logsTechnical notes
PAGE_RESTORE_CHANNELpattern, listener example inapps/collab/src/restore-listener.ts) — the sidecar gains a listener for "run backup now" / "restore set X" commands, the api publishes them and tracks progress viastatus.json(extend the shared contract).INSTANCE_SETTINGSregistry (apps/api/src/settings/instance-settings.service.ts) — non-secret keys only.Dependencies
Builds on #83 (sidecar), #85 (readyz), #86 (admin panel). Supersedes the generic need behind #84, which stays open as a personal, tunnel-blocked extra.
Size: ~3 days
Conventions: English code/comments, clear human-readable code, no hard-coded UI strings (ADR 0012, add
deanden), permission checks only via the shared guard (docs/architecture/permissions.md). Read the referenced ADRs before starting.Implemented in
5cef359(CI fully green, deployed to Test + Int).What shipped
@dorfteich/shared/webdav, PROPFIND/MKCOL/PUT/GET/DELETE, no new dependency).dorfteich-backup-<id>.tar.gz(db dump + files archive + manifest.json) and uploads it; schedule off/daily/weekly (manual runs always upload); remote prune honours its own retention and never deletes the newest bundle.backup_commandpg NOTIFY → sidecar (same LISTEN/NOTIFY primitive as the collab bus); nightly local run unchanged.backup.localRetentionDays(admin override; env stays authoritative until first save) andbackup.remoteRetentionDaysin the instance-settings registry.restore-status.json→ maintenance mode (api answers 503maintenance_modeeverywhere except health + publicGET /backup/restore-status) → collab closes/refuses sessions viabackup_maintenanceNOTIFY → terminate connections → same restore path asrestore.sh→ api restarts itself for a clean boot. SPA shows a maintenance status page for everyone and reloads when the instance is back.restore.sh+ runbook stay the disaster path; the runbook documents fetching a bundle from Nextcloud by hand.status.jsongained aremoteblock (contract in sharedbackup-status.ts); admin card shows upload state; readyz gained a warning-levelbackup_remotecheck (26 h daily / 170 h weekly); upload failures alert via a newbackupUploadFailedmail (de+en).Hardening found by the live verify (fresh stack + fake WebDAV server, full configure→backup→upload→remote-restore cycle): the restore's connection termination crashed the api through an unhandled worker rejection — conversion/mail/scheduler sweeps now catch transient DB failures; and
main.tswaits whilerestore-status.jsonsaysrunningso a container restarting mid-restore can never racemigrate deployagainstpg_restore.Notes / deviations
BACKUP_TIME); a separate upload-only clock did not seem worth the extra setting.backup-admin.e2e.db.test.ts) incl. maintenance-gate semantics + staleness; sidecar tests run against an in-process WebDAV server (streamed PUT, prune, bundle roundtrip); collab listener unit-tested.Acceptance criteria all met; credentials verified absent from
instance_settings, exports and logs.