Backup & restore posture

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.

Local doctor visibility Manual restore drills No backup artifacts in git
Recovery contract

What must be recoverable

ComponentBackup sourceRecovery note
PostgreSQLLogical 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 rootFilesystem 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/secretsOut-of-band secret manager or owner vault.Keep DATABASE_URL, owner keys, processor secrets, and future webhook secrets out of repository backups.
Deployment docsRepo docs plus private deployment notes.Document VPS provider, volumes, reverse proxy, offsite storage location, and restore drill dates.
Environment

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
PostureMeaningDoctor behavior
localDefault development/dry-run posture.Warns locally; fails in production mode.
configuredOwner has configured a local backup target for manual dumps/snapshots.Requires NOCTURNE_BACKUP_DIR to exist and be writable.
externalBackups 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.

Owner command

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.

Backup capture

Manual capture checklist

  1. Create NOCTURNE_BACKUP_DIR with owner-only permissions on a non-public path.
  2. Run a PostgreSQL logical dump from the same environment that owns DATABASE_URL.
  3. Create a private media archive from NOCTURNE_MEDIA_ROOT.
  4. Encrypt artifacts before copying them off the VPS or workstation.
  5. Move encrypted artifacts to a separate provider or storage location.
  6. Record the backup timestamp, artifact hashes, byte sizes if available, and retention expiry in private operator notes.
  7. Append secret-free artifact hashes to backup-manifest-record so later restore drills can reference a complete dump/media pair.
  8. Append secret-free encrypted/offsite custody metadata to backup-custody-record so private VPS readiness can verify current retention.
Privacy reminder: backups inherit the sensitivity of the production database and media root. Even without emails, raw session IDs, ID documents, or raw IP/user-agent logs, the backup still contains private membership, payment-reference, moderation, and content-access records.
Restore drill

Manual restore drill rules

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.

Retention

MVP retention posture

ArtifactRecommended minimumLaunch caveat
Local backup artifactsShort-lived staging only; remove after encrypted/offsite copy is verified.Keep /backups/ ignored by git.
Offsite encrypted backupsDaily 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 metadataRecord 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 drillsBefore 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.
Non-goals

What this foundation avoids