setup-guides

Cornerstone managed off-site backup (Backblaze B2)

The Cornerstone Vault keeps a family's sealed data in Supabase (Postgres rows + private Storage blobs). Managed off-site backup holds an encrypted second copy on a genuinely separate provider so that losing our primary store — account termination, provider outage, corruption — doesn't lose the family's data.

Because every vault item is already ciphertext, the second copy is sealed bytes too. The backend reads ciphertext (fine) and never a key — honest custody holds even though we run the backup.

What it does when enabled

  • Back up off-site now (POST /api/cornerstone/backup/run) — assembles the user's whole vault into the same cornerstone-vault-backup archive the client-side "Download backup" produces, and uploads it as one object per user to your B2 bucket.
  • Restore from off-site (POST /api/cornerstone/backup/restore) — pulls that archive and re-creates the items (adds them; restore into a fresh vault).
  • Status (GET /api/cornerstone/backup/status) — last backup time, item count, bytes, last error. Surfaced in the vault's Cold-backup panel.

A managed backup and a family-held "Download backup" file are the same format, so either can restore the other.

Enable it

Set all four env vars on the backend (Railway). Missing any one → the feature reads as unconfigured: every route returns BACKUP_NOT_CONFIGURED and the UI shows "not set up on this deployment yet." Nothing runs until it's fully configured.

CORNERSTONE_B2_KEY_ID=<B2 applicationKeyId>
CORNERSTONE_B2_APP_KEY=<B2 applicationKey>
CORNERSTONE_B2_BUCKET_ID=<the bucket's id (not the name)>
CORNERSTONE_B2_BUCKET=<the bucket's name (for the download URL)>

Create these in the Backblaze console:

  1. Create a bucket — set it Private (never public; it holds sealed archives).
  2. Create an application key scoped to that bucket with read + write. Copy the keyID and applicationKey (shown once).
  3. The bucket id is on the bucket's detail page; the bucket name is what you named it.

Apply migration 607 (cornerstone_backup_status) — status only, no vault content.

⚠️ Smoke-test before trusting it

The B2 network wire (apps/backend/src/services/b2-client.ts, native B2 v2 API via fetch, no SDK) is code-complete but was not testable without live credentials. Before relying on it for real family data:

  1. Configure the four env vars against a throwaway private bucket.
  2. Seal a note and a small photo in the vault.
  3. Back up off-site now → confirm an object appears in the B2 bucket and the status row shows the item count.
  4. Delete an item locally, Restore from off-site → confirm it comes back and opens with its passphrase.
  5. Only then point it at the real bucket.

Not yet (follow-ons)

  • Scheduled auto-backup (a cron) — deliberately not built yet, so there's no recurring-task heartbeat/toggle obligation. It's manual ("Back up now") for now. When added, wire the heartbeat + CRON_REGISTRY toggle per the recurring-tasks doc.
  • Incremental per-item diff (currently a full-vault archive each run — fine at family scale).
  • S3 Glacier Deep Archive tier as a cheaper cold option (the client is B2-native today; the interface is small enough to add an S3 backend behind the same config).

See docs/product/CORNERSTONE_DATA_LAYER.md and the honest-custody standard.

CORNERSTONE OFFSITE BACKUP — Docs | HiveJournal