JSON summaries¶
Every worker writes a structured per-run summary under /var/log after
it finishes. They are intended for external monitoring without requiring
a push gateway: scrape the file or have your webhook endpoint receive
the same document.
Files are written via *.tmp + mv so a node-exporter / log forwarder
scrape never sees a partial document.
Read-only diagnostics commands may also emit one-shot JSON on stdout.
Those documents are not written back to /var/log: /bin/status --json
uses schema restic-backup-helper.status/1, /bin/doctor --json uses
restic-backup-helper.doctor/1, and config-check --json uses
restic-backup-helper.config-check/1.
Common fields (every worker)¶
| Field | Type | Description |
|---|---|---|
job |
string | One of backup, check, prune, forget, replicate, restore, snapshot-export, forget-preview, mount-snapshot, unlock, sources-report, init-repo, notify-test, restore-test. |
hostname |
string | Container hostname. Set explicitly in Compose / Kubernetes for stable labels. |
release |
string | ${VERSION}-${restic_base} baked at build time, e.g. 2.14.1-0.18.1. |
started_at |
string | ISO 8601 in container TZ. |
finished_at |
string | ISO 8601 in container TZ. |
started_epoch |
integer | Unix epoch seconds at start. |
finished_epoch |
integer | Unix epoch seconds at finish. |
duration_seconds |
integer | Wall-clock duration. |
exit_code |
integer | Worker exit code. 0 = success. |
repository |
string | Masked Restic repository URL (mask_repository). |
Per-worker schemas¶
last-backup.json¶
/bin/backup produces, in addition to the common fields:
| Field | Type | Description |
|---|---|---|
backup_root_dir |
string | Value of BACKUP_ROOT_DIR at runtime. |
restic_tag |
string | The tag passed to restic backup. |
forget_exit_code |
integer | omitted | Exit code of the post-backup restic forget when RESTIC_FORGET_ARGS is set. 0 = success, 11 = skipped because another host held the exclusive lock (multi-host race, harmless), other values = forget failure. Omitted when no forget was attempted. See Backup worker → Multi-host repositories and exit 11. |
snapshot_id |
string | omitted | Short snapshot ID when restic produced one. |
files_new |
integer | omitted | Files added in this snapshot. |
files_changed |
integer | omitted | Files changed since the previous snapshot. |
files_unmodified |
integer | omitted | Files that were unchanged. |
dirs_new / dirs_changed / dirs_unmodified |
integer | omitted | Same for directories. |
bytes_added |
string | omitted | Bytes added, human-formatted (1.234 MiB). |
bytes_stored |
string | omitted | Bytes stored in this snapshot, human-formatted. |
Numeric extras only appear when restic actually printed them. Failed
backups before restic emitted its summary line will have only the
common fields and exit_code.
{
"job": "backup",
"hostname": "backup-node",
"release": "2.14.1-0.18.1",
"started_at": "2026-05-11T02:00:00+0200",
"finished_at": "2026-05-11T02:05:12+0200",
"started_epoch": 1762828800,
"finished_epoch": 1762829112,
"duration_seconds": 312,
"exit_code": 0,
"repository": "s3:https://s3.example.com/***@bucket/restic",
"backup_root_dir": "/data",
"restic_tag": "backup-node-data",
"forget_exit_code": 0,
"snapshot_id": "a1b2c3d4",
"files_new": 12,
"files_changed": 4,
"files_unmodified": 21034,
"bytes_added": "1.234 MiB"
}
last-check.json¶
Common fields only. restic check does not emit headline metrics worth
extracting; repository is the helpful one.
last-prune.json¶
Common fields only.
last-forget.json¶
/bin/forget (the standalone retention worker, activated by
FORGET_CRON) emits the common fields plus the masked repository
URL. The cumulative-and-idempotent nature of restic forget means
the JSON does not duplicate the inline path's forget_exit_code
field — here the top-level exit_code is the forget result:
0= success2=RESTIC_FORGET_ARGSempty (misconfiguration; nothing to forget — set a policy or unsetFORGET_CRON)11= skipped because another host held the exclusive lock (multi-host race, harmless; retention catches up on the next tick)- other = restic failure (see
forget-error-last.log).
Exit 11 is also auto-promoted to a restic_forget_last_exit_code
gauge in restic_forget.prom — monitoring can alert on persistent
11 without false-flagging the dedicated worker run.
See Forget worker for the full state machine.
last-forget-preview.json¶
/bin/forget-preview produces, in addition to the common fields:
| Field | Type | Description |
|---|---|---|
repo_wide |
string | ON when --repo-wide was used, otherwise OFF. |
policy_args |
string | Retention policy used (RESTIC_FORGET_ARGS or --policy). |
extra_args |
string | Extra restic forget args appended via --extra. |
host_filter |
string | omitted | Host filter used for the preview; omitted when repo_wide=ON. |
tag_filter |
string | omitted | Tag filter used for the preview; omitted when repo_wide=ON. |
It is always a dry-run wrapper; a successful preview does not delete snapshots.
last-replicate.json¶
| Field | Type | Description |
|---|---|---|
replicate_jobs_processed |
integer | Number of job rows parsed and dispatched. |
replicate_jobs_failed |
integer | Number that failed (counting recoveries). |
{
"job": "replicate",
"hostname": "backup-node",
"release": "2.14.1-0.18.1",
"started_at": "2026-05-11T09:00:00+0200",
"finished_at": "2026-05-11T09:11:23+0200",
"duration_seconds": 683,
"exit_code": 0,
"replicate_jobs_processed": 3,
"replicate_jobs_failed": 0
}
last-restore.json¶
| Field | Type | Description |
|---|---|---|
snapshot |
string | Snapshot ID restored. |
target |
string | Restore target path. |
dry_run |
boolean | true when --dry-run was passed. |
cancelled |
boolean | true when the operator typed q/quit or hit Ctrl+C. |
include_zero_match |
boolean | true when --include matched 0 files/dirs. |
files_restored |
integer | omitted | From restic's Summary: line. |
bytes_restored |
string | omitted | Human-formatted bytes restored. |
elapsed_human |
string | omitted | Human-formatted restore duration (from restic, may differ slightly from duration_seconds). |
Exit codes:
0on success.3wheninclude_zero_match=true.130whencancelled=true.
last-snapshot-export.json¶
| Field | Type | Description |
|---|---|---|
snapshot |
string | Snapshot ID exported. |
archive |
string | Final archive path (or null when --dry-run). |
work_dir |
string | Temporary work directory used (auto-generated or operator-supplied). |
dry_run |
boolean | true when --dry-run was passed. |
include_zero_match |
boolean | true when --include matched 0 files/dirs. |
archive_size_bytes |
integer | omitted | Size of the archive on disk; only when not --dry-run. |
files_restored / bytes_restored / elapsed_human |
– | Same as restore. |
{
"job": "snapshot-export",
"hostname": "backup-node",
"release": "2.14.1-0.18.1",
"started_at": "2026-05-11T15:30:00+0200",
"finished_at": "2026-05-11T15:31:12+0200",
"duration_seconds": 72,
"exit_code": 0,
"repository": "rclone:jottacloud:backups",
"snapshot": "5a3f2c8b",
"archive": "/restore/snapshot-export-5a3f2c8b-20260511-153000.tar.gz",
"archive_size_bytes": 595132416,
"work_dir": "/tmp/snapshot-export.pFKmAM",
"dry_run": false,
"include_zero_match": false,
"files_restored": 4523,
"bytes_restored": "567.89 MiB",
"elapsed_human": "1m12s"
}
last-mount-snapshot.json¶
/bin/mount-snapshot records one entry per mount session, written
after restic releases the FUSE mount:
| Field | Type | Description |
|---|---|---|
target |
string | Mountpoint used (default /restore). |
repo_wide |
string | ON when --repo-wide was used, otherwise OFF. |
allow_other |
string | ON when --allow-other was used, otherwise OFF. |
host_filter |
string | omitted | Host filter used; omitted when repo_wide=ON. |
tag_filter |
string | omitted | Tag filter used; omitted when repo_wide=ON. |
path_filters |
string | omitted | Space-separated list of --path values, when any were passed. |
duration_seconds measures the length of the mount session itself
(from "restic mount started" to "FUSE unmounted"); for ad-hoc operator
browsing this can be minutes-to-hours.
{
"job": "mount-snapshot",
"hostname": "backup-node",
"release": "2.14.1-0.18.1",
"started_at": "2026-05-12T17:00:00+0200",
"finished_at": "2026-05-12T17:12:31+0200",
"duration_seconds": 751,
"exit_code": 0,
"repository": "rclone:jottacloud:backups",
"target": "/restore",
"repo_wide": "OFF",
"allow_other": "OFF",
"host_filter": "backup-node",
"tag_filter": "backup-node-data"
}
last-unlock.json¶
/bin/unlock (the operator-driven manual restic unlock wrapper)
emits the common fields plus:
| Field | Type | Description |
|---|---|---|
repository |
string | Masked repository URL. |
remove_all |
string | ON when --remove-all was used, otherwise OFF. |
dry_run |
string | ON when --dry-run was used, otherwise OFF. |
locks_before |
string | Lock count from restic list locks before the unlock call. "unknown" when the listing itself failed. |
locks_after |
string | Lock count after the unlock call. Equals locks_before when dry_run=ON. |
{
"job": "unlock",
"hostname": "backup-node",
"release": "2.14.1-0.18.1",
"started_at": "2026-05-13T13:25:00+0200",
"finished_at": "2026-05-13T13:25:01+0200",
"duration_seconds": 1,
"exit_code": 0,
"repository": "rclone:jottacloud:backups",
"remove_all": "OFF",
"dry_run": "OFF",
"locks_before": "1",
"locks_after": "0"
}
See Unlock for the full flag reference and
the safety story around RESTIC_AUTO_UNLOCK=OFF.
last-sources-report.json¶
/bin/sources-report (the operator-driven pre-flight inventory) emits
the common fields plus a flat aggregate and three nested arrays
with per-source / per-files-from / per-exclude-file detail:
| Field | Type | Description |
|---|---|---|
backup_root_dir |
string | Value of BACKUP_ROOT_DIR at report time. |
sources_count |
integer | Number of unique source paths inspected. |
files_from_count |
integer | Number of --files-from files inspected. |
exclude_files_count |
integer | Number of --exclude-file files inspected. |
total_files |
integer | Sum of find -type f counts across sized sources. 0 when --no-size was set. |
total_bytes |
integer | Sum of du -sk * 1024 across sized sources. 0 when --no-size was set. |
errors_count |
integer | Unreadable / missing entries (sources, --files-from files themselves, entries inside --files-from, --exclude-file files). |
no_size |
string | ON / OFF mirroring --no-size. |
depth_limit |
string | --depth N value, or unlimited. |
sources |
array of {path, readable, type, files, bytes} |
Per-source detail. files / bytes are -1 when the source was skipped via --no-size. |
files_from |
array of {path, readable, lines, missing_entries} |
Per-file detail. |
exclude_files |
array of {path, readable, patterns} |
Per-file detail. |
{
"job": "sources-report",
"hostname": "backup-node",
"release": "2.14.1-0.18.1",
"started_at": "2026-05-13T15:30:00+0200",
"finished_at": "2026-05-13T15:30:08+0200",
"duration_seconds": 8,
"exit_code": 0,
"backup_root_dir": "/data",
"sources_count": 1,
"files_from_count": 1,
"exclude_files_count": 1,
"total_files": 18247,
"total_bytes": 9876543210,
"errors_count": 0,
"no_size": "OFF",
"depth_limit": "unlimited",
"sources": [
{"path": "/data", "readable": true, "type": "directory", "files": 18247, "bytes": 9876543210}
],
"files_from": [
{"path": "/config/files-from.txt", "readable": true, "lines": 4, "missing_entries": 0}
],
"exclude_files": [
{"path": "/config/excludes.txt", "readable": true, "patterns": 12}
]
}
See Sources report for the full flag reference and estimate semantics (size is unfiltered; exclude-file inventory is reported separately).
last-init-repo.json¶
/bin/init-repo (the audited restic init wrapper) emits the
common fields plus:
| Field | Type | Description |
|---|---|---|
repository |
string | Masked repository URL. |
dry_run |
string | ON when --dry-run was used, otherwise OFF. |
assume_yes |
string | ON when --yes / -y was used, otherwise OFF. |
confirmed |
string | ON when the operator typed init at the prompt OR when --yes was used; OFF when the prompt was declined / not reached. |
repo_existed |
string | "true" / "false" / "unknown" from the pre-init probe. |
probe_exit_code |
string | Raw exit code of restic cat config. -1 when env validation failed before the probe ran. |
init_args |
string | The combined RESTIC_INIT_ARGS + CLI passthrough flag list (space-joined). |
{
"job": "init-repo",
"hostname": "backup-node",
"release": "2.14.1-0.18.1",
"started_at": "2026-05-13T16:30:00+0200",
"finished_at": "2026-05-13T16:30:02+0200",
"duration_seconds": 2,
"exit_code": 0,
"repository": "rclone:jottacloud:backups",
"dry_run": "ON",
"assume_yes": "OFF",
"confirmed": "OFF",
"repo_existed": "false",
"probe_exit_code": "10",
"init_args": "--repository-version=2"
}
See Init repo for the full flag reference, the type-to-confirm prompt and the dry-run verdict matrix.
last-notify-test.json¶
/bin/notify-test (the operator-driven mail/webhook delivery test)
emits the common fields plus:
| Field | Type | Description |
|---|---|---|
target_mode |
string | auto, mail, webhook or all. |
dry_run |
string | ON / OFF. |
mail_requested |
string | ON when mail delivery was selected. |
webhook_requested |
string | ON when webhook delivery was selected. |
mail_configured |
string | ON when MAILX_RCPT was set. |
webhook_configured |
string | ON when WEBHOOK_URL was set. |
mail_result |
string | delivered, failed, dry-run or skipped. |
webhook_result |
string | delivered, failed, dry-run or skipped. |
mail_rc |
string | Raw return code from notify_mail. |
webhook_rc |
string | Raw return code from notify_webhook. |
webhook_url |
string | Masked webhook URL (scheme://host/...). |
webhook_auth_header_set |
string | ON when WEBHOOK_HEADER_AUTH was present. |
mail_on_error |
string | Original MAILX_ON_ERROR value observed at runtime. |
webhook_on_error |
string | Original WEBHOOK_ON_ERROR value observed at runtime. |
webhook_timeout |
string | Effective WEBHOOK_TIMEOUT value. |
subject |
string | Subject prefix / webhook detail. |
message |
string | Optional operator message. |
duration_so_far_seconds |
string | Runtime at the moment the JSON extras were rendered. |
See Notify test for target-selection rules and why delivery failures affect this helper's exit code.
last-restore-test.json¶
/bin/restore-test (the disaster-recovery rehearsal) emits the common
fields plus:
| Field | Type | Description |
|---|---|---|
repository |
string | Masked repository URL. |
snapshot |
string | Snapshot selector passed to restic (latest or short/long ID). |
target |
string | Effective restore target. |
target_autotmp |
string | ON when the target was an auto-mktemp tempdir, OFF for --target. |
keep |
string | ON / OFF. |
dry_run |
string | ON / OFF. |
verify |
string | ON when restic was invoked with --verify. |
min_files |
string | Effective --min-files floor. |
min_files_met |
string | "true" / "false". |
files_restored |
string | Restored regular files counted on disk. |
bytes_restored |
string | Restored bytes counted on disk. |
verification |
string | passed, failed, skipped. |
canary_total |
string | Total canary checks attempted. |
canary_passed / canary_failed |
string | Per-canary outcome counts. |
cleanup_status |
string | cleaned, kept, cleanup-failed, absent. |
include_paths_count |
string | Number of --path filters used (0 = full snapshot). |
tag_filter / host_filter |
string | Restic filters, when set. |
restic_files_restored / restic_bytes_restored / restic_elapsed_human |
string | Parsed from restic's Summary: line. |
include_zero_match |
string | "true" when --path matched zero files (exit 3). |
canary_results |
array | One object per canary with path, expected_sha256, actual_sha256, status (passed, mismatch, missing, hash-failed), message. |
See Restore test for the file-count
floor, canary contract and the cleanup_status matrix.
Reading the files¶
docker exec restic-backup-helper cat /var/log/last-backup.json
docker exec restic-backup-helper cat /var/log/last-backup.json | jq '.duration_seconds, .files_new, .bytes_added'
The file is also POSTed to WEBHOOK_URL when set — see Webhooks.
Command JSON¶
/bin/status --json is deliberately a stdout-only summary and does
not create /var/log/last-status.json. It reads the files documented
above, the rendered crontab (or env preview) and release metadata, then
emits schema restic-backup-helper.status/1:
| Field | Type | Description |
|---|---|---|
schema |
string | Constant restic-backup-helper.status/1. |
command |
string | Constant status. |
verdict |
string | OK, WARN, FAIL. |
warnings / failures / exit_code |
integer | Compact health outcome. |
runtime |
object | tz and masked repository. |
crontab |
object | source, path, line_count. |
schedules[] |
array | Enabled / disabled cron rows. |
jobs[] |
array | Core job health (backup, check, forget, prune, replicate). |
recent_json[] |
array | Known last-*.json files with presence, exit and age. |
findings[] |
array | {level, message} warnings / failures. |
See Status / health summary for full rules.
Stability promise¶
Field names and types are part of the public API surface; we treat changes the same way we treat env-var changes:
- Adding new fields is a MINOR bump.
- Renaming or removing a field is a MAJOR bump.
So a parser written today will still work for the entire 2.x lifecycle.