aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/hypr-live-update-guard101
1 files changed, 85 insertions, 16 deletions
diff --git a/scripts/hypr-live-update-guard b/scripts/hypr-live-update-guard
index 4f561ae..e78200d 100755
--- a/scripts/hypr-live-update-guard
+++ b/scripts/hypr-live-update-guard
@@ -3,15 +3,28 @@
# hypr-live-update-guard - abort a live GPU/compositor library upgrade.
#
# Installed as a pacman PreTransaction hook. When an upgrade transaction
-# includes GPU/compositor runtime libraries (mesa, hyprland, wayland, GPU
-# drivers, ...) AND a Hyprland session is running, this aborts the
-# transaction BEFORE any package is swapped. Replacing those libraries out
-# from under a live compositor makes the next GPU-lib call hit a now
-# "(deleted)" file and SIGABRT, taking the Wayland clients down with it
-# (hit on ratio 2026-06-07: mesa + hyprland upgraded live, Hyprland crashed
-# and took awww/insync/emacs with it). Aborting at PreTransaction is the
-# safe point: nothing has been replaced yet, so the running session is
-# untouched and the user can re-run the upgrade from a TTY.
+# would CHANGE the on-disk version of GPU/compositor runtime libraries
+# (mesa, hyprland, wayland, GPU drivers, ...) AND a Hyprland session is
+# running, this aborts the transaction BEFORE any package is swapped.
+# Replacing those libraries out from under a live compositor makes the next
+# GPU-lib call hit a now "(deleted)" file and SIGABRT, taking the Wayland
+# clients down with it (hit on ratio 2026-06-07: mesa + hyprland upgraded
+# live, Hyprland crashed and took awww/insync/emacs with it). Aborting at
+# PreTransaction is the safe point: nothing has been replaced yet, so the
+# running session is untouched and the user can re-run from a TTY.
+#
+# Version-aware (2026-07-08): a same-version reinstall writes identical
+# bytes over identical bytes and is harmless to the live session — the
+# maintenance console's integrity REINSTALL is exactly that after an
+# update, and the name-only guard was blocking it. Each target's installed
+# version (pacman -Q) is compared against the sync-db candidate
+# (expac -S %v); targets that match are pure reinstalls and pass. Both are
+# read-only queries and run fine inside a hook — the ALPM lock only gates
+# other transactions (pinned live with db.lck present). A target whose
+# versions can't be resolved (AUR package, a -U transaction installing a
+# local file, expac absent) reads as unknown and blocks conservatively —
+# a -U of a guard-listed package at repo-matching version would slip
+# through, which is accepted as pathological.
#
# Pacman feeds the matched package names on stdin (NeedsTargets).
#
@@ -21,13 +34,23 @@
# HYPR_GUARD_SENTINEL path whose existence also proceeds anyway
# (default /run/archsetup-allow-live-gpu-update,
# cleared on reboot since /run is tmpfs)
+# HYPR_GUARD_VERSIONS "pkg installed candidate" lines replacing the
+# pacman/expac lookups; when set, a package absent
+# from the map reads as unknown (blocks)
set -u
sentinel="${HYPR_GUARD_SENTINEL:-/run/archsetup-allow-live-gpu-update}"
-# Explicit override: the user knows what they're doing.
-if [ "${HYPR_ALLOW_LIVE_UPDATE:-0}" = "1" ] || [ -e "$sentinel" ]; then
+# Explicit override: the user knows what they're doing. The sentinel is
+# consumed as it's honored — one touch buys exactly one transaction, so a
+# leftover from a crashed caller can't keep the guard disarmed until
+# reboot.
+if [ "${HYPR_ALLOW_LIVE_UPDATE:-0}" = "1" ]; then
+ exit 0
+fi
+if [ -e "$sentinel" ]; then
+ rm -f "$sentinel" 2>/dev/null || true
exit 0
fi
@@ -43,17 +66,63 @@ hyprland_running() {
# this is exactly the from-a-TTY-after-logout path the warning points to.
hyprland_running || exit 0
-# Collect the triggering packages (stdin from NeedsTargets) for the message.
-pkgs=$(cat 2>/dev/null | sort -u | tr '\n' ' ')
+# Collect the triggering packages (stdin from NeedsTargets).
+targets=$(cat 2>/dev/null | sort -u)
+
+# "installed candidate" for a package, or nothing when unknown.
+versions_for() {
+ if [ -n "${HYPR_GUARD_VERSIONS+x}" ]; then
+ printf '%s\n' "$HYPR_GUARD_VERSIONS" \
+ | awk -v p="$1" '$1 == p { print $2, $3; exit }'
+ else
+ inst=$(pacman -Q -- "$1" 2>/dev/null | awk '{ print $2 }')
+ # expac prints one line per repo carrying the package; the first is
+ # the repo pacman resolves from
+ cand=$(expac -S '%v' -- "$1" 2>/dev/null | head -n 1)
+ printf '%s %s\n' "$inst" "$cand"
+ fi
+}
+
+# Split the targets into version-changing (dangerous live) and same-version
+# reinstalls (harmless). Unknown versions count as dangerous.
+changed=""
+count=0
+for pkg in $targets; do
+ count=$((count + 1))
+ # shellcheck disable=SC2046 # splitting "inst cand" into $1 $2 is the point
+ set -- $(versions_for "$pkg")
+ inst=${1:-}
+ cand=${2:-}
+ if [ -n "$inst" ] && [ -n "$cand" ] && [ "$inst" = "$cand" ]; then
+ continue # pure reinstall: identical bytes, no swap hazard
+ fi
+ changed="$changed $pkg"
+done
+
+# Every guarded target is a pure reinstall -- nothing changes on disk that
+# the live session has mapped. Let it through silently.
+if [ "$count" -gt 0 ] && [ -z "$changed" ]; then
+ exit 0
+fi
+
+# An empty target list shouldn't normally happen (the hook only fires when
+# dangerous targets exist); if Hyprland is up, stay safe and abort.
+pkg_lines=""
+for pkg in $changed; do
+ pkg_lines="${pkg_lines} - ${pkg}
+"
+done
+[ -n "$pkg_lines" ] || pkg_lines=" (GPU/compositor runtime libraries)
+"
cat >&2 <<EOF
==========================================================================
BLOCKED: live GPU/compositor library upgrade while Hyprland is running
==========================================================================
- Packages in this upgrade can crash the running compositor if swapped now:
- ${pkgs:-(GPU/compositor runtime libraries)}
-
+ Packages in this upgrade change version and can crash the running
+ compositor if swapped now:
+${pkg_lines}
Replacing these out from under a live Hyprland session makes the next
GPU-lib call hit a deleted library and SIGABRT, taking your Wayland apps
down with it (and risking an unclean shutdown).