Actions
23 actions, callable from the panel, the command palette and the API as
POST /api/v1/a/<id>. Internal actions used between modules are not listed.
| Action | What it does | Risk | Preview |
|---|---|---|---|
backups.target.create | Create backup target | medium | No dry run |
backups.target.delete | Delete backup target | high | |
backups.target.test | Test backup target (read) | low | No dry run |
backups.target.list | List backup targets (read) | low | No dry run |
backups.job.create | Create backup job | medium | |
backups.job.update | Update or pause a backup job | medium | No dry run |
backups.job.delete | Delete backup job | medium | No dry run |
backups.job.list | List backup jobs (read) | low | No dry run |
backups.job.run | Back up now | low | No dry run |
backups.run.list | List backup runs (read) | low | No dry run |
backups.status | Backup status of an account (read) | low | No dry run |
backups.snapshot.list | List snapshots (read) | low | No dry run |
backups.snapshot.browse | Browse a snapshot (read) | low | No dry run |
backups.snapshot.delete | Delete a snapshot | high | No dry run |
backups.restore.files | Restore files | high | |
backups.restore.database | Restore a database | high | |
backups.restore.account | Restore a whole account | critical | |
backups.restore.rollback | Roll back a restore | high | No dry run |
backups.verify.run | Check a repository | low | No dry run |
backups.verify.test_restore | Test restore | low | No dry run |
backups.retention.preview | Preview retention (read) | low | No dry run |
backups.recovery.export | Export recovery bundle (read) | critical | No dry run |
backups.reconcile | Re-apply backups | low | No dry run |
Permissions and limits
Permissions
backups.viewView targets, jobs, snapshots and runsbackups.target.manageCreate and delete backup targetsbackups.job.manageCreate, change and delete backup jobsbackups.job.runRun a backup nowbackups.snapshot.manageDelete snapshotsbackups.restoreRestore files, databases and roll backbackups.restore.accountRestore a whole accountbackups.verifyCheck repositories and test restoresbackups.recoveryExport the recovery bundlebackups.reconcileRe-apply schedules and repositories
Plan limits
backups.jobsBackup jobsbackups.targetsBackup targetsbackups.storage_gbBackup storage on this serverbackups.retention_daysLongest daily retentionbackups.manual_runs_per_dayManual backups per day
Engineering notes
Generated from modules/backups/docs.md at build d90e9e2. These are the notes the engineers keep
next to the code: precise, technical, and honest about what is not done yet.
Model
- Target = one restic repository for one account (own random 40-char password in the vault,
target/<id>; ADR 0013 §7). Kinds:local(/var/lib/rc-backups/<account>/<id>, root 0700),connection(anystorageconnection through restic's rclone backend, path below the connection's base path) ands3(a private bucket + two keys created on the built-in store through the publicstorage.*actions: a backup key and a prune key, both in the vault). Creating a target installs restic, runsinitand fails cleanly (everything created is rolled back) when it cannot write/read. - Immutability (
immutable_daysons3): the bucket is created with S3 object lock (COMPLIANCE, default retention). Proven in the lab: a permanent delete of a snapshot object version with the target's own credentials isAccessDenied, the panel refuses to force-delete the bucket,accounts.deletedkeeps the bucket. Honest limit: SeaweedFS has no "write without delete" action, so a plain S3 DELETE (whatrestic forgetdoes) only adds a delete marker - the data stays recoverable until retention ends but restic no longer sees that snapshot. Prune cannot free space inside the window. Backup, check, snapshots, ls, stats use--no-lock; forget/prune must lock (restic refuses otherwise), so on a locked bucket a lock file may outlive the run (see follow-ups). - Job = scope (paths relative to the home, excludes as restic globs,
databases: export every database first) + schedule (interval>= 15 min,daily,weekly,monthly, account time zone, up to 5 min stable jitter) + retention (keep-last/hourly/ daily/weekly/monthly/yearly, default 7d/4w/3m, daily capped bybackups.retention_days) + targets (each gets every run, per-target result).backups.schedule.tick(every minute, system actor) advancesnext_runbefore running, so a slow run never repeats, runs at most 5 due jobs per tick, a 5 %restic checkper target weekly, a test restore monthly, and raisesbackups.staleafter 48 h without success (once a day). - Run (
backups.job.run, or the tick): per-target busy flag (one backup/restore/check per target, stale after 3 h) ->databases.db.exportinto<home>/rc-backup/db/<db>.sql.gz(a database that fails to export fails the run: no silent partial backups) ->restic backup <home> --one-file-system --exclude-cachesundernice 19 / ionice idle-> 3 attempts with backoff -> stats -> retention (forget + prune with the prune credential, only after success, only snapshots taggedrcof that account; pre-restore safety snapshots are taggedpre-restoreand never pruned). Limits:backups.manual_runs_per_day(per target-run),backups.storage_gb(local and built-in S3 targets; skipped run +backups.quota.exceeded, nothing deleted). - Restore:
restore.files(default into<home>/restore-<time>/with the snapshot's relative tree, ownership/mode/symlinks kept,--verify;inplaceover the originals;otherinto another account on the same server, re-owned),restore.database(replacewith a safety dump first,newdatabase,downloadthe dump),restore.account(plan via dry run, typed account name, safety snapshot first, files in place then every database import, rollback within 24 h viarestore.rollback). All dry-runnable. verify.run(restic check, optional subset),verify.test_restore(random 5 files into a scratch dir, hash verify, cleanup),retention.preview(forget --dry-run),recovery.export(admin, critical: repository locations + passwords + S3 keys; audited).
Verified in the lab (2026-10-09)
Connect external S3 (fake cloud) -> init repo through rclone -> job with files + MariaDB -> run twice (second run added 4.7 kB of 3 MB)
-> delete files and drop a table -> alternate-folder restore (sha256 equal, mode 640 kept), in-place restore, database replace (row
data back) -> whole-account restore after deleting www/report.bin and dropping a table, then rollback -> verify 100 %, test
restore, retention keep-last 2 removed 2 of 4 snapshots on local and S3 targets -> immutable target proof -> restore into a second
account -> scheduler ran the due job -> account deletion removed targets, jobs, local repo, keys and connection config.
Known gaps / next (see handover)
Agent op timeout is 60 s (module SDK cannot set it) so big backups will fail until Host.Agent takes a timeout; no progress/cancel;
mail and containers volumes, PITR, server-level repo + DR restore, tar.zst archive download, mailbox restore, domain remap, cron
expressions, restoring deletes nothing that is not in the snapshot (a rollback does not remove files the restore added),
PostgreSQL "replace" needs a clean import (databases.db.import does not drop existing tables), restic/rclone from the distro
(0.16.4 / 1.60.1), per-snapshot delete.