From f5194e3ff596789b71b912ba945d90e3693cf6aa Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 13 Sep 2026 10:21:38 +0200 Subject: [PATCH 1/4] Shield Omarchy pacman transactions from desktop session teardown Upgrading systemd runs its post_upgrade scriptlet mid-transaction, which reexecs both the system manager and every user manager. When pacman runs inside a user-session scope (the floating update terminal), that reexec can SIGKILL it and abandon the transaction halfway, with packages upgraded but none of the post-transaction hooks run. Route every Omarchy-owned system mutation through a new hidden omarchy-update-pacman helper that registers the transaction as a PID 1 scope via systemd-run, keeping it out of the user manager's cgroups. System scopes survive the system manager's own reexec, and as a bonus the transaction now also survives its terminal window closing. On unbooted systems (the installer chroot) the helper runs pacman directly. Co-Authored-By: Claude Fable 5 --- bin/omarchy-channel-set | 2 +- bin/omarchy-refresh-pacman | 2 +- bin/omarchy-reinstall-pkgs | 4 ++-- bin/omarchy-update-pacman | 25 +++++++++++++++++++++++++ bin/omarchy-update-system-pkgs | 4 ++-- docs/update-process.md | 14 ++++++++++---- test/shell.d/channel-test.sh | 18 ++++++++++++------ 7 files changed, 53 insertions(+), 16 deletions(-) create mode 100755 bin/omarchy-update-pacman diff --git a/bin/omarchy-channel-set b/bin/omarchy-channel-set index 7e112388..dcc9bdb4 100755 --- a/bin/omarchy-channel-set +++ b/bin/omarchy-channel-set @@ -85,7 +85,7 @@ fi omarchy-refresh-pacman "$pacman_channel" # --ask 4 accepts omarchy <-> omarchy-dev replacement prompts without file overwrites. -sudo env OMARCHY_UPDATE_PACMAN=1 pacman -S --needed --noconfirm --ask 4 "${packages[@]}" +omarchy-update-pacman -S --needed --noconfirm --ask 4 "${packages[@]}" if [[ -z $dev_checkout ]]; then omarchy-dev-unlink --no-reboot diff --git a/bin/omarchy-refresh-pacman b/bin/omarchy-refresh-pacman index 299d6c20..af16a347 100755 --- a/bin/omarchy-refresh-pacman +++ b/bin/omarchy-refresh-pacman @@ -23,4 +23,4 @@ sudo cp -f "$OMARCHY_PATH/default/pacman/mirrorlist-$channel" /etc/pacman.d/mirr omarchy-hook pre-refresh-pacman # Reset all package DBs and then update -sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syyuu --noconfirm +omarchy-update-pacman -Syyuu --noconfirm diff --git a/bin/omarchy-reinstall-pkgs b/bin/omarchy-reinstall-pkgs index c1d51cbf..97aff9c6 100755 --- a/bin/omarchy-reinstall-pkgs +++ b/bin/omarchy-reinstall-pkgs @@ -11,8 +11,8 @@ set -e omarchy-refresh-pacman # Downgrade any packages to the stable setup -sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Suu --noconfirm +omarchy-update-pacman -Suu --noconfirm # Ensure all packages are installed mapfile -t packages < <(grep -v '^#' "$OMARCHY_PATH/install/omarchy-base.packages" | grep -v '^$') -sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syu --noconfirm --needed "${packages[@]}" +omarchy-update-pacman -Syu --noconfirm --needed "${packages[@]}" diff --git a/bin/omarchy-update-pacman b/bin/omarchy-update-pacman new file mode 100755 index 00000000..66f66731 --- /dev/null +++ b/bin/omarchy-update-pacman @@ -0,0 +1,25 @@ +#!/bin/bash + +# omarchy:summary=Run a pacman transaction for the Omarchy update flow, shielded from desktop session teardown. +# omarchy:args= +# omarchy:hidden=true +# omarchy:requires-sudo=true + +# 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 +# transaction halfway. Registering the transaction as a PID 1 scope keeps it +# out of the user manager's cgroups entirely, and system scopes survive the +# system manager's own reexec. +scope=() +if [[ -d /run/systemd/system && ! -L /run/systemd/system ]]; then + scope=(systemd-run --scope --quiet --collect) +fi + +# LC_ALL passes through so callers that parse pacman's stderr can pin the locale. +env_args=(OMARCHY_UPDATE_PACMAN=1) +if [[ -n ${LC_ALL:-} ]]; then + env_args+=(LC_ALL="$LC_ALL") +fi + +exec sudo env "${env_args[@]}" "${scope[@]}" pacman "$@" diff --git a/bin/omarchy-update-system-pkgs b/bin/omarchy-update-system-pkgs index 9e6f9998..c2e86388 100755 --- a/bin/omarchy-update-system-pkgs +++ b/bin/omarchy-update-system-pkgs @@ -10,7 +10,7 @@ set -e # a stream, because an upgrade without --noconfirm prompts on stderr. The # handler has already said why, so no heading here either. if [[ ${OMARCHY_UPDATE_CONFLICT:-} == 1 && ${OMARCHY_UPDATE_INTERACTIVE:-} == 1 ]]; then - exec sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syu --overwrite '/usr/share/omarchy/*' + exec omarchy-update-pacman -Syu --overwrite '/usr/share/omarchy/*' fi echo -e "\e[32m\nUpdate system packages\e[0m" @@ -23,7 +23,7 @@ trap 'rm -f "$errors"' EXIT # # Progress bars stay on stdout. Errors are on stderr, kept for the conflict # handler below; LC_ALL=C is what keeps them parseable in any locale. -if sudo env LC_ALL=C OMARCHY_UPDATE_PACMAN=1 pacman -Syu --noconfirm \ +if LC_ALL=C omarchy-update-pacman -Syu --noconfirm \ --overwrite '/usr/share/omarchy/*' 2>"$errors"; then cat "$errors" >&2 exit 0 diff --git a/docs/update-process.md b/docs/update-process.md index bec3350d..efecb94c 100644 --- a/docs/update-process.md +++ b/docs/update-process.md @@ -82,13 +82,18 @@ Omarchy update command, the hook exits non-zero with `AbortOnFail`, which stops the transaction before packages are changed. `omarchy-update-system-pkgs`, `omarchy-refresh-pacman`, `omarchy-reinstall-pkgs`, -`omarchy-channel-set`, and the v4 upgrader run pacman through: +and `omarchy-channel-set` run pacman through the hidden `omarchy-update-pacman` +helper (the v4 upgrader sets `OMARCHY_UPDATE_PACMAN=1` directly): ```bash -env OMARCHY_UPDATE_PACMAN=1 pacman ... +sudo env OMARCHY_UPDATE_PACMAN=1 systemd-run --scope --quiet --collect pacman ... ``` -so the guard allows Omarchy-owned update flows. A user can intentionally bypass +so the guard allows Omarchy-owned update flows. The `systemd-run --scope` +wrapper registers the transaction as a PID 1 scope: upgrading systemd reexecs +the system and user managers mid-transaction, and a pacman left inside a +user-session scope can be SIGKILLed by that reexec. On unbooted systems (such +as the installer chroot) the helper runs pacman directly. A user can intentionally bypass the guard with: ```bash @@ -272,12 +277,13 @@ scripts. | `omarchy-update-confirm` | Gum confirmation copy for `omarchy update`. | **Question.** Could be inlined into `omarchy-update`; separate file only helps keep copy isolated. | | `omarchy-update-dev` | Fast-forwards the active dev-linked checkout from its configured upstream; no-ops for package-backed installs. | **Keep.** Runs before package updates so a checkout conflict stops the update before system mutation. | | `omarchy-update-keyring` | Ensures Omarchy keyring and Arch keyring are current before the main transaction. | **Keep, but review.** It uses targeted `pacman -Sy` for keyring bootstrapping; acceptable for this special case but should remain tightly scoped. | -| `omarchy-update-system-pkgs` | Runs `sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syu --noconfirm` with `--overwrite '/usr/share/omarchy/*'`, capturing stderr to a report file; on failure it execs `omarchy-update-system-pkgs-when-conflicted`. | **Keep for now.** Small leaf command, clear/testable. | +| `omarchy-update-system-pkgs` | Runs `omarchy-update-pacman -Syu --noconfirm` with `--overwrite '/usr/share/omarchy/*'`, capturing stderr to a report file; on failure it execs `omarchy-update-system-pkgs-when-conflicted`. | **Keep for now.** Small leaf command, clear/testable. | | `omarchy-update-system-pkgs-when-conflicted` | Hidden conflict handler: quarantines unowned conflicting files under `/var/lib/omarchy/replaced`, retries the upgrade once, restores files the upgrade didn't claim, and hands package-vs-package conflicts to an interactive pacman run (never under `-y`). | **Keep internal/hidden.** Keeps conflict recovery out of the happy path. | | `omarchy-update-pkg-prune` | Trims the pacman cache to two versions per package (`paccache -rk2`) before the snapshot, keeping the offline downgrade path while capping snapshot growth. | **Keep internal/hidden.** | | `omarchy-update-requires-free-space` | Aborts the update below a 10 GiB free-space threshold on `/`; silently skipped when free space cannot be determined; `OMARCHY_UPDATE_FORCE=1` bypasses. | **Keep internal/hidden.** | | `omarchy-migrate` | Public migration command. Waits for pacman, then runs all pending migrations for the current user. Supports `--pending`. | **Keep.** This replaces the discarded `omarchy-update-user-finalize` name and no longer needs `--force`. | | `omarchy-update-pacman-guard` | ALPM pre-transaction guard that aborts direct `pacman -Syu` style upgrades unless Omarchy set `OMARCHY_UPDATE_PACMAN=1` or the user explicitly set `OMARCHY_ALLOW_DIRECT_PACMAN=1`. | **Keep internal/hidden.** This is what nudges users back to `omarchy update`. | +| `omarchy-update-pacman` | Hidden helper that runs a guard-approved pacman transaction as a PID 1 scope (`systemd-run --scope`) so a mid-transaction systemd reexec cannot kill it; runs pacman directly when not booted under systemd. | **Keep internal/hidden.** Single place that owns how Omarchy invokes pacman for system mutation. | | `omarchy-migrate-notify` | Internal login-time notification helper. Uses `omarchy-migrate --pending` and shows a notification only when this user has pending migrations. | **Keep internal/hidden.** Clear name now that the public command is `omarchy-migrate`. | | `omarchy-update-user-notify` | Hidden compatibility wrapper for `omarchy-migrate-notify`. | **Temporary.** Keep only for old callers. | | `omarchy-update-available` | Update checker for shell widget and post-update refresh. | **Keep.** Could eventually be renamed `omarchy-update-check`, but current name matches widget semantics. | diff --git a/test/shell.d/channel-test.sh b/test/shell.d/channel-test.sh index 664e17c5..122a15aa 100644 --- a/test/shell.d/channel-test.sh +++ b/test/shell.d/channel-test.sh @@ -31,6 +31,12 @@ for arg in "$@"; do printf "\t%s" "$arg" >>"$OMARCHY_CHANNEL_TEST_LOG"; done printf "\n" >>"$OMARCHY_CHANNEL_TEST_LOG" ' +write_stub omarchy-update-pacman '#!/bin/bash +printf "update-pacman" >>"$OMARCHY_CHANNEL_TEST_LOG" +for arg in "$@"; do printf "\t%s" "$arg" >>"$OMARCHY_CHANNEL_TEST_LOG"; done +printf "\n" >>"$OMARCHY_CHANNEL_TEST_LOG" +' + write_stub omarchy-dev-unlink '#!/bin/bash printf "unlink" >>"$OMARCHY_CHANNEL_TEST_LOG" for arg in "$@"; do printf "\t%s" "$arg" >>"$OMARCHY_CHANNEL_TEST_LOG"; done @@ -106,7 +112,7 @@ assert_log_line() { run_channel stable assert_log_line $'refresh\tstable' "stable refreshes the stable pacman channel" -assert_log_line $'sudo\tenv\tOMARCHY_UPDATE_PACMAN=1\tpacman\t-S\t--needed\t--noconfirm\t--ask\t4\tomarchy\tomarchy-settings' "stable installs stable Omarchy packages" +assert_log_line $'update-pacman\t-S\t--needed\t--noconfirm\t--ask\t4\tomarchy\tomarchy-settings' "stable installs stable Omarchy packages" assert_log_line $'unlink\t--no-reboot' "stable restores the package-backed Omarchy path without an early reboot prompt" assert_log_line $'update\t-y\tOMARCHY_PATH=/usr/share/omarchy' "stable runs the normal update pipeline from the package-backed path" if grep -q $'^state\tset\treboot-required$' "$log_file"; then @@ -116,17 +122,17 @@ pass "stable does not require reboot when already package-backed" run_channel rc assert_log_line $'refresh\trc' "rc refreshes the rc pacman channel" -assert_log_line $'sudo\tenv\tOMARCHY_UPDATE_PACMAN=1\tpacman\t-S\t--needed\t--noconfirm\t--ask\t4\tomarchy\tomarchy-settings' "rc installs rc Omarchy packages" +assert_log_line $'update-pacman\t-S\t--needed\t--noconfirm\t--ask\t4\tomarchy\tomarchy-settings' "rc installs rc Omarchy packages" assert_log_line $'unlink\t--no-reboot' "rc restores the package-backed Omarchy path without an early reboot prompt" assert_log_line $'update\t-y\tOMARCHY_PATH=/usr/share/omarchy' "rc runs the normal update pipeline from the package-backed path" OMARCHY_TEST_PATH="$ROOT" run_channel edge assert_log_line $'refresh\tedge' "edge refreshes the edge pacman channel" -assert_log_line $'sudo\tenv\tOMARCHY_UPDATE_PACMAN=1\tpacman\t-S\t--needed\t--noconfirm\t--ask\t4\tomarchy-dev\tomarchy-settings-dev' "edge installs development Omarchy packages" +assert_log_line $'update-pacman\t-S\t--needed\t--noconfirm\t--ask\t4\tomarchy-dev\tomarchy-settings-dev' "edge installs development Omarchy packages" assert_log_line $'unlink\t--no-reboot' "edge unlinks dev without an early reboot prompt" assert_log_line $'state\tset\treboot-required' "edge marks reboot required when leaving dev" assert_log_line $'update\t-y\tOMARCHY_PATH=/usr/share/omarchy' "edge runs the normal update pipeline from the package-backed path" -[[ $(grep -E '^(unlink|state|update)' "$log_file") == $'unlink\t--no-reboot\nstate\tset\treboot-required\nupdate\t-y\tOMARCHY_PATH=/usr/share/omarchy' ]] || +[[ $(grep -E $'^(unlink|state|update)\t' "$log_file") == $'unlink\t--no-reboot\nstate\tset\treboot-required\nupdate\t-y\tOMARCHY_PATH=/usr/share/omarchy' ]] || fail "edge defers the reboot prompt until the update restart stage" "$(cat "$log_file")" pass "edge defers the reboot prompt until the update restart stage" @@ -146,12 +152,12 @@ rmdir "$checkout" run_channel dev assert_log_line $'gum\tconfirm\t--default=false\tSwitch to dev channel?' "dev asks for confirmation" assert_log_line $'refresh\tedge' "dev refreshes the edge pacman channel" -assert_log_line $'sudo\tenv\tOMARCHY_UPDATE_PACMAN=1\tpacman\t-S\t--needed\t--noconfirm\t--ask\t4\tomarchy-dev\tomarchy-settings-dev' "dev installs development Omarchy packages" +assert_log_line $'update-pacman\t-S\t--needed\t--noconfirm\t--ask\t4\tomarchy-dev\tomarchy-settings-dev' "dev installs development Omarchy packages" assert_log_line $'git\tclone\thttps://github.com/basecamp/omarchy.git\t'"$checkout" "dev clones the source checkout to ~/omarchy" assert_log_line $'link\t'"$checkout"$'\t--no-reboot' "dev links ~/omarchy without an early reboot prompt" assert_log_line $'state\tset\treboot-required' "dev defers the reboot prompt to the update pipeline" assert_log_line $'update\t-y\tOMARCHY_PATH='"$checkout" "dev runs the normal update pipeline from the source checkout" -[[ $(grep -E '^(git|link|state|refresh|sudo|update)' "$log_file") == $'git\tclone\thttps://github.com/basecamp/omarchy.git\t'"$checkout"$'\nlink\t'"$checkout"$'\t--no-reboot\nstate\tset\treboot-required\nrefresh\tedge\nsudo\tenv\tOMARCHY_UPDATE_PACMAN=1\tpacman\t-S\t--needed\t--noconfirm\t--ask\t4\tomarchy-dev\tomarchy-settings-dev\nupdate\t-y\tOMARCHY_PATH='"$checkout" ]] || +[[ $(grep -E '^(git|link|state|refresh|sudo|update)' "$log_file") == $'git\tclone\thttps://github.com/basecamp/omarchy.git\t'"$checkout"$'\nlink\t'"$checkout"$'\t--no-reboot\nstate\tset\treboot-required\nrefresh\tedge\nupdate-pacman\t-S\t--needed\t--noconfirm\t--ask\t4\tomarchy-dev\tomarchy-settings-dev\nupdate\t-y\tOMARCHY_PATH='"$checkout" ]] || fail "dev activates the checkout before changing or updating packages" "$(cat "$log_file")" pass "dev activates the checkout before changing or updating packages" From bc0753dfcbf723a362060c32b3e36739798fee38 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 13 Sep 2026 10:21:46 +0200 Subject: [PATCH 2/4] Show a failure state when a presented command exits non-zero The floating-terminal presentation wrapper showed the green "Done!" prompt for every exit code except Ctrl-C, so a failed update or channel switch closed looking like a success. Pass the command's exit code through to omarchy-show-done and render a red "Failed (exit code N)!" prompt when it is non-zero. The pkg install/remove pickers get the same treatment. Co-Authored-By: Claude Fable 5 --- ...omarchy-launch-floating-terminal-with-presentation | 3 ++- bin/omarchy-pkg-aur-install | 3 ++- bin/omarchy-pkg-install | 2 +- bin/omarchy-pkg-remove | 2 +- bin/omarchy-show-done | 11 +++++++++-- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/bin/omarchy-launch-floating-terminal-with-presentation b/bin/omarchy-launch-floating-terminal-with-presentation index c9cb255a..4497ff0d 100755 --- a/bin/omarchy-launch-floating-terminal-with-presentation +++ b/bin/omarchy-launch-floating-terminal-with-presentation @@ -8,6 +8,7 @@ source omarchy-restart-gum cmd="$*" -presentation_script="omarchy-show-logo; $cmd; if (( \$? != 130 )); then omarchy-show-done; fi" +# 130 is the user bailing with Ctrl-C; anything else gets the Done/Failed prompt. +presentation_script="omarchy-show-logo; $cmd; code=\$?; if (( code != 130 )); then omarchy-show-done \$code; fi" exec setsid uwsm-app -- xdg-terminal-exec --app-id=org.omarchy.terminal --title=Omarchy -e bash -c "$presentation_script" diff --git a/bin/omarchy-pkg-aur-install b/bin/omarchy-pkg-aur-install index 3d38b4db..dc393b8d 100755 --- a/bin/omarchy-pkg-aur-install +++ b/bin/omarchy-pkg-aur-install @@ -24,6 +24,7 @@ if [[ -n $pkg_names ]]; then source omarchy-sudo-keepalive echo "$pkg_names" | sed 's/^/aur\//' | tr '\n' ' ' | xargs yay -S --noconfirm + code=$? sudo updatedb --prune-bind-mounts=no --add-prunepaths=/.snapshots - omarchy-show-done + omarchy-show-done $code fi diff --git a/bin/omarchy-pkg-install b/bin/omarchy-pkg-install index 15e5e288..e5e0fb42 100755 --- a/bin/omarchy-pkg-install +++ b/bin/omarchy-pkg-install @@ -22,5 +22,5 @@ if [[ -n $pkg_names ]]; then # Convert newline-separated selections to space-separated for pacman echo "$pkg_names" | tr '\n' ' ' | xargs sudo pacman -S --noconfirm - omarchy-show-done + omarchy-show-done $? fi diff --git a/bin/omarchy-pkg-remove b/bin/omarchy-pkg-remove index 486d3772..37ca448e 100755 --- a/bin/omarchy-pkg-remove +++ b/bin/omarchy-pkg-remove @@ -20,5 +20,5 @@ pkg_names=$(yay -Qqe | fzf "${fzf_args[@]}") if [[ -n $pkg_names ]]; then # Convert newline-separated selections to space-separated for yay echo "$pkg_names" | tr '\n' ' ' | xargs sudo pacman -Rns --noconfirm - omarchy-show-done + omarchy-show-done $? fi diff --git a/bin/omarchy-show-done b/bin/omarchy-show-done index d5d8ccde..14c9f156 100755 --- a/bin/omarchy-show-done +++ b/bin/omarchy-show-done @@ -1,6 +1,9 @@ #!/bin/bash -# omarchy:summary=Display a "Done!" message and wait for user to press any key. +# omarchy:summary=Display a "Done!" or "Failed!" message and wait for user to press any key. +# omarchy:args=[exit-code] + +code="${1:-0}" # The device node is there whether or not a terminal is behind it, so opening # it is the only test that means anything. @@ -13,6 +16,10 @@ while read -rsn 1 -t 0.1 _ /dev/tty +if (( code == 0 )); then + printf '\n\033[32m● \033[0mDone! Press any key to close...' >/dev/tty +else + printf '\n\033[31m● \033[0mFailed (exit code %d)! Press any key to close...' "$code" >/dev/tty +fi read -rsn 1 /dev/tty From 24841ac5d24552ebb7bfe4e0491949236e22963a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 13 Sep 2026 10:21:46 +0200 Subject: [PATCH 3/4] Tell how to resume a channel switch that failed partway omarchy-channel-set runs under set -e, so a failure after it has begun mutating the system (dev link, pacman channel, packages) died silently with the switch half-applied. Trap ERR once the mutation phase starts and say how to pick the switch back up. Co-Authored-By: Claude Fable 5 --- bin/omarchy-channel-set | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/omarchy-channel-set b/bin/omarchy-channel-set index dcc9bdb4..d4c482f2 100755 --- a/bin/omarchy-channel-set +++ b/bin/omarchy-channel-set @@ -72,6 +72,10 @@ case "$channel" in ;; esac +# A failure past this point leaves the channel switch half-applied, so say how +# to pick it back up rather than dying silently under set -e. +trap 'echo -e "\nThe channel switch did not complete. Review the error above, then rerun: omarchy-channel-set '"$channel"'" >&2' ERR + if [[ -z $dev_checkout && $OMARCHY_PATH != "/usr/share/omarchy" ]]; then leaving_dev=1 fi From 83c6dd230c1e9ec35550c6ad077a206b17a99b8b Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 13 Sep 2026 10:35:32 +0200 Subject: [PATCH 4/4] Keep the conflict tests inside their fixture despite the scope wrapper The update conflict tests stub sudo and pacman, but omarchy-update-pacman now puts systemd-run between them, so on a systemd-booted host the tests would reach for the real system manager. Stub systemd-run to drop the wrapper's options and run the command, and cover the helper's own invocation composition in a new test. Raised by codex review. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_015zcqENR1UbhuwC1v5u3Wop --- test/shell.d/update-file-conflict-test.sh | 10 ++++- test/shell.d/update-package-conflict-test.sh | 10 ++++- test/shell.d/update-pacman-test.sh | 39 ++++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100755 test/shell.d/update-pacman-test.sh diff --git a/test/shell.d/update-file-conflict-test.sh b/test/shell.d/update-file-conflict-test.sh index 5fa954ea..526a1c6c 100755 --- a/test/shell.d/update-file-conflict-test.sh +++ b/test/shell.d/update-file-conflict-test.sh @@ -15,6 +15,14 @@ cat >"$stub_bin/sudo" <<'STUB' exec "$@" STUB +# omarchy-update-pacman wraps the transaction in a real PID 1 scope; the tests +# must stay inside the fixture, so drop the wrapper's options and run the command. +cat >"$stub_bin/systemd-run" <<'STUB' +#!/bin/bash +while [[ $1 == -* ]]; do shift; done +exec "$@" +STUB + # Fails the first -Syu with the report under test, then succeeds unless the case # asked for the retry to fail too. cat >"$stub_bin/pacman" <<'STUB' @@ -40,7 +48,7 @@ fi echo "upgrade complete" STUB -chmod +x "$stub_bin/sudo" "$stub_bin/pacman" +chmod +x "$stub_bin/sudo" "$stub_bin/systemd-run" "$stub_bin/pacman" replaced="$test_tmp/replaced" diff --git a/test/shell.d/update-package-conflict-test.sh b/test/shell.d/update-package-conflict-test.sh index cbe6286f..e05e8f7b 100755 --- a/test/shell.d/update-package-conflict-test.sh +++ b/test/shell.d/update-package-conflict-test.sh @@ -17,6 +17,14 @@ cat >"$stub_bin/sudo" <<'STUB' exec "$@" STUB +# omarchy-update-pacman wraps the transaction in a real PID 1 scope; the tests +# must stay inside the fixture, so drop the wrapper's options and run the command. +cat >"$stub_bin/systemd-run" <<'STUB' +#!/bin/bash +while [[ $1 == -* ]]; do shift; done +exec "$@" +STUB + # Fails the first -Syu with the report under test, then succeeds. Every call # records its arguments and which of its streams reached a terminal: pacman puts # its questions on stderr once it is not running --noconfirm, so a retry meant @@ -39,7 +47,7 @@ fi echo "upgrade complete" STUB -chmod +x "$stub_bin/sudo" "$stub_bin/pacman" +chmod +x "$stub_bin/sudo" "$stub_bin/systemd-run" "$stub_bin/pacman" # Everything a blocked qemu-common upgrade leaves on stderr, and no more. The # ":: ... Remove qemu-block-gluster? [y/N]" pacman asked is deliberately absent: diff --git a/test/shell.d/update-pacman-test.sh b/test/shell.d/update-pacman-test.sh new file mode 100755 index 00000000..a9278b37 --- /dev/null +++ b/test/shell.d/update-pacman-test.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "$0")/base-test.sh" + +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT + +stub_bin="$test_tmp/bin" +mkdir -p "$stub_bin" + +cat >"$stub_bin/sudo" <<'STUB' +#!/bin/bash +printf '%s\n' "$*" >"$SUDO_CALL_LOG" +STUB +chmod +x "$stub_bin/sudo" + +run_helper() { + PATH="$stub_bin:$PATH" SUDO_CALL_LOG="$test_tmp/call" "$ROOT/bin/omarchy-update-pacman" "$@" +} + +# The scope wrapper only applies on a systemd-booted host, so expect what the +# helper's own booted check would decide for this machine. +if [[ -d /run/systemd/system && ! -L /run/systemd/system ]]; then + expected_scope="systemd-run --scope --quiet --collect " +else + expected_scope="" +fi + +run_helper -Syu --noconfirm +[[ $(cat "$test_tmp/call") == "env OMARCHY_UPDATE_PACMAN=1 ${expected_scope}pacman -Syu --noconfirm" ]] || + fail "helper composes the guarded pacman invocation" "$(cat "$test_tmp/call")" +pass "helper composes the guarded pacman invocation" + +LC_ALL=C run_helper -Syu +[[ $(cat "$test_tmp/call") == "env OMARCHY_UPDATE_PACMAN=1 LC_ALL=C ${expected_scope}pacman -Syu" ]] || + fail "helper forwards LC_ALL to the transaction" "$(cat "$test_tmp/call")" +pass "helper forwards LC_ALL to the transaction"