aboutsummaryrefslogtreecommitdiff
path: root/docs/design/2026-06-29-zfs-pre-snapshot-installer.org
blob: e5a339ef104c94c27af21f7074cad0893ad66d2e (plain)
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
#+TITLE: ZFS pre-pacman snapshot installer step (durable retention)
#+DATE: 2026-06-29
#+SOURCE: handoff from the home project, 2026-06-29

* Problem

A pacman =PreTransaction= hook snapshots =zroot/ROOT/default@pre-pacman_<ts>=
before every transaction, but nothing prunes them. Sanoid doesn't manage them
(they aren't =autosnap_= names), so they accumulated to 53 on velox between
April and the 2026-06-29 health check. Unbounded, they fill the pool over time.

* What's actually on velox vs. archsetup

The live =/usr/local/bin/zfs-pre-snapshot= is *not* authored by archsetup —
=git grep= for its content (=MIN_INTERVAL=, the pre-pacman =LOCKFILE= logic)
finds nothing tracked. The =PreTransaction= hooks in the archsetup monolith
(~lines 910, 1907, 1942) are the live-update guard, a different hook. The
script appears hand-placed on velox.

The 2026-01-17 security doc line "ZFS pre-pacman snapshots (already in
install-archzfs)" is therefore *out of date* — archsetup does not install this.
Incorporating the fix is a NET-NEW installer step, not a patch to an existing
one. Correct that stale doc line as part of the work.

velox was patched live (pruned to 10, script replaced with the self-pruning
version below); live backup at =/usr/local/bin/zfs-pre-snapshot.bak-2026-06-29=.

* Proposed installer step

In the archzfs / ZFS-on-root install path, gated to ZFS-root installs (velox is
the only ZFS daily driver; ratio is btrfs), install:

1. =/etc/pacman.d/hooks/zfs-snapshot.hook= — the =PreTransaction= hook that
   runs the script. *Not included in the handoff* — source it from velox
   (=/etc/pacman.d/hooks/zfs-snapshot.hook=) or write it.
2. =/usr/local/bin/zfs-pre-snapshot= — the =KEEP=10= self-pruning version
   below.

Tests live in archsetup, so this wants an archsetup session and a ZFS-root VM
test (=make test FS_PROFILE=zfs=), not a cross-project edit from home.

* The script (KEEP=10 self-pruning version)

#+begin_src bash
#!/bin/bash
POOL="zroot"
DATASET="$POOL/ROOT/default"
LOCKFILE="/tmp/.zfs-pre-snapshot.lock"
MIN_INTERVAL=60
KEEP=10   # how many pre-pacman snapshots to retain (rollback safety for recent transactions)

# Skip if a snapshot was created within the last 60 seconds
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"

    # Retention: keep only the most recent $KEEP pre-pacman snapshots, destroy older ones.
    # Sanoid does not manage these (they aren't autosnap_), so prune them here at creation time.
    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
#+end_src

* Implementation (2026-06-30)

- Hook sourced from velox (=/etc/pacman.d/hooks/zfs-snapshot.hook=) and embedded
  as a heredoc in =configure_pre_pacman_snapshots()=.
- Insertion point: a new =configure_pre_pacman_snapshots()= gated on
  =is_zfs_root=, called from =boot_ux= (the last step) so the hook doesn't fire
  during the install's own package operations — the first pre-pacman snapshot is
  the fresh system. The script ships as =scripts/zfs-pre-snapshot= (the
  =zfs-replicate= pattern), made =ZFS_PRE_*=-env-overridable for testability.
- Tests: =tests/zfs-pre-snapshot/= unit-tests the pruning logic against a fake
  =zfs= (creates, prunes oldest-past-KEEP, ignores non-=pre-pacman_= snapshots,
  honors the lockfile, warns on snapshot failure); =test_boot.py= asserts the
  hook + script land on a ZFS install; the orchestrator test pins the new
  =boot_ux= substep.

* Note on the "stale security doc"

The 2026-01-17 line "ZFS pre-pacman snapshots (already in install-archzfs)" is
*not* stale: that file is an archive generated by install-archzfs (see its
header and footer), and the claim is accurate for install-archzfs. The real gap
was that archsetup took sanoid from install-archzfs but never ported the
pre-pacman hook. This change ports it. The archive is left untouched.

* Remaining

- ZFS-root VM verification (=make test FS_PROFILE=zfs=) before the task closes.