scribase

Operations

Portable logical backups

Portable logical backups with SHA-256 evidence and verified restore.

scribase-backup create is the production-safe primitive for independent logical database exports. It uses PostgreSQL custom format, validates the completed archive through pg_restore --list, calculates SHA-256 evidence, writes a versioned JSON manifest, and publishes both files atomically without overwriting an earlier artifact.

export SCRIBASE_BACKUP_DATABASE_URL='postgresql://backup-role:...@db/app?sslmode=require'
export SCRIBASE_BACKUP_DIRECTORY=/var/lib/scribase/backups
scribase-backup create acme billing production nightly-2026-09-13

The output directory must be an absolute path backed by durable encrypted storage. The database URL is passed through PGDATABASE, never a process argument, and is redacted from debug and error output. Use a dedicated login with CONNECT plus the minimum schema/table/sequence privileges needed by pg_dump; it must not have BYPASSRLS, role creation, or database creation. Archive and manifest files are created with mode 0600. Publication uses same-filesystem no-clobber links, so two workers cannot overwrite the same backup ID. PostgresBackupExecutor::verify_artifact rechecks the byte length, SHA-256 digest, and pg_restore readability before an archive is eligible for restore.

Optional bounds:

Variable Default
SCRIBASE_BACKUP_TIMEOUT_SECONDS 3600 (maximum 24 hours)
SCRIBASE_BACKUP_MAXIMUM_BYTES 107374182400 (maximum 10 TiB)
SCRIBASE_PG_DUMP_BINARY pg_dump
SCRIBASE_PG_RESTORE_BINARY pg_restore
SCRIBASE_SHA256SUM_BINARY sha256sum

This primitive proves archive creation and offline readability. Durable control-plane backup records remain a separate required layer; a successful archive alone is not restore evidence.

Uploading to versioned object storage

scribase-backup upload copies a published archive and manifest to an S3-compatible bucket (AWS S3, Cloudflare R2, MinIO, Backblaze B2) under content-addressed keys. It reconstructs the artifact from disk and recomputes its SHA-256 first, so a corrupt local file never reaches the bucket, and no database connection is required.

export SCRIBASE_BACKUP_DIRECTORY=/var/lib/scribase/backups
export SCRIBASE_OBJECT_STORE_BUCKET=scribase-backups
export SCRIBASE_OBJECT_STORE_REGION=auto
export SCRIBASE_OBJECT_STORE_PREFIX=scribase
export SCRIBASE_OBJECT_STORE_ENDPOINT='https://ACCOUNT.r2.cloudflarestorage.com'  # optional
export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=...                          # AWS_SESSION_TOKEN optional
scribase-backup upload acme billing production nightly-2026-09-13

Each object key embeds the file's SHA-256, so identical content always maps to the same immutable key. Every put-object passes the expected SHA-256 and the store rejects any body that does not match, giving server-side integrity on ingest. Object-store credentials are injected through the child environment and never appear in process arguments; the endpoint must be an https URL. The upload is idempotent — re-running re-puts the same bytes to the same keys and accepts a matching local *.upload.json receipt.

Variable Default
SCRIBASE_OBJECT_STORE_ENDPOINT AWS default endpoints
SCRIBASE_UPLOAD_TIMEOUT_SECONDS 1800 (maximum 6 hours)
SCRIBASE_AWS_BINARY aws
SCRIBASE_SHA256SUM_BINARY sha256sum

A restore into an isolated staging database with post-restore verification (the prepare-restore-target and restore executor below) remains the final required layer.

Guarding a restore target

Before any archive can be restored, a newly provisioned empty staging database must receive an identity-bound one-time guard:

export SCRIBASE_RESTORE_DATABASE_URL='postgresql://restore-role:...@db/staging?sslmode=require'
export SCRIBASE_RESTORE_ALLOW_MUTATIONS=isolated-staging-only
scribase-backup prepare-restore-target acme billing restore-check NONCE

Preparation runs in a serializable transaction under an advisory lock. It refuses any existing user relation, creates only the scribase_restore_guard schema, and records the organization, project, environment, and hex-encoded nonce. An identical retry succeeds; a different identity or nonce fails. The database URL remains in PGDATABASE and raw PostgreSQL errors are not exposed. This guard is the mandatory prerequisite for the forthcoming restore executor; the current command does not run pg_restore itself.