open-standards/openpay/docs/deploy/backup.md
Transaction Science 70ecd6caa0 Initial import — three open standards under one shelf
Transaction Science open standards monorepo. Each subdirectory is a
self-contained reference protocol with its own README and licence.

- openpay/    Apache-2.0  payment-acceptance stack (Rust, 26 crates)
- smart-byte/ CC-BY-4.0   content-addressed value carrier (spec)
- eoc/        CC-BY-4.0   energy-optimised compute (spec)

The wire format and the right to fork are public. The optional hosted
services are the offer; the protocols are not.
2026-05-23 07:58:09 -04:00

4.2 KiB

OpenPay — backup and restore

The entire deployment — refunds, disputes, settlement batches, ledger, reconciliation, webhooks, subscriptions, idempotency cache — lives in one file: the .graph Minigraf database pointed at by AppState::with_graph_path. Backup is "copy that file"; restore is "put it back."

This is the single biggest win of the Phase 26 single-file persistence model (docs/26-graph-persistence-progress.md): no multi-table dump, no schema-aware dump tool, no per-store backup job. One file. Atomic.

What you're backing up

By default the systemd unit puts the file at /var/lib/openpay/data.graph. (See OP_GRAPH_PATH in openpay.env.sample.) If you configured a different path, that's the one.

The honest caveat: hot copies can miss recent writes

Minigraf holds in-memory state that the on-disk file lags slightly behind. A cp / rsync of the file while op-server is serving traffic will get you something close-but-not-equal to the in-memory truth — the last few seconds of writes may be missing, and depending on what's mid-flush you may copy a partial page.

Two ways to get a clean snapshot:

sudo systemctl stop openpay
sudo rsync -av /var/lib/openpay/data.graph \
    backup-host:/snapshots/openpay-$(date +%F-%H%M).graph
sudo systemctl start openpay

The service shutdown handler (SIGTERM → shutdown_signal in crates/op-server/src/main.rs) drains in-flight requests cleanly, and Drop on GraphHandle flushes pending writes. Typical service downtime: under five seconds for graphs in the single-digit-GB range.

Option B — call compact() first, then copy

GraphHandle::compact() (see crates/op-graph/src/graph.rs:239) flushes pending writes and rewrites the file in compact form. Right after compact() returns, the on-disk state is consistent with what was in memory up to that moment. There's no shipped HTTP endpoint for this — you'd add one to your patched main.rs (an operator-only path on a local-only socket, ideally) or trigger it from a maintenance binary that opens the same graph path.

This still doesn't capture writes that arrive between compact() and the copy. For that you want Option A.

restic -r s3:s3.amazonaws.com/openpay-backups init    # first time only

sudo systemctl stop openpay
sudo restic -r s3:s3.amazonaws.com/openpay-backups \
    --tag openpay --tag $(hostname) \
    backup /var/lib/openpay/data.graph
sudo systemctl start openpay

# Verify periodically:
sudo restic -r s3:s3.amazonaws.com/openpay-backups check

restic deduplicates across snapshots — a daily backup of a mostly-static graph file is cheap. Pair with restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 to age out old snapshots.

Restore

Stop the service, drop the file in place, start the service. Minigraf opens it on startup; every store recovers automatically.

sudo systemctl stop openpay
sudo cp /snapshots/openpay-2026-05-20-0300.graph \
        /var/lib/openpay/data.graph
sudo chown openpay:openpay /var/lib/openpay/data.graph
sudo systemctl start openpay

./target/release/op readiness
# Stores should all answer ready.

No migrations to run. No "reseed the index." The audit-report endpoint (GET /v1/audit/report) will reflect the restored state.

What about the in-memory graph?

AppState::new_in_memory() keeps everything in RAM and drops it on restart. There is nothing to back up; there is also nothing to recover. If you're running on the in-memory graph, you accepted that. (Suitable for: tests, demos, ephemeral kiosks. Not for: anything you want to see tomorrow.)

Cross-host restore

The .graph file is portable across hosts of the same Rust target triple. Copy it to a new machine, install the matching op-server binary, point OP_GRAPH_PATH at the file, start the service — done. No external dependencies, no DB cluster to migrate.

What about bi-temporal history?

The ledger's bi-temporal history (op_ledger::LedgerHistory) is stored in the same .graph file. Restoring the file restores the history. Time-travel queries (balance_as_of, the audit report's as_of parameter, etc.) work against the restored state with no extra work.