diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-30 07:56:41 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-30 07:56:41 -0400 |
| commit | 6bd832897813c730deb12768d1eb5b02af66ad20 (patch) | |
| tree | fdb3b76316deb14c6a8dfd39e3b7d03e06283c32 /scripts/zfs-pre-snapshot | |
| parent | 394f3dbdadb29f7477d452634605f5c269aaed6f (diff) | |
| download | archsetup-6bd832897813c730deb12768d1eb5b02af66ad20.tar.gz archsetup-6bd832897813c730deb12768d1eb5b02af66ad20.zip | |
feat: install pre-pacman ZFS snapshot hook on ZFS-root systems
archsetup took sanoid from install-archzfs but never ported the pre-pacman snapshot hook, so a ZFS-root install had no transaction-triggered rollback point — the working setup only existed as a hand-placed script on velox, lost on reinstall. Add configure_pre_pacman_snapshots(): a PreTransaction pacman hook plus a self-pruning script that keeps the 10 most recent pre-pacman snapshots (sanoid ignores them — they aren't autosnap_ names). It's gated to ZFS-root and runs late in boot_ux, so the hook doesn't fire during the install's own package operations and the first snapshot is the fresh system.
The script ships as scripts/zfs-pre-snapshot, made ZFS_PRE_* env-overridable so the pruning logic is unit-testable. Unit tests drive it against a fake zfs (creates a snapshot, prunes the oldest past KEEP, ignores non-pre-pacman snapshots, honors the lockfile interval, warns on failure); a Testinfra test asserts the hook and script land on a ZFS install; the orchestrator test pins the new boot_ux substep.
Diffstat (limited to 'scripts/zfs-pre-snapshot')
| -rwxr-xr-x | scripts/zfs-pre-snapshot | 43 |
1 files changed, 43 insertions, 0 deletions
diff --git a/scripts/zfs-pre-snapshot b/scripts/zfs-pre-snapshot new file mode 100755 index 0000000..ed914d0 --- /dev/null +++ b/scripts/zfs-pre-snapshot @@ -0,0 +1,43 @@ +#!/bin/bash +# Snapshot the root dataset before a pacman transaction, then prune to the most +# recent $KEEP pre-pacman snapshots. Run from the zfs-snapshot.hook pacman hook +# (PreTransaction). Sanoid doesn't manage these (they aren't autosnap_ names), +# so retention is enforced here at creation time. +# +# Defaults match the live zroot layout; the ZFS_PRE_* env vars override them so +# the pruning logic is unit-testable against a fake zfs on PATH. + +POOL="${ZFS_PRE_POOL:-zroot}" +DATASET="${ZFS_PRE_DATASET:-$POOL/ROOT/default}" +LOCKFILE="${ZFS_PRE_LOCKFILE:-/tmp/.zfs-pre-snapshot.lock}" +MIN_INTERVAL="${ZFS_PRE_MIN_INTERVAL:-60}" +KEEP="${ZFS_PRE_KEEP:-10}" # pre-pacman snapshots to retain (recent-transaction rollback) + +# Skip if a snapshot was created within the last $MIN_INTERVAL seconds. A single +# pacman invocation can fire several transactions; this stops a burst of them +# from each cutting a near-identical snapshot. +if [ -f "$LOCKFILE" ]; then + last=$(stat -c %Y "$LOCKFILE" 2>/dev/null || echo 0) + now=$(date +%s) + if (( now - last < MIN_INTERVAL )); then + exit 0 + fi +fi + +TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S) +SNAPSHOT_NAME="pre-pacman_$TIMESTAMP" + +if zfs snapshot "$DATASET@$SNAPSHOT_NAME"; then + echo "Created snapshot: $DATASET@$SNAPSHOT_NAME" + touch "$LOCKFILE" + + # Keep only the most recent $KEEP pre-pacman snapshots; destroy older ones. + zfs list -H -o name -t snapshot -s creation "$DATASET" 2>/dev/null \ + | grep '@pre-pacman_' \ + | head -n -"$KEEP" \ + | while read -r old; do + zfs destroy "$old" && echo "Pruned old snapshot: $old" + done +else + echo "Warning: Failed to create snapshot" >&2 +fi |
