diff options
| -rwxr-xr-x | archsetup | 16 | ||||
| -rw-r--r-- | docs/design/2026-06-29-zfs-pre-snapshot-installer.org | 8 | ||||
| -rw-r--r-- | docs/design/system-monitor-design-ideas.org | 2 | ||||
| -rw-r--r-- | scripts/testing/tests/test_boot.py | 4 | ||||
| -rw-r--r-- | scripts/testing/tests/test_desktop.py | 2 | ||||
| -rw-r--r-- | tests/installer-steps/test_pacman_hook_order.py | 27 |
6 files changed, 49 insertions, 10 deletions
@@ -1945,7 +1945,12 @@ configure_pre_pacman_snapshots() { action="installing pre-pacman snapshot hook" && display "task" "$action" mkdir -p /etc/pacman.d/hooks - cat << 'EOF' > /etc/pacman.d/hooks/zfs-snapshot.hook + # Remove the pre-ordering filename from earlier archsetup runs; leaving it + # would execute a second snapshot after mkinitcpio's removal hook. + rm -f /etc/pacman.d/hooks/zfs-snapshot.hook + # Pacman runs hooks lexically. Keep this before the distribution's + # 60-mkinitcpio-remove hook so the snapshot preserves a bootable /boot. + cat << 'EOF' > /etc/pacman.d/hooks/05-zfs-snapshot.hook [Trigger] Operation = Upgrade Operation = Install @@ -2104,7 +2109,12 @@ UDEVEOF action="installing the live-update guard pacman hook" && display "task" "$action" mkdir -p /etc/pacman.d/hooks - cat > /etc/pacman.d/hooks/hypr-live-update-guard.hook << 'HOOKEOF' + # Migrate installations made before the numeric ordering prefix. + rm -f /etc/pacman.d/hooks/hypr-live-update-guard.hook + # This AbortOnFail guard must run before 60-mkinitcpio-remove; otherwise a + # blocked transaction can remove the current initramfs without reaching the + # PostTransaction hook that rebuilds it. + cat > /etc/pacman.d/hooks/10-hypr-live-update-guard.hook << 'HOOKEOF' [Trigger] Operation = Upgrade Type = Package @@ -2131,7 +2141,7 @@ Exec = /usr/local/bin/hypr-live-update-guard AbortOnFail NeedsTargets HOOKEOF - chmod 644 /etc/pacman.d/hooks/hypr-live-update-guard.hook + chmod 644 /etc/pacman.d/hooks/10-hypr-live-update-guard.hook } ### Display Server (conditional) diff --git a/docs/design/2026-06-29-zfs-pre-snapshot-installer.org b/docs/design/2026-06-29-zfs-pre-snapshot-installer.org index e5a339e..1b9235e 100644 --- a/docs/design/2026-06-29-zfs-pre-snapshot-installer.org +++ b/docs/design/2026-06-29-zfs-pre-snapshot-installer.org @@ -30,9 +30,9 @@ version below); live backup at =/usr/local/bin/zfs-pre-snapshot.bak-2026-06-29=. 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 +1. =/etc/pacman.d/hooks/05-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. + (=/etc/pacman.d/hooks/05-zfs-snapshot.hook=) or write it. 2. =/usr/local/bin/zfs-pre-snapshot= — the =KEEP=10= self-pruning version below. @@ -80,8 +80,10 @@ fi * Implementation (2026-06-30) -- Hook sourced from velox (=/etc/pacman.d/hooks/zfs-snapshot.hook=) and embedded +- Hook sourced from velox (=/etc/pacman.d/hooks/05-zfs-snapshot.hook=) and embedded as a heredoc in =configure_pre_pacman_snapshots()=. +- The installer removes the legacy unprefixed hook filenames during migration; + leaving them would run duplicate hooks, including a too-late snapshot. - 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 diff --git a/docs/design/system-monitor-design-ideas.org b/docs/design/system-monitor-design-ideas.org index 26619a0..b536604 100644 --- a/docs/design/system-monitor-design-ideas.org +++ b/docs/design/system-monitor-design-ideas.org @@ -395,7 +395,7 @@ Representation: Tools: - ZFS: =zfs list -t snapshot=, =zfs-pre-snapshot=, - =/etc/pacman.d/hooks/zfs-snapshot.hook=. + =/etc/pacman.d/hooks/05-zfs-snapshot.hook=. - Btrfs: =snapper list=, =snap-pac=, =grub-btrfs-mkconfig=, =/.snapshots=. - Common: =pacman -Q= for snapshot packages. diff --git a/scripts/testing/tests/test_boot.py b/scripts/testing/tests/test_boot.py index e442682..66b78e4 100644 --- a/scripts/testing/tests/test_boot.py +++ b/scripts/testing/tests/test_boot.py @@ -76,8 +76,8 @@ def test_zfs_pre_pacman_snapshot_hook(host): script = host.file("/usr/local/bin/zfs-pre-snapshot") assert script.exists and script.is_file, "pre-pacman snapshot script missing" assert script.mode & 0o111, "pre-pacman snapshot script is not executable" - hook = host.file("/etc/pacman.d/hooks/zfs-snapshot.hook") - assert hook.exists and hook.is_file, "zfs-snapshot.hook missing" + hook = host.file("/etc/pacman.d/hooks/05-zfs-snapshot.hook") + assert hook.exists and hook.is_file, "05-zfs-snapshot.hook missing" assert "PreTransaction" in hook.content_string, "hook not PreTransaction" assert "/usr/local/bin/zfs-pre-snapshot" in hook.content_string, \ "hook does not exec the snapshot script" diff --git a/scripts/testing/tests/test_desktop.py b/scripts/testing/tests/test_desktop.py index 6f79bfd..468bc75 100644 --- a/scripts/testing/tests/test_desktop.py +++ b/scripts/testing/tests/test_desktop.py @@ -56,7 +56,7 @@ def test_live_update_guard_installed(host, hyprland_installed): guard = host.file("/usr/local/bin/hypr-live-update-guard") assert guard.exists, "live-update guard script missing" assert guard.mode & 0o111, "live-update guard not executable" - hook = host.file("/etc/pacman.d/hooks/hypr-live-update-guard.hook") + hook = host.file("/etc/pacman.d/hooks/10-hypr-live-update-guard.hook") assert hook.exists, "live-update guard pacman hook missing" assert "hypr-live-update-guard" in hook.content_string, \ "hook does not invoke the guard script" diff --git a/tests/installer-steps/test_pacman_hook_order.py b/tests/installer-steps/test_pacman_hook_order.py new file mode 100644 index 0000000..49acdbe --- /dev/null +++ b/tests/installer-steps/test_pacman_hook_order.py @@ -0,0 +1,27 @@ +"""Regression tests for the pacman hook ordering safety invariant.""" + +import os +import unittest + + +REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +ARCHSETUP = os.path.join(REPO_ROOT, "archsetup") + + +class PacmanHookOrderTests(unittest.TestCase): + def test_safety_hooks_precede_mkinitcpio_removal(self): + with open(ARCHSETUP, encoding="utf-8") as source: + text = source.read() + + self.assertIn( + "cat << 'EOF' > /etc/pacman.d/hooks/05-zfs-snapshot.hook", text) + self.assertIn( + "cat > /etc/pacman.d/hooks/10-hypr-live-update-guard.hook", text) + self.assertLess("05-zfs-snapshot.hook", "60-mkinitcpio-remove.hook") + self.assertLess("10-hypr-live-update-guard.hook", "60-mkinitcpio-remove.hook") + self.assertIn("rm -f /etc/pacman.d/hooks/zfs-snapshot.hook", text) + self.assertIn("rm -f /etc/pacman.d/hooks/hypr-live-update-guard.hook", text) + + +if __name__ == "__main__": + unittest.main() |
