From 033b5ecd3de1a64f464c1fd3d9c716258e9451b6 Mon Sep 17 00:00:00 2001 From: Spencer Bull <7035687+spencerbull@users.noreply.github.com> Date: Tue, 6 Oct 2026 00:30:29 -0500 Subject: [PATCH] Retain XPS 13 Panther Lake firmware during package refresh Constrain Cirrus firmware in the package transaction so a stable channel refresh cannot remove the required aliases. Recover old firmware independently of completed migrations, and request reboot only after verifying a successful repair. Preserve package exclusions through native pacman handling while keeping interactive conflict recovery answerable. Co-Authored-By: GPT-6.1-Sol High Co-Authored-By: GPT-6.1-Sol XHigh --- bin/omarchy-update-pacman | 99 ++++++++- test/shell.d/fixtures/sudo-boundary-test.sh | 2 + test/shell.d/update-package-conflict-test.sh | 23 ++ test/shell.d/update-pacman-test.sh | 2 + .../xps13-ptl-firmware-retention-test.sh | 206 ++++++++++++++++++ 5 files changed, 331 insertions(+), 1 deletion(-) create mode 100755 test/shell.d/xps13-ptl-firmware-retention-test.sh diff --git a/bin/omarchy-update-pacman b/bin/omarchy-update-pacman index 66f66731..c9273f3c 100755 --- a/bin/omarchy-update-pacman +++ b/bin/omarchy-update-pacman @@ -5,6 +5,93 @@ # omarchy:hidden=true # omarchy:requires-sudo=true +pacman_args=("$@") +query_args=() +declare -A firmware_targets needed_options +sync=0 upgrades=0 ask_bits=0 repair_reboot=0 +noninteractive=0 +operand="" option="" +end_options=0 +separator_index="" +for index in "${!pacman_args[@]}"; do + arg=${pacman_args[index]} + if [[ -n $operand ]]; then + if [[ $operand == "query" ]]; then + query_args+=("$option" "$arg") + elif [[ $operand == "ask" ]]; then + [[ $arg =~ ^[0-9]{1,10}$ ]] || { echo "Invalid pacman question mask: $arg" >&2; exit 2; } + ask_bits=$((10#$arg)) + noninteractive=1 + fi + operand="" + continue + fi + if [[ $arg == "--" ]]; then + end_options=1 + separator_index=$index + continue + fi + if (( end_options )); then + [[ $arg != "linux-firmware-cirrus" && $arg != */linux-firmware-cirrus ]] || firmware_targets[$index]=1 + continue + fi + case "$arg" in + --sync) sync=1 ;; + --sysupgrade) ((upgrades += 1)) ;; + --noconfirm) noninteractive=1 ;; + --confirm) noninteractive=0 ;; + --ask) operand=ask ;; + --ask=*) + mask=${arg#*=} + [[ $mask =~ ^[0-9]{1,10}$ ]] || { echo "Invalid pacman question mask: $mask" >&2; exit 2; } + ask_bits=$((10#$mask)) + noninteractive=1 + ;; + --config|--dbpath|--root|--sysroot|-b|-r) operand=query; option=$arg ;; + --config=*|--dbpath=*|--root=*|--sysroot=*) query_args+=("$arg") ;; + --ignore|--ignoregroup|--cachedir|--hookdir|--gpgdir|--logfile|--overwrite) operand=skip ;; + -b?*|-r?*) query_args+=("$arg") ;; + --needed) needed_options[$index]=1 ;; + --*) ;; + -*) + [[ $arg != *S* ]] || sync=1 + upgrade_flags=${arg//[^u]/} + ((upgrades += ${#upgrade_flags})) + ;; + linux-firmware-cirrus|*/linux-firmware-cirrus) firmware_targets[$index]=1 ;; + esac +done + +if (( sync && upgrades )) && omarchy-hw-dell-xps13-dx13260-ptl; then + cirrus_package=$(pacman "${query_args[@]}" -Q linux-firmware-cirrus 2>/dev/null || true) + old_firmware=0 + if [[ -z $cirrus_package ]] || (( $(vercmp "${cirrus_package#* }" "20260810-3") < 0 )); then + old_firmware=1 + fi + if (( upgrades > 1 || old_firmware || ${#firmware_targets[@]} )); then + pacman_args=() + original_args=("$@") + policy_inserted=0 + for index in "${!original_args[@]}"; do + arg=${original_args[index]} + if (( noninteractive )) && [[ $index == "$separator_index" ]]; then + pacman_args+=(--ask "$((ask_bits | 1))") + policy_inserted=1 + fi + [[ ! -v firmware_targets[$index] ]] || continue + # --needed skips an installed shim before -Suu selects the older core copy. + (( upgrades < 2 )) || [[ ! -v needed_options[$index] ]] || continue + pacman_args+=("$arg") + done + # Keep exclusions through pacman's own IgnorePkg/IgnoreGroup question handling. + if (( noninteractive && ! policy_inserted )); then + pacman_args+=(--ask "$((ask_bits | 1))") + fi + pacman_args+=('linux-firmware-cirrus>=20260810-3') + repair_reboot=$old_firmware + fi +fi + # Upgrading systemd runs its post_upgrade scriptlet mid-transaction, which # reexecs both the system manager and every user manager. A pacman running # inside a user-session scope can be SIGKILLed by that reexec, abandoning the @@ -22,4 +109,14 @@ if [[ -n ${LC_ALL:-} ]]; then env_args+=(LC_ALL="$LC_ALL") fi -exec sudo env "${env_args[@]}" "${scope[@]}" pacman "$@" +if (( repair_reboot )); then + sudo env "${env_args[@]}" "${scope[@]}" pacman "${pacman_args[@]}" || exit $? + cirrus_package=$(pacman "${query_args[@]}" -Q linux-firmware-cirrus 2>/dev/null || true) + if [[ -n $cirrus_package ]] && (( $(vercmp "${cirrus_package#* }" "20260810-3") >= 0 )); then + omarchy-state set reboot-required + else + echo "Speaker firmware was not repaired; check the transaction's package exclusions." >&2 + fi +else + exec sudo env "${env_args[@]}" "${scope[@]}" pacman "${pacman_args[@]}" +fi diff --git a/test/shell.d/fixtures/sudo-boundary-test.sh b/test/shell.d/fixtures/sudo-boundary-test.sh index 1fb123d0..544d2482 100644 --- a/test/shell.d/fixtures/sudo-boundary-test.sh +++ b/test/shell.d/fixtures/sudo-boundary-test.sh @@ -32,6 +32,8 @@ PY copy_boundary_file bin/omarchy-security-functions copy_boundary_file bin/omarchy-update-pacman copy_boundary_file default/omarchy/sudo-no-update/sudo +printf '#!/bin/bash\nexit 1\n' >"$SUDO_TEST_ROOT/bin/omarchy-hw-dell-xps13-dx13260-ptl" +chmod +x "$SUDO_TEST_ROOT/bin/omarchy-hw-dell-xps13-dx13260-ptl" cat >"$SUDO_TEST_ROOT/mock/sudo" <<'STUB' #!/bin/bash diff --git a/test/shell.d/update-package-conflict-test.sh b/test/shell.d/update-package-conflict-test.sh index e05e8f7b..2f0d7053 100755 --- a/test/shell.d/update-package-conflict-test.sh +++ b/test/shell.d/update-package-conflict-test.sh @@ -31,6 +31,12 @@ STUB # for a person has to keep that stream. cat >"$stub_bin/pacman" <<'STUB' #!/bin/bash +if [[ $1 == "-Q" ]]; then + version=20260810-2 + (( $(cat "$PACMAN_ATTEMPTS") < 2 )) || version=20260810-3 + printf 'linux-firmware-cirrus %s\n' "$version" + exit 0 +fi attempt=$(($(cat "$PACMAN_ATTEMPTS") + 1)) echo "$attempt" >"$PACMAN_ATTEMPTS" { @@ -47,7 +53,16 @@ fi echo "upgrade complete" STUB +cat >"$stub_bin/omarchy-hw-dell-xps13-dx13260-ptl" <<'STUB' +#!/bin/bash +[[ ${TEST_PTL_MATCH:-0} == 1 ]] +STUB +cat >"$stub_bin/omarchy-state" <<'STUB' +#!/bin/bash +exit 0 +STUB chmod +x "$stub_bin/sudo" "$stub_bin/systemd-run" "$stub_bin/pacman" +chmod +x "$stub_bin/omarchy-hw-dell-xps13-dx13260-ptl" "$stub_bin/omarchy-state" # Everything a blocked qemu-common upgrade leaves on stderr, and no more. The # ":: ... Remove qemu-block-gluster? [y/N]" pacman asked is deliberately absent: @@ -161,3 +176,11 @@ fi [[ $(call_line 1 args) == *"--noconfirm"* ]] || fail "a caller can ask for an interactive upgrade directly" pass "only the conflict handler can hand the upgrade to a person" + +write_conflict_report +TEST_PTL_MATCH=1 run_on_terminal || fail "the firmware minimum blocks interactive conflict recovery" +[[ $(call_line 1 args) == *'linux-firmware-cirrus>=20260810-3'* && $(call_line 1 args) == *'--ask 1'* ]] || + fail "the first matching-hardware transaction retains its firmware and exclusion policy" +[[ $(call_line 2 args) == *'linux-firmware-cirrus>=20260810-3'* && $(call_line 2 args) != *'--ask'* && $(call_line 2 args) != *'--noconfirm'* ]] || + fail "the matching-hardware conflict retry must let the person answer" +pass "Panther Lake firmware repair preserves interactive package conflict recovery" diff --git a/test/shell.d/update-pacman-test.sh b/test/shell.d/update-pacman-test.sh index 8af6766c..9f2b088c 100755 --- a/test/shell.d/update-pacman-test.sh +++ b/test/shell.d/update-pacman-test.sh @@ -15,6 +15,8 @@ cat >"$stub_bin/sudo" <<'STUB' printf '%s\n' "$*" >"$SUDO_CALL_LOG" STUB chmod +x "$stub_bin/sudo" +printf '#!/bin/bash\nexit 1\n' >"$stub_bin/omarchy-hw-dell-xps13-dx13260-ptl" +chmod +x "$stub_bin/omarchy-hw-dell-xps13-dx13260-ptl" run_helper() { PATH="$stub_bin:$PATH" SUDO_CALL_LOG="$test_tmp/call" "$ROOT/bin/omarchy-update-pacman" "$@" diff --git a/test/shell.d/xps13-ptl-firmware-retention-test.sh b/test/shell.d/xps13-ptl-firmware-retention-test.sh new file mode 100755 index 00000000..418276eb --- /dev/null +++ b/test/shell.d/xps13-ptl-firmware-retention-test.sh @@ -0,0 +1,206 @@ +#!/bin/bash +set -euo pipefail + +source "$(dirname "$0")/base-test.sh" +require_command pacman +require_command pacman-conf +require_command vercmp +real_pacman=$(command -v pacman) +real_pacman_conf=$(command -v pacman-conf) +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT +mkdir -p "$test_tmp/bin" "$test_tmp/db/local" "$test_tmp/db/sync" "$test_tmp/package" +printf '9\n' >"$test_tmp/db/local/ALPM_DB_VERSION" + +export RETENTION_REAL_PACMAN="$real_pacman" RETENTION_REAL_CONF="$real_pacman_conf" +export RETENTION_ROOT="$test_tmp" RETENTION_PTL=1 +cat >"$test_tmp/bin/pacman" <<'STUB' +#!/bin/bash +query=0 +for arg in "$@"; do + [[ $arg != -Q* && $arg != -Sg* ]] || query=1 +done +if (( query )); then + if [[ ${RETENTION_SIMULATE_SUCCESS:-0} == 1 && -e $RETENTION_ROOT/transaction-success && $* == *-Q* ]]; then + printf 'linux-firmware-cirrus 20260810-3\n' + exit 0 + fi + exec "$RETENTION_REAL_PACMAN" --config "$RETENTION_ROOT/pacman.conf" --dbpath "$RETENTION_ROOT/db" "$@" +fi +printf '%s\n' "$@" >"$RETENTION_ROOT/transaction-args" +[[ ${RETENTION_FAILURE:-0} == 0 ]] || exit "$RETENTION_FAILURE" +"$RETENTION_REAL_PACMAN" --config "$RETENTION_ROOT/pacman.conf" --dbpath "$RETENTION_ROOT/db" --print --print-format '%r/%n %v' "$@" || exit $? +touch "$RETENTION_ROOT/transaction-success" +STUB +cat >"$test_tmp/bin/omarchy-state" <<'STUB' +#!/bin/bash +printf '%s\n' "$*" >>"$RETENTION_ROOT/state" +STUB +cat >"$test_tmp/bin/pacman-conf" <<'STUB' +#!/bin/bash +exec "$RETENTION_REAL_CONF" --config "$RETENTION_ROOT/pacman.conf" "$@" +STUB +cat >"$test_tmp/bin/omarchy-hw-dell-xps13-dx13260-ptl" <<'STUB' +#!/bin/bash +[[ $RETENTION_PTL == 1 ]] +STUB +cat >"$test_tmp/bin/sudo" <<'STUB' +#!/bin/bash +exec "$@" +STUB +cat >"$test_tmp/bin/systemd-run" <<'STUB' +#!/bin/bash +while [[ $1 == -* ]]; do shift; done +exec "$@" +STUB +chmod +x "$test_tmp/bin/"* + +write_repo() { + local repo="$1" version="$2" + local entry="linux-firmware-cirrus-$version" + mkdir -p "$test_tmp/package/$entry" + cat >"$test_tmp/package/$entry/desc" <"$test_tmp/db/local/linux-firmware-cirrus-$1/desc" + : >"$test_tmp/db/local/linux-firmware-cirrus-$1/files" + fi + cat >"$test_tmp/pacman.conf" <"$test_tmp/result" 2>"$test_tmp/errors" || status=$? + (( status == 0 )) || cat "$test_tmp/errors" >&2 + return "$status" +} +expect_package() { + [[ $(<"$test_tmp/result") == "$1" ]] || fail "$2" "$(<"$test_tmp/result") $(<"$test_tmp/errors")" + pass "$2" +} + +reset_fixture 20260810-3 +run_transaction -Suu --noconfirm +expect_package 'omarchy/linux-firmware-cirrus 20260810-3' "refresh selects the shim instead of downgrading to core" +run_transaction -Suu --needed --noconfirm +expect_package 'omarchy/linux-firmware-cirrus 20260810-3' "an installed target is retained even when --needed was supplied" +run_transaction --sync --sysupgrade --sysupgrade --noconfirm +expect_package 'omarchy/linux-firmware-cirrus 20260810-3' "long downgrade options retain the firmware minimum" +RETENTION_PTL=0 run_transaction -Suu --needed --noconfirm +expect_package 'core/linux-firmware-cirrus 20260810-2' "other hardware retains the original downgrade behavior" + +for installed in '' 20260810-2; do + reset_fixture "$installed" + run_transaction -Su --noconfirm + expect_package 'omarchy/linux-firmware-cirrus 20260810-3' "normal update repairs missing or downgraded firmware independently of migrations" + [[ ! -e $test_tmp/state ]] || fail "print-only transactions request no reboot" +done +reset_fixture 20260810-2 +run_transaction -Su --needed --noconfirm +expect_package 'omarchy/linux-firmware-cirrus 20260810-3' "normal update with --needed still repairs old firmware" +for installed in 20260810-3 20260910-2 1:20260810-2; do + reset_fixture "$installed" + run_transaction -Su --noconfirm + expect_package '' "normal update preserves sufficient or newer local firmware without reinstalling" + [[ ! -e $test_tmp/state ]] || fail "unchanged firmware requests no reboot" +done +reset_fixture 20260810-3 +# --print omits IgnorePkg questions, so these cases verify the forwarded mask. +run_transaction -Suu --noconfirm --ignore 'linux-firmware-*' +expect_package 'omarchy/linux-firmware-cirrus 20260810-3' "print resolution retains the minimum target with command-line exclusions" +[[ $(sed -n '/^--ask$/{n;p;}' "$test_tmp/transaction-args") == 1 ]] || fail "package exclusions use pacman's native question policy" +run_transaction -Suu --noconfirm --ignoregroup=firmware,other +expect_package 'omarchy/linux-firmware-cirrus 20260810-3' "print resolution retains the minimum target with command-line group exclusions" +[[ $(sed -n '/^--ask$/{n;p;}' "$test_tmp/transaction-args") == 1 ]] || fail "group exclusions use pacman's native question policy" +reset_fixture 20260810-3 +write_repo core 20260910-2 +run_transaction -Suu --noconfirm +expect_package 'core/linux-firmware-cirrus 20260910-2' "refresh selects newer core firmware when available" + +for ignored in 'IgnorePkg = linux-firmware-*' 'IgnoreGroup = firm*'; do + for installed in 20260810-2 20260810-3; do + reset_fixture "$installed" + sed -i "/Architecture/a $ignored" "$test_tmp/pacman.conf" + run_transaction -Suu --noconfirm + expect_package 'omarchy/linux-firmware-cirrus 20260810-3' "print resolution delegates configured exclusions to the native policy" + [[ $(sed -n '/^--ask$/{n;p;}' "$test_tmp/transaction-args") == 1 ]] || fail "configured exclusions use pacman's native question policy" + [[ ! -e $test_tmp/state ]] || fail "excluded firmware requests no reboot" + done +done +reset_fixture 20260810-3 +rm "$test_tmp/db/sync/omarchy.db" +sed -i '/\[omarchy\]/,$d' "$test_tmp/pacman.conf" +if run_transaction -Suu --noconfirm; then + fail "unavailable firmware minimum fails the transaction" +fi +[[ ! -s $test_tmp/result ]] || fail "failed resolution schedules no downgrade" +pass "unavailable firmware minimum fails without scheduling a downgrade" +reset_fixture 20260810-2 +status=0 +RETENTION_FAILURE=23 run_transaction -Su --noconfirm || status=$? +[[ $status == 23 && ! -e $test_tmp/state ]] || fail "a failed firmware repair preserves its status and requests no reboot" +pass "a failed firmware repair preserves its status and requests no reboot" + +reset_fixture 20260810-2 +RETENTION_SIMULATE_SUCCESS=1 run_transaction -Su --noconfirm +[[ $(<"$test_tmp/state") == 'set reboot-required' ]] || fail "verified installed repair requests reboot without migrations" +pass "verified installed repair requests reboot without migrations" +for installed in 20260810-2 20260810-3; do + reset_fixture "$installed" + run_transaction -Su --needed --noconfirm linux-firmware-cirrus + expected='omarchy/linux-firmware-cirrus 20260810-3' + [[ $installed != 20260810-3 ]] || expected='' + expect_package "$expected" "explicit bare targets cannot bypass the firmware minimum or create duplicates" +done +reset_fixture 20260810-3 +run_transaction -Suu --noconfirm core/linux-firmware-cirrus +expect_package 'omarchy/linux-firmware-cirrus 20260810-3' "repo-qualified bare targets cannot bypass refresh retention" +reset_fixture '' +sed -i '/Architecture/a IgnoreGroup = firm*' "$test_tmp/pacman.conf" +run_transaction -Suu --noconfirm +expect_package 'omarchy/linux-firmware-cirrus 20260810-3' "missing firmware delegates sync-only group exclusions to the native policy" + +reset_fixture 20260810-3 +run_transaction -Suu --noconfirm --ask 4 +[[ $(sed -n '/^--ask$/{n;p;}' "$test_tmp/transaction-args" | tail -1) == 5 ]] || fail "existing question bits are preserved" +pass "existing question bits are preserved" + +reset_fixture 20260810-3 +run_transaction -Suu --noconfirm -- linux-firmware-cirrus +expect_package 'omarchy/linux-firmware-cirrus 20260810-3' "the option separator remains valid with an explicit target" +reset_fixture 20260810-2 +run_transaction -Su --noconfirm --confirm +[[ $(<"$test_tmp/transaction-args") != *'--ask'* ]] || fail "a final --confirm keeps the interactive question policy" +pass "a final --confirm keeps the interactive question policy" +run_transaction -Su --confirm --noconfirm +[[ $(sed -n '/^--ask$/{n;p;}' "$test_tmp/transaction-args") == 1 ]] || fail "a final --noconfirm keeps noninteractive exclusions" +pass "a final --noconfirm keeps noninteractive exclusions"