Connect

agent.yaml reference

Canonical spec

The single source of truth for this schema lives with the open-source agent at restorable/docs/agent-config.md. This page renders that spec for the dashboard audience; if the two ever drift, the spec wins.

Every field the agent reads from config.yaml. In the recommended install, this file lives at /var/lib/restorable/config.yaml, owned root:restorable with mode 0640.

The agent validates the file at startup and refuses to run on a malformed config. Secrets never appear in the file directly; they are referenced with env: or file: indirection and resolved at use time.

Top-level shape

mode: managed

agent:
  id: agent_01hz...
  org_id: org_abc
  api_token: file:keys/api_token

orchestrator:
  url: https://app.restorable.app
  heartbeat_interval_s: 60

restore_scratch:
  kind: auto            # auto (default) | podman | docker | uri
  # image: postgres:16  # optional override; agent auto-picks per source major
  # uri: postgres://... # required when kind=uri

storage:
  # all optional. Each defaults to /.
  # blobs_dir:    /var/lib/restorable/blobs     # staged ciphertext
  # receipts_dir: /var/lib/restorable/receipts  # signed receipts
  # events_dir:   /var/lib/restorable/events    # local event log

sources:
  - slug: prod-db
    kind: postgres
    connection:
      uri: env:RESTORABLE_DB_URI
    checks:
      - healthy

checks:
  - id: healthy
    summary_sql: |
      SELECT 1 AS ok
    expect: "ok == 1"

mode

managed or standalone. setup writes managed; init writes standalone. The paying-customer path is managed. Standalone is the open-source, self-sufficient path for air-gapped installs; scheduling is done by external cron or systemd timers.

agent

agent.id

The identifier the orchestrator assigned at registration. Appears in every receipt the agent signs. Written by setup; do not edit.

agent.org_id

The org this agent belongs to. Written by setup; do not edit.

agent.api_token

Bearer token the agent presents on every orchestrator call. Always a secret reference (file: is the default, pointing at file:keys/api_token relative to the install root). Putting the token inline in the config file is rejected at validation.

orchestrator

orchestrator.url

Base URL of the Restorable orchestrator the agent talks to. Default: https://app.restorable.app. Override only for a private orchestrator deployment.

orchestrator.heartbeat_interval_s

Seconds between heartbeat polls. Default 60. Lower gives the dashboard faster feedback at the cost of more HTTP calls. Values under 10 are rejected.

restore_scratch

Where the ephemeral restored database lives during a restore test. Two kinds, mutually exclusive.

restore_scratch.kind: auto (default)

The agent detects the container runtime at use time: Podman if installed, otherwise Docker. Pin explicitly with kind: podman or kind: docker if you run both and want one. Runs the scratch DB as a short-lived container; destroyed at session end. With Podman (rootless) no extra group membership is needed; with Docker the restorable user is added to the docker group by the installer.

Optional image: field overrides the default image. By default the agent picks the image matching your source's major version (e.g. postgres:16 for PG 16.x, mongo:7 for Mongo). Override to pin extensions or collations; leave empty to let the agent track your source.

restore_scratch.kind: uri

The agent restores into an external reachable database endpoint you control. The agent drops and recreates a scratch database on that endpoint between sessions. Use this when the host cannot run any container runtime, or when you want to keep the scratch in a separate managed DB outside the agent host.

restore_scratch:
  kind: uri
  uri: env:RESTORABLE_SCRATCH_URI

The endpoint needs a role that can create and drop databases. Nothing persists between sessions.

sources

An array. Each entry defines one database to back up.

sources[].slug

Human handle for this source, local to this agent. URL-safe (lowercase letters, digits, hyphens). Required and unique. Changing a source slug is treated as delete-and-recreate; historical receipts for the old slug stay verifiable but the source under the new slug starts with no schedule.

In managed mode the slug maps to an orchestrator-minted opaque source_id at runtime via POST /v1/sources/resolve. That opaque id is what appears on the wire; the slug stays in your config. In standalone mode the slug is also what appears in the receipt's source_id field.

sources[].kind

postgres or mongodb. Determines the dump tool and the check payload shape.

sources[].connection.uri

The database connection string. Always a secret reference: env:NAME or file:path. Relative file: paths resolve from the install root.

For Postgres: a standard postgres:// URI. sslmode=require is strongly recommended.

For MongoDB: a mongodb:// or mongodb+srv:// URI. Atlas and self-hosted clusters both work.

sources[].checks

Array of check ids that run against this source's restores. Each id must match an entry in the top-level checks: list. Order is significant; checks run sequentially against one shared scratch database.

sources[] schedule and retention (standalone mode only)

These fields are relevant only in standalone mode. The agent never runs an internal scheduler based on them; they document the intended cadence for your crontab or systemd timer.

  • backup_schedule: cron expression documenting when your system timer invokes restorable backup --source <slug>.
  • restore_schedule: cron expression documenting when your system timer invokes restorable restore-test --source <slug>.
  • retention_days: how long to keep backups before restorable prune deletes them. Minimum 7 days.

In managed mode, schedules and retention are set in the dashboard. The orchestrator is the sole authority; omit these fields from config.yaml.

checks

An array. Each entry defines one verification that a source can reference by id. See Writing a check for how the contents are evaluated. The fields:

checks[].id

Stable identifier. Appears in every receipt that runs this check.

checks[].expect

The assertion expression. A small comparison DSL over the identifiers returned by the summary query.

checks[].summary_sql

Postgres check body. One query returning exactly one row. Column names become the identifiers expect can reference. The agent hashes the canonical SQL text and stores the hash in the receipt; the text itself stays on your host.

checks[].summary_find

Mongo check body. Mutually exclusive with summary_sql. One of three shapes: a filter (implicit count), a full aggregate pipeline, or a list-collections probe that counts user collections in the database (the mongo equivalent of postgres's schema-count check).

summary_find:
  database: app
  collection: orders
  filter: { "status": "open" }     # produces { n: <count> }
  # --- or ---
  aggregate:
    - { $match: { status: "open" } }
    - { $group: { _id: null, n: { $sum: 1 } } }
  # --- or ---
  list_collections: true        # produces { n: <user-collection count> }

With list_collections: true the agent runs db.getCollectionNames() against database, drops names that start with system., and returns the count as { n }. collection, filter, and aggregate must be unset in this mode; database is required.

Secret references

Fields marked secret accept three forms. Resolution happens at use time, so rotated secrets take effect on the next operation.

  • a-literal-string: taken as-is. Avoid; secrets do not belong in the YAML directly.
  • env:VAR_NAME: read from the environment variable. The systemd unit loads /var/lib/restorable/env into the process environment at start time.
  • file:path: read from a file, whitespace-trimmed. Relative paths resolve from the install root. Absolute paths work as written.

storage

Local directory layout for the three things the agent writes to disk during normal operation. Each field defaults to a subdirectory of the runtime install root (/var/lib/restorable/ in systemd installs) and is overridable independently. Missing fields fall back to the default.

storage.blobs_dir

Default: <root>/blobs. Staged ciphertext, one file per backup. Sized proportionally to the compressed dump. In managed mode the file is deleted after upload succeeds, so the peak usage is one concurrent dump.

If /var/lib is on a small boot volume and your dumps are large, point this at a bigger disk and add that path to the systemd unit's ReadWritePaths. See Moving blobs to a larger disk for the full recipe (the systemd sandbox blocks writes to any path not listed).

storage.receipts_dir

Default: <root>/receipts. DSSE receipts (.intoto.json). One per restore test. Kilobytes each. Retained locally as an auditor-cross-checkable record alongside the orchestrator's copy.

storage.events_dir

Default: <root>/events. Append-only local event log. Kilobytes. The agent emits events for backup / restore lifecycle and ships them as part of the heartbeat.

storage.s3 (standalone mode only)

Direct S3 uploads for the standalone deployment. Managed mode ignores this block: ciphertext always uploads through the orchestrator's pre-signed URLs so the agent never needs object storage credentials.

storage:
  s3:
    endpoint: s3.fr-par.scw.cloud
    region: fr-par
    bucket: my-restorable-backups
    access_key: env:RESTORABLE_S3_ACCESS_KEY
    secret_key: env:RESTORABLE_S3_SECRET_KEY

Systemd sandbox and custom paths

The vendored systemd unit runs with ProtectSystem=strict and ReadWritePaths=/var/lib/restorable. Any custom path you supply to storage.blobs_dir, storage.receipts_dir, storage.events_dir, or a restore_scratch of kind uri with local-file credentials must be covered by ReadWritePaths or the agent will fail with a read-only filesystem error.

The pattern is a drop-in override rather than an edit to the vendored unit:

sudo mkdir -p /etc/systemd/system/restorable.service.d
sudo tee /etc/systemd/system/restorable.service.d/storage.conf >/dev/null <<'EOF'
[Service]
ReadWritePaths=/mnt/big-disk/restorable
EOF
sudo systemctl daemon-reload
sudo systemctl restart restorable

One entry covers every subpath beneath it. Verify with systemctl cat restorable.

Reloading after an edit

Changes to config.yaml require reloading the agent:

sudo systemctl reload restorable.service

Reload re-reads the config. Restart is not required unless you changed something the agent caches at process start (very rare).

Validation errors

The agent's Validate() pass produces one-line errors like:

source "prod-db": connection.uri is required
check "healthy": cannot mix summary_sql (postgres) and summary_find (mongodb)
duplicate source slug "prod-db"

Every validation error is a refusal to start. Fix the YAML; the agent does not run with a half-valid config.