Read this before you deploy to the target host
I did not use the source host's GELI settings, and you must verify target.geli.localKey by hand.

localKey is read only by the target. The source host has a value but never uses it, so it is unverified and could be stale. The target host's copy was redacted, so I couldn't compare them. If it's wrong, the air-gapped target host can't rebuild its GELI key, can't import its zpool — on a host you're about to lose access to. On the target host, before deploying:


grep localKey /usr/local/opt/zfs_utils/sneakernet/sneakernet.conf.yaml
If it differs from what's in the master, keep the target host's value.

For secureKey.label/keyfile I took the target host's (secureKey, part1.key) over the source host's (sneakernet, keys/part1.key) — the target host actually mounts that disk and has been importing successfully, so its values are proven. Also worth noting the source host's sneakernet label is identical to transport.label, which would make sneakernet treat the key disk and transport drive as the same device.

By contrast transport.encryptionKey from the source host is safe to trust: the source encrypts with it and the target host has been receiving fine, so they necessarily already match.

Deliberate choices worth knowing
dryrun: '0' — the source host's copy still had '1' from the diagnostic runs.
verifyStream: full, not the datastructure's header. verifyModeFromConfig returns VERIFY_FULL when the key is absent, so the source host has been doing full verification all along; writing header would silently weaken it.
verbosity: '2' (the target host's, not the source host's 3). At verbosity 3, the target's decryptAndMountGeli dumps its whole geli config — including localKey — into the log, which reaches the report drive. Raise to 3 after Part 1 moves that guard to level 6. Nothing diagnostic is lost at 2; the base-selection warnings log unconditionally.
displayLogsOnTTY: '' — the target host had ttyv0, a relative path. logMsg does open(FH, '>', $value), so that writes a file named ttyv0 in cron's working directory rather than the console. It should be /dev/ttyv0 if you want it.
No angle brackets anywhere, including comments — loadConfig treats a bracketed token as an uninterpolated marker and would rewrite the file, stripping all the comments.
A finding that fell out of this
I dropped the target host's cleanSnaps/runSmart schedule entries in favour of the source host's MCcleanSnaps/MCrunSmart, since the source owns that key. But the schedule is a whitelist keyed on filename, and the repo's script is named cleanSnaps — no MC-prefixed variants exist there. That made me check the target's snapshot inventory:

class	count on the target host
hourly --1d	3261
daily --7d	802 (oldest 2026-01-20)
weekly --4w	186
Those --1d and --7d snapshots expired months ago. Snapshot cleanup is not effectively running on the target host. Either the source host's live directory has no file named MCcleanSnaps (so the whitelist matches nothing and it never ships), or it ships but lacks the -f flag cleanSnaps needs to actually delete. Worth checking:


ls /usr/local/opt/zfs_utils/sneakernet/cleanupScripts/
This also explains the 4986-line target inventory versus 1076 on the source, and why the target host still held those June dailies.

I will look at this (the cleanSnaps). I believe the client asked to increase the ttl on the air gap to include more snapshots, so the air-gapped target host should have more snapshots; they should roll off at a much greater age. I have copied your comments and will save them for later review.
That reframes it, and it's probably the right explanation — a deliberately longer TTL on the air gap would produce exactly this inventory. Two things follow.

The filename question is still worth checking, because an intentional long TTL and a whitelist miss look identical from the inventory alone. If MCcleanSnaps has no matching file in the source host's cleanupScripts/, nothing ships regardless of what the client asked for — you'd get long retention by accident rather than by policy, with no upper bound. ls on that directory distinguishes the two in one step.

I need to correct something I told you earlier. In the deployment plan I characterised the rollback casualties as benign:

All are --7d dailies that expired by their own retention around 2026-06-08…06-12 — seven weeks ago — and are already gone from the source, so they are not re-sent. Benign, but real.

If the target host intentionally retains longer, that's wrong. Those 4 nextcloud and 5 files_share dailies aren't expired junk — they're inside the target's intended retention window, and zfs receive -F will destroy them when the incremental from monthly_2026-06-01 lands. Still unavoidable if you want the incremental, but it's archive data the client is paying to keep, not garbage.

The broader implication is worth weighing in your October review. The base-selection fallback I built makes this a recurring cost, not a one-off: whenever it reaches back to an older base, zfs receive -F rolls the target to that point and destroys every target-only snapshot newer than it — which under a longer-target-TTL policy is precisely the snapshots the air gap exists to hold. The longer the air-gapped target host's retention, the more the fallback destroys each time it fires.

That's an argument for making the long-TTL anchor work well enough that the fallback rarely fires, and possibly for fullSendPolicy-style visibility on how far back a base was reached. Not something to act on now — but it's a real interaction between the fix and the retention policy that I hadn't accounted for, and it belongs in the notes you're saving.