Skip to content

Backup and restore

A UniDPP deployment’s state is a small set of append-only files. This page documents what they are, the consistency model that makes copying them safe while services run, and the unidpp-ops backup/unidpp-ops restore tooling contract.

Status of the tooling. The unidpp-ops backup/restore commands are implemented by the unidpp-ops program (built from ops-tools/ in unidpp-pilot-data). The manual tar+shasum procedure below is kept as the transparent form of the same contract; read it to understand what the program does. Everything below about the state layout and the consistency contract is verified against the running deployment; the exact command surface (unidpp-ops backup <tenant>, unidpp-ops restore <archive> <tenant>, the console Backups page) is the contract the tool implements.

File Owner Restores
unidpp-operator.yaml the operator the deployment declaration itself
registry-journal.jsonl registry every item, version, binding, discovery record
trust-journal.jsonl trust the trust graph, lists, revocations
log-journal.jsonl log the sequenced commitments and tree
issuer-journal.jsonl issuer every passport, event, pack record
archive-journal.jsonl archive every snapshot’s notarized record
archive-snapshots/*.json archive the AIP files (re-materializable from the journal)
passports/*.json projector the passport document store
seed assets the operator reproducibility of the initial state

For a tenant, all of the above live inside tenants/<name>/ (plus run/tenants/<name>/ for logs and pids, which are not state).

Every journal is append-only JSONL; services replay them on start. No service rewrites its journal, so a file copy taken while services append is at worst a torn tail, and every journal replay checks sequence, digest, and torn-tail integrity, refusing to continue past a torn record rather than silently accepting a partial line.

The consistency point across services is the transparency log’s signed tree head: at backup time, record GET /log/tree/head (tree size + root + signature). A restore that replays journals and then verifies the tree head against the recorded consistency point proves the backup is coherent across services, because the log anchors the archive snapshots and the issuer’s verdicts reference the log head.

Terminal window
$ curl -s http://127.0.0.1:8392/tree/head | jq '{tree_size, root: .root[0:16], log_id}'
{
"tree_size": 5,
"root": "d23b199b96ed73ea",
"log_id": "unidpp-pilot-log-1"
}

The signature over the full head (in the same response) is what makes the point verifiable, rather than merely recorded.

The backup contract (unidpp-ops backup <tenant>)

Section titled “The backup contract (unidpp-ops backup <tenant>)”
Terminal window
$ ./unidpp-ops backup
backup: unidpp-reference-20260908T053255Z
files: 27 consistency point: size 5 root d23b199b96ed73ea7a7e...
archive: backups/unidpp-reference-20260908T053255Z.tar.gz
$ ./unidpp-ops verify backups/unidpp-reference-20260908T053255Z.tar.gz
verified: 27 member(s), consistency point size 5 root d23b199b...

A tampered archive fails verification naming the corrupted member; a restore of the reference deployment’s backup served the identical 1 531 registry items, with parity proven.

What the tooling must produce, and what you should demand of any hand-rolled equivalent:

  1. Contents: the operator manifest, every journal, the snapshot directory, the passport store, and the seed assets of the tenant.
  2. A manifest-of-the-backup: what was copied, when, and a SHA-256 per file, so the backup is verifiable the same way everything else in the system is.
  3. The consistency point: the log tree head recorded at snapshot time (from the live log service if the deployment declares one).
  4. Taken hot: services keep running; append-only journals make this sound (above).
  5. Versioned: backup files carry the deployment name and timestamp; nothing overwrites a previous backup.

The restore contract (unidpp-ops restore <archive> <tenant>)

Section titled “The restore contract (unidpp-ops restore <archive> <tenant>)”
  1. Unpack into a fresh tenant directory; refuse to overwrite an existing tenant without an explicit --force.
  2. Verify checksums before writing anything; a tampered or corrupt archive is a hard failure naming the file.
  3. Validate the manifest with unidpp-config validate; a restore of a manifest that no longer validates is refused.
  4. Start via unidpp-stack tenant <tenant> up; journals replay.

Acceptance (the test the tooling must pass): backup → restore → start → the registry serves the same item count as before, and the log verifies its head against the recorded consistency point.

Terminal window
# 1. Record the consistency point (if the deployment declares a log).
$ curl -s http://127.0.0.1:8392/tree/head > /tmp/tree-head-$(date -u +%Y%m%dT%H%M%SZ).json
# 2. Copy the tenant's state (services stay up).
$ tar -C unidpp-pilot-data -czf acme-backup-$(date -u +%Y%m%dT%H%M%SZ).tgz \
tenants/acme
# 3. Checksum the archive (the manifest-of-the-backup, minimal form).
$ shasum -a 256 acme-backup-*.tgz > acme-backup-*.tgz.sha256
# Restore into a fresh directory:
$ mkdir -p unidpp-pilot-data/tenants/acme-restored
$ tar -C unidpp-pilot-data/tenants/acme-restored -xzf acme-backup-<stamp>.tgz
# (adjust the manifest's state_file paths if the directory name changed)
$ unidpp-config validate unidpp-pilot-data/tenants/acme-restored/unidpp-operator.yaml
$ ./ops/target/release/unidpp-stack tenant acme-restored up # journals replay

The console’s Backups page lists backups in the configured directory, triggers one by invoking the program (the console never reimplements the logic), and shows the consistency point. It is session-gated like every console mutation.

  • Secrets. The manifest carries ${VAR} references; the values live in your secret manager. A restored deployment needs the same environment — by design, a stolen backup yields no tokens.
  • Signing keys. Keyrings derive from seed material in the environment (UNIDPP_ISSUER_SEED, UNIDPP_LOG_SEED, …). Back up the seed configuration the same way you back up other secrets: separately, and more carefully. Lose the seed, lose the identity: re-issued anchors do not verify against previously published ones.
  • Ephemeral runtime: run/ pids and logs are not state.