Configuration reference
zolder reads a single machine-local TOML file, zolder.toml. zolder init writes a working one for you; this page is the full reference for hand-editing it afterwards.
Every check section is optional and config-gated per machine — a machine runs only the checks it declares. The Mac and Windows peers drop [zfs]; a box outside the mesh drops [syncthing]. Unknown keys are rejected (the parser uses deny_unknown_fields), so a typo is an error rather than a silently ignored setting.
Complete example
The annotated zolder.example.toml ships with every release. It is embedded verbatim from that source file at build time, and CI parse-tests it against the config structs (example_config_parses in src/config.rs), so this reference cannot drift from what the binary actually accepts:
# zolder machine-local config. The easy path is `zolder init`, which discovers
# Syncthing and writes a working config into the platform config dir for you;
# this file is the full reference for hand-editing afterwards.
#
# Copy to zolder.toml (gitignored) per machine. Every check section is optional
# — a machine runs only what it declares: the Mac/Windows peers drop [zfs]; a
# box outside the mesh drops [syncthing].
# Point this into a synced folder and the mesh itself carries every device's
# status report to the node (fleet aggregation, post-MVP).
status_path = "zolder.json"
[syncthing]
url = "http://127.0.0.1:8384"
api_key_file = "/home/demian/.config/zolder/syncthing-api-key"
device_max_last_seen_hours = 24
[restic]
binary = "restic"
# What `zolder run backup` backs up — typically the synced tree on the node.
paths = ["/data/sync"]
exclude = ["**/node_modules", "**/target"]
[[restic.repo]]
name = "hetzner"
repository = "s3:https://fsn1.your-objectstorage.example/zolder-backup"
password_file = "/home/demian/.config/zolder/restic-password"
env_file = "/home/demian/.config/zolder/restic-hetzner.env"
max_snapshot_age_hours = 36
[[restic.repo]]
name = "scaleway"
repository = "s3:https://s3.nl-ams.scw.cloud/zolder-backup"
password_file = "/home/demian/.config/zolder/restic-password"
env_file = "/home/demian/.config/zolder/restic-scaleway.env"
max_snapshot_age_hours = 36
[canary]
dir = "/data/sync/.zolder/canary"
max_age_hours = 24
# Home node only — omit on Mac/Windows peers.
[zfs]
pools = ["tank"]
# Tailscale mesh transport — the layer Syncthing dials over. Omit on a machine
# that isn't on the tailnet.
[tailscale]
binary = "tailscale"
# Peers expected on the tailnet, by hostname or MagicDNS first label. A
# configured peer that isn't there fails the check.
peers = ["node"]
# An offline peer unseen for longer than this warns.
peer_max_last_seen_hours = 24
# A node key expiring within this many days warns — expiry stops sync silently.
key_expiry_warn_days = 7
# Tailnet Lock off means the control plane can introduce a peer: fail on it.
# Set false on a tailnet that doesn't use lock and it is never queried.
require_lock = true
[alert]
# Dead-man's switch: pinged on every completed run, `<url>/fail` when unhealthy.
# heartbeat_url = "https://hc-ping.com/<uuid>"
[alert.ntfy]
server = "https://ntfy.sh"
topic = "zolder-CHANGE-ME"
# Bearer token for a self-hosted / protected ntfy topic.
# token_file = "/home/demian/.config/zolder/ntfy-token"The sections below are the field-by-field reference — types, defaults, and semantics for every key above.
Top level
| Key | Type | Default | Notes |
|---|---|---|---|
status_path | path | required | Where zolder check writes its report. zolder init points this at zolder.json in the config dir. Point it into a synced folder and the mesh itself carries every device's status to the node (fleet aggregation, post-MVP). |
[syncthing] — mesh health (optional)
Read-only Syncthing REST health: per-folder sync state and backlog, plus whether every configured peer is connected or was seen recently enough.
url is Syncthing's local REST API (http://127.0.0.1:8384), never the sync listen address (tcp://<tailnet-ip>:22000) — for the transport itself, see Syncthing over Tailscale.
| Key | Type | Default | Notes |
|---|---|---|---|
url | string | required | REST API base, e.g. http://127.0.0.1:8384. |
api_key_file | path | required | File holding the API key — never the key itself in config. |
device_max_last_seen_hours | integer | 24 | Fail when a disconnected peer hasn't been seen for longer than this. |
A folder in error or with scan errors fails; a paused folder or a growing backlog warns; a peer unseen past the threshold fails.
[restic] — backup freshness (optional)
Read-only restic freshness per repo: is the newest snapshot young enough? A repo that can't be queried, one with no snapshots, or one whose newest snapshot is past its age threshold all fail.
| Key | Type | Default | Notes |
|---|---|---|---|
binary | string | "restic" | restic binary to invoke; found on PATH by default. |
paths | array of paths | [] | What zolder run backup backs up. The freshness check doesn't use this. |
exclude | array of strings | [] | Extra --exclude globs for the backup run. |
At least one repository must be declared as a [[restic.repo]] table:
| Key | Type | Default | Notes |
|---|---|---|---|
name | string | required | Label in reports, e.g. "hetzner". |
repository | string | required | restic repository URL, e.g. s3:https://…. |
password_file | path | required | restic password file. |
env_file | path | optional | Extra env (credentials) sourced at run time. KEY=VALUE per line; blank lines and # comments are ignored. |
max_snapshot_age_hours | integer | 36 | Fail when the newest snapshot is older than this. |
[canary] — sync liveness (optional)
Each peer writes its own <host>.canary marker into a shared synced folder on every check run and reads the others' freshness — catching "everything reports healthy but nothing actually moves". Failing to write our own marker fails the check (the synced tree isn't writable); a stale or absent peer marker warns.
| Key | Type | Default | Notes |
|---|---|---|---|
dir | path | required | Directory inside a synced folder. |
max_age_hours | integer | 24 | A peer marker older than this is stale. |
[zfs] — pool health (optional, node only)
Shells out to zpool list and reports; any pool not ONLINE fails. zolder never scrubs, imports, or resilvers. Omit this section on the Mac/Windows peers.
| Key | Type | Default | Notes |
|---|---|---|---|
pools | array of strings | required | Pools that must be ONLINE. |
[tailscale] — mesh transport (optional)
Read-only Tailscale health for the layer Syncthing dials over: shells out to tailscale status --json and tailscale lock status --json and reports. zolder never runs up, set, or lock sign. Omit this section on a machine that isn't on the tailnet.
| Key | Type | Default | Notes |
|---|---|---|---|
binary | string | "tailscale" | tailscale binary to invoke; found on PATH by default. |
peers | array of strings | required | Peers expected on the tailnet, matched by HostName or the first label of the MagicDNS name, case-insensitively. |
peer_max_last_seen_hours | integer | 24 | Warn when an offline peer hasn't been seen for longer than this. |
key_expiry_warn_days | integer | 7 | Warn when this machine's or a peer's node key expires within this many days. |
require_lock | boolean | true | Fail when Tailnet Lock is off. false means lock is never queried. |
A configured peer that is absent from the tailnet fails, as does Tailnet Lock being off while require_lock is set. A peer carrying traffic over a DERP relay instead of a direct path warns, as do an offline peer unseen past its threshold and a node key inside the expiry window. An idle peer has no path either way and never alerts; a key with expiry disabled is ok, not an error, and a timestamp zolder cannot read warns on that one line rather than failing the whole check. Reports carry names, ages and DERP region codes only — never an address or key material.
[alert] — notifications (optional)
See Alerting for the full semantics.
| Key | Type | Default | Notes |
|---|---|---|---|
heartbeat_url | string | optional | Dead-man's switch: pinged (GET) on every completed run, <url>/fail when unhealthy. |
[alert.ntfy] — push channel (optional)
| Key | Type | Default | Notes |
|---|---|---|---|
server | string | "https://ntfy.sh" | ntfy server base. |
topic | string | required | ntfy topic to publish alerts to. |
token_file | path | optional | Bearer token for a self-hosted / protected ntfy topic. |