Files
omarchy/test/shell.d/update-sequence-test.sh
T
David Heinemeier HanssonandClaude Opus 5.5 e1614f2bdb Ask for the sudo password once per omarchy update (#13323)
* Ask for the sudo password once per omarchy update

Every sudo call in omarchy update prompted, because the no-update wrapper
covered the whole run on top of per-phase revokes, and stay-awake revoked
the timestamp on its own entry and exit. A single update could ask four
times before the snapshot finished (#13319).

Authorize once, right after confirmation, starting from a revoked
timestamp so the prompt always belongs to this update. A background
keepalive refreshes it until the update is done. Prune, snapshot,
stay-awake, keyring, system packages, migrations, orphan removal, service
restarts, the post-update hook, and mise all share that authorization.

AUR builds run third-party PKGBUILD code, so they move to the end and run
cold: the keepalive stops, the timestamp is revoked, and yay and any bare
sudo use the no-update wrapper. The timestamp is revoked again after AUR
and on every exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep the single authorization for passwordless sudo and ttyless inhibition

Authorize by running a command instead of sudo -v. Under the default
verifypw=all, -v prompts even when passwordless sudo is enabled, which
would have added a prompt those users never had.

Inside an update without a terminal, stay-awake now reuses the update's
authorization with a non-interactive sudo instead of asking again through
polkit. It falls back to polkit only if that authorization is gone.

The test sudo refuses a cold non-interactive call, as the real one does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 15:31:40 +02:00

109 lines
3.7 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
source "$SHELL_TEST_DIR/fixtures/sudo-boundary-test.sh"
copy_boundary_file bin/omarchy-update
test_tmp="$boundary_tmp"
stub_bin="$SUDO_TEST_ROOT/bin"
# Every step omarchy-update runs, recorded in order with the unattended flag it
# was handed. One of them can be told to fail.
steps=(
omarchy-update-lock
omarchy-update-requires-free-space
omarchy-update-confirm
omarchy-update-pkg-prune
omarchy-snapshot
omarchy-update-stay-awake
omarchy-update-dev
omarchy-update-keyring
omarchy-update-system-pkgs
omarchy-migrate
omarchy-hook
omarchy-update-aur-pkgs
omarchy-update-mise
omarchy-update-orphan-pkgs
omarchy-update-analyze-logs
omarchy-update-status
omarchy-update-restart
)
for step in "${steps[@]}"; do
rm -f "$stub_bin/$step"
cat >"$stub_bin/$step" <<'STUB'
#!/bin/bash
printf '%s unattended=%s\n' "${0##*/}" "${OMARCHY_UPDATE_UNATTENDED:-}" >>"$STEP_LOG"
[[ ${FAILING_STEP:-} != "${0##*/}" ]] || exit 1
STUB
chmod +x "$stub_bin/$step"
done
# OMARCHY_UPDATE_LOGGED stands in for the script(1) wrapper the update re-execs
# itself under; the stubbed lock reports itself already held.
run_update() {
: >"$test_tmp/steps"
STEP_LOG="$test_tmp/steps" \
FAILING_STEP="${FAILING_STEP:-}" \
OMARCHY_UPDATE_LOGGED=1 \
PATH="$stub_bin:$PATH" \
"$SUDO_TEST_ROOT/bin/omarchy-update" "$@" >"$test_tmp/out" 2>"$test_tmp/err"
}
steps_run() {
cut -d' ' -f1 "$test_tmp/steps"
}
# Every step of a whole update, in order. $1 asks for the one a person confirms.
# Stay Awake bookends the work, so it is here twice.
expected_steps() {
printf '%s\n' \
omarchy-update-lock \
omarchy-update-requires-free-space \
${1:+omarchy-update-confirm} \
omarchy-update-pkg-prune \
omarchy-snapshot \
omarchy-update-stay-awake \
omarchy-update-dev \
omarchy-update-keyring \
omarchy-update-system-pkgs \
omarchy-migrate \
omarchy-update-orphan-pkgs \
omarchy-update-analyze-logs \
omarchy-update-status \
omarchy-update-restart \
omarchy-hook \
omarchy-update-mise \
omarchy-update-aur-pkgs \
omarchy-update-stay-awake \
omarchy-update-restart
}
run_update -y || fail "an update where everything works reports a failure"
diff <(expected_steps) <(steps_run) >"$test_tmp/order" ||
fail "an update where everything works does not run every step in order" "$(cat "$test_tmp/order")"
pass "an update where every step works runs all of them, in order"
grep -q '^omarchy-update-system-pkgs unattended=1$' "$test_tmp/steps" ||
fail "-y does not mark the update unattended"
run_update </dev/null || fail "a confirmed update reports a failure"
diff <(expected_steps confirmed) <(steps_run) >"$test_tmp/order" ||
fail "a confirmed update runs a different set of steps" "$(cat "$test_tmp/order")"
grep -q '^omarchy-update-system-pkgs unattended=$' "$test_tmp/steps" ||
fail "an update a person confirmed is treated as unattended"
pass "-y is what marks an update unattended, not the update itself"
# Migrations ship with the packages the upgrade installs and are written against
# them. Running them against what is still on disk is the failure this ordering
# exists to prevent, so the update stops where the packages did.
if FAILING_STEP=omarchy-update-system-pkgs run_update -y; then
fail "an update whose packages did not upgrade passes for a whole one"
fi
for step in omarchy-migrate omarchy-hook omarchy-update-aur-pkgs omarchy-update-restart; do
if grep -q "^$step " "$test_tmp/steps"; then
fail "a blocked package upgrade still runs $step"
fi
done
pass "a blocked package upgrade stops the update before it migrates"