# Backup, Restore, and Recovery

Audio Vault protects your library with **private object storage** (Cloudflare R2) and a **PostgreSQL catalog**. This guide explains what you should keep locally, what we back up, and how recovery works.

Operational runbooks for production are in [Operator Procedures](#operator-procedures) below.

## For Customers

### Keep Your Own Source Copies

Audio Vault is designed to **preserve originals you upload**, not to replace every copy on your NAS, rip workstation, or portable drives. **Keep your source folders** until you have verified uploads in the library and are comfortable with your backup posture.

Before large imports:

1. **Snapshot or copy** the source folder on your NAS or local disk (read-only copy is fine).
2. Upload through the PWA; wait for **verified** state (checksum confirmed server-side — a progress bar alone is not proof).
3. Spot-check a few tracks in the library and player before deleting local-only copies.

We do not promise unlimited zero-loss recovery in all disaster scenarios ([AV-R23](mvp/REQUIREMENTS.md)); we do commit to documented backup targets and honest status for every file.

### What We Store and Protect

| Data | Where | Your access |
|---|---|---|
| Original audio + approved sidecars | Private R2 bucket (US) | Owner-only playback and download authorization |
| Catalog metadata (edits, browse paths) | PostgreSQL | Library views; edits never rewrite embedded file tags |
| Account, billing status, upload manifests | PostgreSQL | Settings and quota display |

Customer audio **never** ships on the public website apex ([D-17](mvp/DECISIONS.md#infrastructure-and-operations)).

### Trash and Account Deletion

| Policy | Duration | Notes |
|---|---|---|
| **Trash** ([D-80](mvp/DECISIONS.md#retention-and-account-lifecycle)) | **30 days** | Deleted library items stay recoverable for 30 days |
| Trash quota ([D-81](mvp/DECISIONS.md#retention-and-account-lifecycle)) | — | Trashed bytes **still count** toward your storage quota |
| **Account deletion** ([D-82](mvp/DECISIONS.md#retention-and-account-lifecycle)) | **30-day soft delete**, then purge | Wizard **cancels Stripe first** ([D-134](mvp/DECISIONS.md#retention-and-account-lifecycle)) |
| Incomplete multipart uploads ([D-53](mvp/DECISIONS.md#uploads-files-and-integrity)) | Cleaned after **30 days** | Orphan parts do not become library tracks |

There is **no** self-service full-library export until public beta ([D-120](mvp/DECISIONS.md#legal-policies-and-accessibility)). Contact **privacy@audiovau.lt** for privacy-rights requests.

### Service Recovery Targets

Production targets ([D-14](mvp/DECISIONS.md#infrastructure-and-operations)):

| Metric | Target | Meaning |
|---|---|---|
| **RPO** (recovery point) | **24 hours** | Catalog database restores from at most ~24 hours before failure |
| **RTO** (recovery time) | **4 hours** | Aim to restore API + catalog access within four hours of a declared incident |

Object storage (R2) relies on Cloudflare durability; **full library recovery** requires both a healthy object store **and** a restored catalog that matches stored keys. Operations reconciles mismatches explicitly — there is no assumed cross-system transaction.

### If Something Looks Wrong

1. Check upload status in the PWA — rejected, paused, and failed states include reasons.
2. Do **not** delete local originals until uploads show verified.
3. Email **support@audiovau.lt** for billing or playback issues; **privacy@audiovau.lt** for deletion or privacy rights.

---

## Operator Procedures

For Muse & Machine operators and agents with VPS access. Production provisioning (Servarica, R2, daily backup jobs) remains **human-operated** ([AV-T21](mvp/TODO.md)); these scripts support **local drills** and the production runbook shape.

### Targets and Configuration

| Variable | Default | Role |
|---|---|---|
| `BACKUP_RPO_HOURS` | `24` | Maximum catalog data age at restore |
| `BACKUP_RTO_HOURS` | `4` | Target time to restore API + catalog |
| `ORPHAN_MULTIPART_RETENTION_DAYS` | `30` | Incomplete multipart cleanup |
| `TRASH_RETENTION_DAYS` | `30` | Customer trash window |
| `ACCOUNT_DELETION_RETENTION_DAYS` | `30` | Soft-delete before purge |

See [CONFIG.md](mvp/CONFIG.md) for full env keys.

### What to Back Up

1. **PostgreSQL** — accounts, upload manifests, catalog, billing event idempotency, auth-go tables. **Daily** `pg_dump` (or managed backup) with **24h RPO**.
2. **R2 bucket** — customer audio bytes. R2 provides durability; document bucket name and credentials in secure ops storage. After a catalog restore, run object/catalog reconciliation (no destructive cleanup of ambiguous rows).
3. **Secrets** — `.env` on the VPS, Stripe keys, R2 keys, Resend key. Store outside Git ([`.env.example`](../.env.example) is a template only).

### Scripts (Repo)

| Script | Purpose |
|---|---|
| [`scripts/backup-postgres.sh`](../scripts/backup-postgres.sh) | Gzip SQL dump via `pg_dump` (host or Compose) |
| [`scripts/restore-postgres.sh`](../scripts/restore-postgres.sh) | Restore from gzip dump (**destructive** to target DB) |
| [`scripts/backup-restore-drill.sh`](../scripts/backup-restore-drill.sh) | Disposable `audiovault_drill` DB drill for AV-T13 evidence |

**Local backup example:**

```sh
docker compose up -d postgres
DATABASE_URL=postgresql://audiovault:audiovault@127.0.0.1:5432/audiovault \
  ./scripts/backup-postgres.sh
```

**Production backup example** (on VPS with `DATABASE_URL` in `/etc/audiovault/audiovault.env`):

```sh
set -a && source /etc/audiovault/audiovault.env && set +a
BACKUP_DIR=/var/backups/audiovault ./scripts/backup-postgres.sh
```

Schedule daily backups via cron or your host's backup product. Retain at least **7 daily** snapshots before go-live review.

**Restore** (maintenance window, confirm target `DATABASE_URL`):

```sh
./scripts/restore-postgres.sh /var/backups/audiovault/audiovault-YYYYMMDDTHHMMSSZ.sql.gz
```

Then restart `audiovault-api`, verify `/health` and `/ready`, and spot-check tenant isolation tests.

### Restore Drill (Disposable)

Evidence for [AV-T13](mvp/TODO.md) — run on a developer machine:

```sh
./scripts/backup-restore-drill.sh
```

Expected output ends with `PASS: backup-restore drill succeeded`. Record date and environment in [VALIDATION.md](mvp/VALIDATION.md#av-t13-backup-restore-september-7-2026).

When Docker Compose is unavailable, the same script runs an **embedded Postgres** drill (`scripts/backup-restore-drill-go/`) that applies goose migrations, snapshots a marker account row, simulates loss, and restores it.

### Ansible / Vps Baseline

Server prerequisites (Postgres on localhost, Go toolchain, systemd unit template): [ansible/README.md](../ansible/README.md). Backup scheduling on production is part of [AV-T21](mvp/TODO.md) human provisioning.

### Incident Checklist

1. Declare incident; note start time (RTO clock).
2. Restore latest **Postgres** backup within RPO.
3. Confirm R2 bucket reachable; compare sample `object_key` rows to stored objects.
4. Run `go test ./internal/server/... -run Isolation` against restored environment.
5. Document actual RPO/RTO in VALIDATION.md.

---

## Related Documents

- [Privacy Policy](legal/privacy.md) — retention and deletion
- [REQUIREMENTS.md](mvp/REQUIREMENTS.md) — AV-R23 recovery scope
- [DECISIONS.md](mvp/DECISIONS.md) — D-14, D-53, D-80–D-82, D-134
- [VALIDATION.md](mvp/VALIDATION.md) — drill evidence
