Automation & JSON output
Greylag Goose is deliberately a one-shot CLI: it composes with the scheduler
you already run instead of shipping its own daemon. Cron, a systemd timer, or a
CI job plus the exit codes and --json cover the "audit me every night" case.
Exit codes
|
all green/yellow — nothing restore-threatening |
|
at least one RED finding, or a provider error (a host couldn’t be fully audited, so the result is partial — treat it as a failure, since unaudited is unprotected), or the report couldn’t be delivered to a configured destination |
|
config or preflight problem (bad config file, unreachable prerequisite) |
Machine-readable report: --json
restoreguard audit --json > report.json
Prints a single JSON document on stdout. The field names are a contract — changes are additive only, so scripts written today keep working:
{
"schemaVersion": 1,
"generatedAt": "2026-07-05T03:00:12+00:00",
"overall": "red",
"partial": false,
"target": "offsite-minio",
"hash": "9f2c1e…",
"counts": {
"services": 42,
"backupArtifacts": 118,
"storageTargets": 9,
"red": 1,
"yellow": 3
},
"findings": [
{
"ruleId": "db-backup/stale",
"severity": "red",
"service": "myapp-prod-postgres-1",
"host": "mydockerhost",
"evidence": "newest dump myapp-prod-postgres-1_20260630.sql.gz is 4.2 days old (limit 26h)",
"suggestedAction": "check the dump job's cron/logs on mydockerhost"
}
],
"suppressedFindings": [ /* same shape */ ],
"activeSuppressions": [ /* your suppressions.json entries */ ],
"providerErrors": []
}
-
schemaVersionis the version of this contract — currently1, and deliberately decoupled from RestoreGuard’s release version. It bumps only on a breaking shape change (a field renamed, removed, retyped, or given new meaning); additive growth leaves it unchanged. The canonical JSON Schema for each version ships in the repo atcontracts/restoreguard-report.v{N}.schema.json— validate against it, and on an unknown (newer)schemaVersiontreat the report as one you can’t yet parse rather than guessing its shape. -
overallis the worst severity present;partial: truemeans at least one provider failed andproviderErrorssays which. -
targetis the connection id (fromreporting.json) this copy was saved to — distinct per destination,nullon stdout.hashis the SHA-256 of the report core (all fields excepttargetandhash), so it is identical across every copy of one audit — a reader dedups byhashand reads when fromgeneratedAt(UTC). Both additive sinceschemaVersion1. -
suppressedFindingsandactiveSuppressionsare always included — the JSON shows what was silenced, same as the text report.
Report destinations: read the shared reporting.json, not stdout
Redirecting stdout works, but the audit already persists every report by
itself: to a folder (with a stable latest.json), an S3-compatible bucket,
and/or a MongoDB collection. Those destinations live in a standalone
reporting.json (the main config just points at it via reportingFile) — see
the reportingFile section. That file is
self-contained on purpose: a consumer reads the same reporting.json,
connects to the same destination, and pulls the reports RestoreGuard wrote —
which is exactly how HCC consumes them. Read the destination from that file
rather than hard-coding paths, so reconfiguring RestoreGuard never breaks the
consumer:
REPORTING=/etc/restoreguard/reporting.json
REPORT_DIR=$(jq -r '.folder.path // empty' "$REPORTING")
REPORT="${REPORT_DIR:-$HOME/.local/share/restoreguard/reports}/latest.json"
(For the S3 or MongoDB destinations, reporting.json likewise carries the
endpoint/bucket or connection string the consumer needs — the same fields
RestoreGuard wrote with.) latest.json is replaced atomically (temp file
rename), so a reader or file watcher never sees a half-written report. A
destination that can’t be written turns the run into exit 1 — delivery
failures are as loud as findings.
Nightly audit with cron
# /etc/cron.d/restoreguard — 07:00, after the night's backup jobs finished
0 7 * * * auditor cd /home/auditor/goose && \
/home/auditor/.local/bin/restoreguard audit --json > /dev/null \
|| echo "backup audit RED or failed - check the report folder" | mail -s "goose alarm" [email protected]
Because a partial audit exits 1 too, "the audit itself broke" and "a backup
is bad" both raise the alarm — there is no state where failure looks like
success.
The same pattern works with a systemd timer, Gotify/ntfy hooks, or any
monitoring system that can run a command and read an exit code; feed the
persisted report to jq for dashboards:
jq -r '.findings[] | "\(.severity)\t\(.ruleId)\t\(.service)"' "$REPORT"