From da0270e66e8397fc8acc08ecf3449351b43f9dda Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 20 Jul 2026 22:22:37 +0300 Subject: f3s: add zusb removable backup pool scripts (zusb-load/zusb-unload) 4-disk raidz2 USB-SATA pool used as offline backup storage, loaded ~once per quarter. Migrated from t450 to the f3s USB-key scheme: zusb/data/enc rekeyed from passphrase to a raw key on the F3S_KEYS sticks at /keys/zusb.key. Scripts + key deployed to all f-hosts (f0/f1/f2/f3) so the disk stack can be re-plugged to any host. Not auto-mounted. --- f3s/freebsd-hosts/zusb/README.md | 98 ++++++++++++++++++++++++++++++++++++++ f3s/freebsd-hosts/zusb/zusb-load | 42 ++++++++++++++++ f3s/freebsd-hosts/zusb/zusb-unload | 27 +++++++++++ 3 files changed, 167 insertions(+) create mode 100644 f3s/freebsd-hosts/zusb/README.md create mode 100644 f3s/freebsd-hosts/zusb/zusb-load create mode 100644 f3s/freebsd-hosts/zusb/zusb-unload diff --git a/f3s/freebsd-hosts/zusb/README.md b/f3s/freebsd-hosts/zusb/README.md new file mode 100644 index 0000000..60bd259 --- /dev/null +++ b/f3s/freebsd-hosts/zusb/README.md @@ -0,0 +1,98 @@ +# f3s zusb — Removable USB Backup Pool + +`zusb` is a **4-disk raidz2 ZFS pool on 1.8 TB USB-SATA disks** (ASMT ASM235CM +bridges) used as the **offline backup storage device**. It is plugged in and +loaded **roughly once per quarter** to back up data, then exported and +unplugged again. Because it is offline most of the time, it is **not** +auto-imported or auto-mounted at boot — it is loaded manually with +`zusb-load`. + +The pool is hosted on whichever f-host it is currently plugged into (f1 today). +The load/unload scripts and the encryption key are deployed to **all f-hosts** +(f0/f1/f2/f3), so the disk stack can be re-plugged to any host and loaded there +without any per-host setup. + +## Pool & Encryption + +- Pool: `zusb` (raidz2 over 4 × 1.8 TB USB disks, ~5.4 TB usable). +- Encryption root: `zusb/data/enc`, `keyformat=raw`, + `keylocation=file:///keys/zusb.key` — the raw 32-byte key lives on the + `F3S_KEYS` UFS USB stick at `/keys/zusb.key`, exactly like the other f-host + ZFS keys (see [`../keys/README.md`](../keys/README.md)). All child datasets + (`zusb/data/enc/{backups,books,documents,games,git,mail,music,pictures, + videos,yoga,…}`) inherit from this root. +- `zusb` is deliberately **not** listed in any host's `zfskeys_datasets`, so + boot-time key loading skips it and a missing/offline pool never blocks boot. + +`zusb/data/enc` was rekeyed from its original passphrase key (t450's +`zroot/secret/zroot.enc.key`) to the raw key on the stick when the pool was +migrated from t450 to the f3s hosts. The old passphrase key on t450 is now +obsolete for `zusb`. + +## Installed Files + +- `/usr/local/bin/zusb-load` — mounts the `F3S_KEYS` stick, imports `zusb`, + loads the encryption key for `zusb/data/enc` from `/keys/zusb.key`, and mounts + all datasets. +- `/usr/local/bin/zusb-unload` — snapshots `zusb` for safety (via + `/opt/snonux/bin/zfs/zfs.snapshot` when `/opt` is mounted, with a timestamped + fallback) and exports the pool so the disks can be unplugged. + +Both scripts are host-independent (they reference only `zusb`, `zusb/data/enc`, +and `/keys/zusb.key`) and are byte-identical across all f-hosts. + +## Host Configuration + +On each f-host install the scripts: + +```sh +doas install -o root -g wheel -m 0755 zusb-load /usr/local/bin/zusb-load +doas install -o root -g wheel -m 0755 zusb-unload /usr/local/bin/zusb-unload +``` + +Put the raw key on every host's `F3S_KEYS` stick (`/keys` is mounted read-only +at boot, so remount read-write to add it). The same 32-byte key file goes on +all four sticks: + +```sh +doas mount -u -o rw /keys +doas install -o root -g wheel -m 0400 zusb.key /keys/zusb.key +doas mount -u -o ro /keys +``` + +The raw key is **not** committed to this repo (it is secret key material). It +is copied host-to-host from a stick that already has it. + +## Usage + +```sh +doas /usr/local/bin/zusb-load # plug the disks in, then load + mount +doas zfs list -r zusb +# ... run the quarterly backup ... +doas /usr/local/bin/zusb-unload # snapshot + export, then unplug the disks +``` + +## Verification + +```sh +ls -l /usr/local/bin/zusb-load /usr/local/bin/zusb-unload +ls -l /keys/zusb.key +sha256 /usr/local/bin/zusb-load /usr/local/bin/zusb-unload # identical on all hosts +sha256 /keys/zusb.key # identical on all sticks +``` + +After `zusb-load`: + +```sh +zpool status zusb +zfs list -r zusb -o name,keystatus,mounted,mountpoint +``` + +## Origin + +Ported from `t450:/root/bin/zusb-load.csh` and `zusb-unload.csh`. The t450 +flow unlocked a passphrase-protected `zroot/secret` keystore and then read +`/zroot/secret/zroot.enc.key`; on the f-hosts the raw key lives directly on the +`F3S_KEYS` stick, matching the existing f-host ZFS key scheme. The t450 +unload's `zfs.snapshot zroot` step was dropped (f-host `zroot` is the host boot +pool, not part of this workflow). \ No newline at end of file diff --git a/f3s/freebsd-hosts/zusb/zusb-load b/f3s/freebsd-hosts/zusb/zusb-load new file mode 100644 index 0000000..6ef54f3 --- /dev/null +++ b/f3s/freebsd-hosts/zusb/zusb-load @@ -0,0 +1,42 @@ +#!/bin/sh +# zusb-load — import the zusb raidz2 pool (4 x 1.8T USB-SATA disks) and unlock +# zusb/data/enc using the raw key on the F3S_KEYS stick, then mount everything. +# +# Ported from t450:/root/bin/zusb-load.csh to f1's f3s USB-key scheme. The old +# t450 flow unlocked a passphrase-protected zroot/secret keystore and then read +# /zroot/secret/zroot.enc.key; on f1 the raw key lives directly on the F3S_KEYS +# stick at /keys/zusb.key, exactly like the other f-host secrets +# (see f3s-storage/references/usb-keys.md). zusb/data/enc was rekeyed from +# passphrase to a raw key to match that scheme. + +set -eu + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin + +POOL=zusb +ENCROOT=zusb/data/enc +KEYFILE=/keys/zusb.key + +# 1. Make sure the F3S_KEYS stick is mounted (provides $KEYFILE). +/usr/local/sbin/f3s-mount-keys --strict + +if [ ! -r "$KEYFILE" ]; then + echo "zusb-load: key file $KEYFILE missing or unreadable" >&2 + exit 1 +fi + +# 2. Import the pool if it is not already imported. +if ! zpool list "$POOL" >/dev/null 2>&1; then + zpool import "$POOL" +fi + +# 3. Load the encryption key for the encrypted root dataset from the stick. +if [ "$(zfs get -H -o value keystatus "$ENCROOT")" = "unavailable" ]; then + zfs load-key "$ENCROOT" +fi + +# 4. Mount everything. +zfs mount -a + +echo "zusb loaded:" +zfs list -r "$POOL" -o name,keystatus,mounted,mountpoint \ No newline at end of file diff --git a/f3s/freebsd-hosts/zusb/zusb-unload b/f3s/freebsd-hosts/zusb/zusb-unload new file mode 100644 index 0000000..d486d57 --- /dev/null +++ b/f3s/freebsd-hosts/zusb/zusb-unload @@ -0,0 +1,27 @@ +#!/bin/sh +# zusb-unload — snapshot zusb for safety, then export the pool. +# +# Ported from t450:/root/bin/zusb-unload.csh. The t450 version also snapshotted +# zroot (its boot pool); that step is dropped here because f1's zroot is the +# host boot pool and is not part of this workflow. The zfs.snapshot helper +# ships under /opt (mounted from zusb/data/opt) and is used when present, with +# a plain timestamped snapshot as a fallback. + +set -eu + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin + +POOL=zusb +SNAPBIN=/opt/snonux/bin/zfs/zfs.snapshot + +# 1. Snapshot zusb for safety before export. +if [ -x "$SNAPBIN" ]; then + "$SNAPBIN" "$POOL" +else + zfs snapshot -r "${POOL}@unload-$(date +%Y%m%d-%H%M%S)" +fi + +# 2. Export the pool. +zpool export "$POOL" + +echo "zusb exported." \ No newline at end of file -- cgit v1.2.3