aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xarchsetup16
-rw-r--r--docs/design/2026-06-29-zfs-pre-snapshot-installer.org8
-rw-r--r--docs/design/system-monitor-design-ideas.org2
-rw-r--r--scripts/testing/tests/test_boot.py4
-rw-r--r--scripts/testing/tests/test_desktop.py2
-rw-r--r--tests/installer-steps/test_pacman_hook_order.py27
6 files changed, 49 insertions, 10 deletions
diff --git a/archsetup b/archsetup
index 74a477f..2a03ae3 100755
--- a/archsetup
+++ b/archsetup
@@ -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()