Repository navigation
Expand file tree
/
Copy pathexample-config.toml
More file actions
184 lines (165 loc) · 8.06 KB
/
Copy pathexample-config.toml
File metadata and controls
184 lines (165 loc) · 8.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# arctern.toml — annotated example.
#
# This file shows the *idiomatic* form. Every section below has more
# verbose long-hand equivalents; you only need them when you want to
# override per-job. Snapshot tags use `<prefix><RFC3339-utc-no-colons>`
# (e.g. `zrepl_20260514T134500Z`) — wire-compatible with Go zrepl, so a
# host migrating from zrepl can keep its existing snapshot history.
# ─── Host-wide defaults ──────────────────────────────────────────────
#
# Applied to every job at config-load time. Per-job overrides win.
# Keeps the rest of the file short — operators see the *interesting*
# differences, not boilerplate.
state_dir = "/var/lib/arctern"
[defaults]
# Prefix used by snap jobs creating snapshots, push jobs filtering
# which snapshots to replicate, and the auto-generated prune rules.
# Pick one string and forget about it.
prefix = "zrepl_"
[defaults.snapshotting]
# Cadence for snap jobs that don't set their own interval.
interval = "15m"
[defaults.pruning]
# Grid used by snap jobs that don't set their own pruning.
# Format: <count>x<interval> | ... — buckets run from the newest snapshot
# leftwards into the past, so the first term is the finest-grained one.
# Each bucket keeps its OLDEST snapshot (zrepl semantics): the survivor
# ages into the next bucket instead of being displaced by a newer one.
# Because of that, the first bucket must not be longer than the snapshot
# interval unless it is `keep=all` — the loader rejects that shape.
grid = "4x15m | 24x1h | 14x1d"
# `protect_non_prefixed = true` (the default) auto-appends a
# `{ type: regex, regex: "^<prefix>.*", negate: true }` rule so
# snapshots WITHOUT the prefix survive prune. This is the standard
# zrepl idiom for protecting manual snapshots.
protect_non_prefixed = true
# ─── Outbound SSH peers ──────────────────────────────────────────────
#
# A peer is one PHYSICAL host, possibly reachable over several network
# routes (LAN, WireGuard, ...) listed highest-priority first. The link
# connects the first reachable route and switches back to a higher one
# when it returns. Cursor bookmarks, holds and sync history are keyed
# by the peer name — routes are pure transport.
#
# `auto = false` on a route means scheduled replication never runs
# while that route is the active one — it carries only manual
# "Send now" pushes (the metered-WireGuard case).
[[peers]]
name = "nas"
mode = "auto"
auto_interval = "1d" # don't auto-sync more often than daily
[[peers.routes]]
name = "lan"
ssh_target = "arctern-replicator@10.44.44.1"
[[peers.routes]]
name = "wg"
ssh_target = "arctern-replicator@10.66.66.2"
auto = false
# Single-route shorthand — equivalent to one route named "default":
[[peers]]
name = "nas-remote"
ssh_target = "arctern-replicator@203.0.113.7"
# ─── databak: snap job ───────────────────────────────────────────────
#
# Filesystems map: keys with trailing `/` mean "include this subtree"
# (recursive), bare keys mean exact-match. `false` excludes a path
# from any subtree include it sits under.
[[jobs]]
type = "snap"
name = "databak"
filesystems = { "okdata/data/nas" = true, "okdata/data/root" = true, "okdata/data/home" = true }
# snapshotting + pruning omitted — inherited from [defaults] above.
# ─── rootbak: snap with non-default interval ────────────────────────
[[jobs]]
type = "snap"
name = "rootbak"
filesystems = { "okdata/ROOT/default" = true }
[jobs.snapshotting]
interval = "1d"
[jobs.pruning]
keep = [
{ type = "grid", grid = "7x1d", regex = "^zrepl_.*" },
{ type = "regex", regex = "^zrepl_.*", negate = true },
]
# ─── push_to_nas: replicate to both hosts on their own schedules ────
#
# Every DUE target replicates each cycle: manual "Send now" requests
# always run; an auto peer runs when its active route allows auto and
# its `auto_interval` has elapsed since the last success. Failover
# between network paths to the SAME host is the peer's `routes` list,
# not a second target. Each peer keeps its own cursor bookmark, so an
# offline peer catches up cleanly when it's reachable again.
[[jobs]]
type = "push"
name = "push_to_nas"
targets = ["nas", "nas-remote"]
# Scheduling is event-driven: the job sleeps until the earliest auto
# target is due and wakes on Send now / peer connectivity changes.
# `interval` is optional and only bounds the blind sleep (default 15m).
# interval = "15m"
# Cap outgoing replication bandwidth (per second); shared across
# parallel sends. Unset = unthrottled.
# bandwidth_limit = "10MiB"
# Filesystems replicated concurrently per target peer (own recv channel
# each). 1..=4; default 1 (sequential).
# parallel = 2
# How much history reaches the receiver. "latest" (the default) sends
# only the newest snapshot per push: the smallest possible delta, one
# snapshot per push on the receiver — the laptop keeps the fine-grained
# history for quick rollback, the NAS is the disaster copy. "all" is
# zrepl's behaviour: the whole history is replicated (one `zfs send -I`
# per cycle, which carries every snapshot between the common base and
# the head, manual ones included), so the receiver mirrors the sender's
# history and its own prune job has something to thin; it costs the data
# written and deleted between snapshots, which on a home directory can be
# several times the single daily delta.
# replicate = "latest"
dry_run = true # first-run safety: plan and log actions without sending
filesystems = { "novafs/arch0/" = true, "novafs/arch0" = false, "novafs/arch0/data" = false }
[jobs.target]
root_fs = "okdata/backups/laptop"
# `send` flags default to encrypted+embedded+compressed+large_blocks =
# all true — what `zfs send -w -e -c -L` produces. Override one off with
# `[jobs.send] encrypted = false` if you want plaintext.
# ─── received_prune: receiver-side retention ─────────────────────────
#
# Prune-only job — never creates snapshots. Use this on the receiver
# (the NAS) to apply a longer-form retention to whatever the laptop
# pushes. zrepl's `push.pruning.keep_receiver` lives here in arctern.
[[jobs]]
type = "prune"
name = "received_prune"
interval = "1h"
filesystems = { "okdata/backups/laptop/" = true, "okdata/backups/laptop" = false }
[jobs.pruning]
keep = [
{ type = "grid", grid = "1x1h | 24x1h | 14x1d | 8x1w", regex = "^zrepl_.*" },
{ type = "regex", regex = "^zrepl_.*", negate = true },
]
# ─── allowed_clients: NAS-side ACL ──────────────────────────────────
#
# This block lives in the NAS's arctern.toml (despite appearing here
# for documentation). On the NAS, sshd's authorized_keys ForcedCommand
# is `arctern stdinserver-dispatch laptop_nova`; the dispatcher matches
# that argv against `identity` and consults this row before serving any
# control or recv channel.
[[allowed_clients]]
identity = "laptop_nova"
fingerprint = "SHA256:abc123..."
jobs = ["push_to_nas"]
operations = ["control", "control:discard_partial_recv", "recv"]
# Add "control:proxy_admin" to let the client's console MANAGE this
# host like its own (wakeup/pause jobs, scrub pools, create/destroy
# snapshots host-wide via the generic API proxy). Read-only proxying
# (dashboards, pools, ARC, events, config) rides the plain "control"
# scope. Grant proxy_admin only to a peer you fully trust — it is
# root-equivalent administration of this host.
root_fs = "okdata/backups/laptop"
# Per-client recv tuning. Maps zrepl's `recv.properties.inherit` and
# `recv.properties.override` 1:1, applied as `zfs recv -x <key>` (force
# inherit from parent) and `-o <k>=<v>` (force-set). The pattern below
# matches an "untrusted-receiver" sink that wants its children
# unmounted by default.
[allowed_clients.recv]
inherit_properties = ["mountpoint"]
override_properties = { canmount = "off", "org.openzfs.systemd:ignore" = "on" }