Recovery is an operator runbook, not a public feature.
The platform should be recoverable without expanding member identity collection. This page defines the safe MVP recovery posture: PostgreSQL logical dumps, private media snapshots, encrypted/offsite retention, and manual restore drills that cannot accidentally overwrite live data.
What must be recoverable
| Component | Backup source | Recovery note |
|---|---|---|
| PostgreSQL | Logical dump using pg_dump --format=custom. | Contains pseudonymous accounts, entitlements, payments, catalog metadata, participation, reports, sessions, and CSRF-token digests. Do not treat it as anonymous. |
| Private media root | Filesystem snapshot/tar from NOCTURNE_MEDIA_ROOT. | Contains member-gated images/videos and safe preview fixtures; it must stay outside public web roots and git. |
| Environment/secrets | Out-of-band secret manager or owner vault. | Keep DATABASE_URL, owner keys, processor secrets, and future webhook secrets out of repository backups. |
| Deployment docs | Repo docs plus private deployment notes. | Document VPS provider, volumes, reverse proxy, offsite storage location, and restore drill dates. |
Backup posture variables
NOCTURNE_BACKUP_POSTURE=local
NOCTURNE_BACKUP_DIR=backups
NOCTURNE_BACKUP_MANIFEST_FILE=backup_manifests/manifest.log
NOCTURNE_BACKUP_CUSTODY_FILE=backup_custody/custody.log
| Posture | Meaning | Doctor behavior |
|---|---|---|
local | Default development/dry-run posture. | Warns locally; fails in production mode. |
configured | Owner has configured a local backup target for manual dumps/snapshots. | Requires NOCTURNE_BACKUP_DIR to exist and be writable. |
external | Backups are managed by an external/offsite host, volume, or provider-level system documented outside the app. | Attests posture without requiring a local app-side backup directory. |
The local doctor checks posture and target writability; it does not prove that offsite retention, encryption keys, or restore drills are actually healthy. Those remain owner operational tasks.
Non-destructive backup plan output
Use the local CLI for a redacted, copy-safe plan:
cargo run -- backup-status
cargo run -- backup-plan
The command prints the current deployment profile, backup posture, backup directory, media root, a redacted database URL, suggested pg_dump/tar commands that rely on environment variables, and a manual restore drill outline. It intentionally does not run dump, tar, restore, delete, or overwrite operations.
When a sealed backup artifact exists, generate a repeatable restore-drill checklist and evidence command without running restore operations:
cargo run -- restore-drill-plan \
--origin https://members.final-domain.example \
--backup-id backup-YYYYMMDD \
--manifest-file backup_manifests/manifest.log \
--custody-file backup_custody/custody.log \
--drill-id restore-drill-YYYYMMDD \
--artifact-sha256 64_hex_chars
# alias:
cargo run -- backup-restore-drill-plan
The restore-drill plan validates final-origin, backup ID, evidence-file, manifest-file, and custody-file guardrails, prints manifest/custody preflight checks for the selected sealed backup pair, prints isolated target placeholders, and emits the matching deployment-evidence-record --stage backup_restore_drill command. It is scaffold-only: it does not create databases, extract archives, run pg_restore, restart services, delete files, or touch live state.
Record sealed backup artifact hashes before restore-drill work:
cargo run -- backup-manifest-record \
--backup-id backup-YYYYMMDD \
--kind postgres_dump \
--artifact-sha256 64_hex_chars \
--summary "sealed postgres dump"
cargo run -- backup-manifest-record \
--backup-id backup-YYYYMMDD \
--kind media_archive \
--artifact-sha256 64_hex_chars \
--summary "sealed media archive"
cargo run -- backup-manifest-check --backup-id backup-YYYYMMDD
The backup manifest writes only secret-free metadata under backup_manifests/manifest.log by default, which is ignored by git. The check requires both postgres_dump and media_archive hashes for a backup ID before trusting it for restore-drill preparation.
Record encrypted/offsite custody and retention metadata after the sealed artifacts are copied out of the app host:
cargo run -- backup-custody-record \
--backup-id backup-YYYYMMDD \
--location offsite_encrypted \
--retention-until YYYY-MM-DD \
--evidence-sha256 64_hex_chars \
--summary "sealed offsite custody receipt"
cargo run -- backup-custody-check --backup-id backup-YYYYMMDD
The custody ledger writes only secret-free metadata under backup_custody/custody.log by default, which is ignored by git. The check requires current encrypted offsite/provider custody for the selected backup ID. It does not prove provider health, decryptability, or restore success.
Manual capture checklist
- Create
NOCTURNE_BACKUP_DIRwith owner-only permissions on a non-public path. - Run a PostgreSQL logical dump from the same environment that owns
DATABASE_URL. - Create a private media archive from
NOCTURNE_MEDIA_ROOT. - Encrypt artifacts before copying them off the VPS or workstation.
- Move encrypted artifacts to a separate provider or storage location.
- Record the backup timestamp, artifact hashes, byte sizes if available, and retention expiry in private operator notes.
- Append secret-free artifact hashes to
backup-manifest-recordso later restore drills can reference a complete dump/media pair. - Append secret-free encrypted/offsite custody metadata to
backup-custody-recordso private VPS readiness can verify current retention.
Manual restore drill rules
- Never restore into the live database first.
- Run
backup-manifest-check --backup-id ...andbackup-custody-check --backup-id ...for the selected backup ID immediately before any restore work; stop if either check fails. - Provision an isolated database and empty media directory for the drill.
- Restore the PostgreSQL dump with
pg_restoreagainst the isolated database only. - Extract private media into the isolated media root only.
- Point
DATABASE_URLandNOCTURNE_MEDIA_ROOTat the isolated targets. - Run
cargo run -- doctor, login/media smoke tests, report intake smoke tests, and owner dashboard smoke tests. - Document the elapsed restore time, missing files, migration/version issues, and corrective action in sealed operator notes.
- Hash the sealed notes/output and run the evidence command printed by
restore-drill-plan. - Record a
deployment-evidence-record --stage backup_restore_drillentry with an artifact hash for the sealed drill notes or doctor output.
Automated restore is intentionally deferred because a careless restore command can destroy live data. A future restore helper must require an isolated target, typed confirmation, a selected backup ID, a current backup manifest, and current encrypted/offsite custody before it exists.
MVP retention posture
| Artifact | Recommended minimum | Launch caveat |
|---|---|---|
| Local backup artifacts | Short-lived staging only; remove after encrypted/offsite copy is verified. | Keep /backups/ ignored by git. |
| Offsite encrypted backups | Daily during private test; keep a small rotating window until legal/accounting retention is decided. | Exact retention depends on jurisdiction, processor, and abuse-response obligations. |
| Custody metadata | Record every offsite encrypted/provider custody handoff with a retention date and sealed receipt hash. | Keep storage URLs, bucket names, credentials, encryption keys, and raw logs out of the custody file. |
| Restore drills | Before first external test and after schema/media-storage changes. | Do not claim production readiness without at least one successful isolated restore and a backup_restore_drill deployment-evidence record. |
What this foundation avoids
- No public backup status endpoint.
- No automatic restore command;
restore-drill-planis checklist/evidence scaffolding only. - No storage of backup artifacts, dumps, media archives, custody receipts, or secrets in git; only git-ignored secret-free manifest/custody metadata is recorded locally.
- No expansion of account identity fields for backup convenience.
- No provider-specific object storage, snapshot, or offsite service dependency until deployment provider selection is final.