Wait on the profile being repaired, not on every browser (#6837)
The migration asked a user to close every running browser before repairing the Copy URL shortcut, but a browser only ever rewrites its own Preferences on exit. Waiting on all browsers deadlocks `omarchy update` for anyone whose main browser is effectively never closed: the pending ghosts commonly sit in a stale profile nobody has open, yet the migration blocks on the always-open browser until the prompt is declined, failing the whole update. A running Chromium-family browser holds a SingletonLock (and socket) inside its user-data-dir, so whether the profile being repaired is open is mechanical. Gate on that instead of on the sheer presence of a browser process — the repair proceeds where the affected profile is closed, and browsers attached to other profiles no longer hold the update hostage. The gate stays conservative while an affected profile actually is open, and the existing post-repair verification still catches a browser that starts mid-repair and restores stale Preferences on exit. The migration test now simulates an open profile with its SingletonLock instead of a pgrep stub; every prior scenario still passes.
This commit is contained in:
@@ -12,7 +12,8 @@ test_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$test_dir"' EXIT
|
||||
|
||||
home="$test_dir/home"
|
||||
preferences="$home/.config/chromium/Default/Preferences"
|
||||
profile_root="$home/.config/chromium"
|
||||
preferences="$profile_root/Default/Preferences"
|
||||
mkdir -p "$(dirname "$preferences")"
|
||||
|
||||
# Any id Chromium once derived from the extension's keyless load path; the
|
||||
@@ -27,23 +28,38 @@ write_stale_preferences() {
|
||||
stub_bin="$test_dir/bin"
|
||||
mkdir -p "$stub_bin"
|
||||
|
||||
REAL_PYTHON=$(command -v python3)
|
||||
export REAL_PYTHON
|
||||
|
||||
run_migration() {
|
||||
HOME="$home" PATH="$stub_bin:$PATH" bash -euo pipefail "$migration" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# A running browser prompts for the windows to be closed; declining (or having
|
||||
# no terminal to ask in) defers the repair so a rewrite-on-exit cannot revert
|
||||
# it.
|
||||
printf '#!/bin/bash\nexit 0\n' >"$stub_bin/pgrep"
|
||||
# A running Chromium-family browser marks its profile root with a SingletonLock
|
||||
# symlink to <hostname>-<pid>, a target that never exists on disk. That lock —
|
||||
# not the mere presence of a browser process — is what the migration waits on.
|
||||
open_browser() {
|
||||
mkdir -p "$profile_root"
|
||||
ln -sfn "test-host-1234" "$profile_root/SingletonLock"
|
||||
}
|
||||
close_browser() {
|
||||
rm -f "$profile_root/SingletonLock"
|
||||
}
|
||||
|
||||
# The affected profile being open prompts for the windows to be closed;
|
||||
# declining (or having no terminal to ask in) defers the repair so a
|
||||
# rewrite-on-exit cannot revert it.
|
||||
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/gum"
|
||||
chmod +x "$stub_bin/pgrep" "$stub_bin/gum"
|
||||
chmod +x "$stub_bin/gum"
|
||||
write_stale_preferences
|
||||
open_browser
|
||||
|
||||
before_hash=$(sha256sum "$preferences" | cut -d' ' -f1)
|
||||
|
||||
run_migration && fail "migration defers while a browser is running"
|
||||
run_migration && fail "migration defers while the affected profile is open"
|
||||
[[ $(sha256sum "$preferences" | cut -d' ' -f1) == "$before_hash" ]] ||
|
||||
fail "migration leaves preferences alone while a browser is running"
|
||||
pass "migration defers the repair while a browser is running"
|
||||
fail "migration leaves preferences alone while the affected profile is open"
|
||||
pass "migration defers the repair while the affected profile is open"
|
||||
|
||||
# gum paints its prompt on stderr, so that stream has to stay attached:
|
||||
# suppressing it leaves gum reading keys behind an unpainted screen, which
|
||||
@@ -59,34 +75,47 @@ HOME="$home" PATH="$stub_bin:$PATH" bash -euo pipefail "$migration" >/dev/null 2
|
||||
grep -q "gum-prompt-painted" "$prompt_stderr" || fail "migration keeps the browser prompt visible"
|
||||
pass "migration keeps the browser prompt visible"
|
||||
|
||||
# Confirming the prompt after closing the browser lets the repair proceed.
|
||||
# A browser holding a different profile root cannot revert this repair, so it
|
||||
# must not hold the update: the repair goes through without ever reaching the
|
||||
# prompt, which the still-declining gum stub would otherwise fail.
|
||||
close_browser
|
||||
mkdir -p "$home/.config/google-chrome"
|
||||
ln -sfn "test-host-1234" "$home/.config/google-chrome/SingletonLock"
|
||||
write_stale_preferences
|
||||
run_migration || fail "migration repairs while a different profile root is open"
|
||||
jq -e --arg pinned "$pinned_id" '.extensions.commands["linux:Alt+Shift+L"].extension == $pinned' "$preferences" >/dev/null ||
|
||||
fail "migration repairs the shortcut while a different profile root is open"
|
||||
pass "migration ignores a browser on a different profile root"
|
||||
rm -f "$home/.config/google-chrome/SingletonLock" "$preferences.omarchy-copy-url-repair.bak"
|
||||
|
||||
# Closing the affected profile and confirming the prompt lets the repair
|
||||
# proceed.
|
||||
write_stale_preferences
|
||||
open_browser
|
||||
cat >"$stub_bin/gum" <<'STUB'
|
||||
#!/bin/bash
|
||||
"$CLOSE_BROWSER"
|
||||
touch "${GUM_CALLED:?}"
|
||||
exit 0
|
||||
STUB
|
||||
cat >"$stub_bin/pgrep" <<'STUB'
|
||||
cat >"$stub_bin/close-browser" <<'STUB'
|
||||
#!/bin/bash
|
||||
count_file="${PGREP_COUNT_FILE:?}"
|
||||
count=$(( $(cat "$count_file" 2>/dev/null || echo 0) + 1 ))
|
||||
printf '%s\n' "$count" >"$count_file"
|
||||
(( count == 1 )) && exit 0 || exit 1
|
||||
rm -f "$HOME/.config/chromium/SingletonLock"
|
||||
STUB
|
||||
rm -f "$test_dir/pgrep-count"
|
||||
GUM_CALLED="$test_dir/gum-called" PGREP_COUNT_FILE="$test_dir/pgrep-count" \
|
||||
chmod +x "$stub_bin/gum" "$stub_bin/close-browser"
|
||||
GUM_CALLED="$test_dir/gum-called" CLOSE_BROWSER="$stub_bin/close-browser" \
|
||||
HOME="$home" PATH="$stub_bin:$PATH" bash -euo pipefail "$migration" >/dev/null 2>&1 ||
|
||||
fail "migration proceeds once the browser prompt is confirmed"
|
||||
fail "migration proceeds once the profile is closed and the prompt confirmed"
|
||||
[[ -e $test_dir/gum-called ]] || fail "migration asks before repairing under a running browser"
|
||||
jq -e --arg pinned "$pinned_id" '.extensions.commands["linux:Alt+Shift+L"].extension == $pinned' "$preferences" >/dev/null ||
|
||||
fail "migration repairs after the browser prompt is confirmed"
|
||||
pass "migration asks to close the browser and repairs on confirmation"
|
||||
rm -f "$preferences.omarchy-copy-url-repair.bak"
|
||||
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/gum"
|
||||
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/pgrep"
|
||||
write_stale_preferences
|
||||
|
||||
# With browsers closed the ghost registration moves to the pinned id.
|
||||
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/pgrep"
|
||||
# With the affected profile closed the ghost registration moves to the pinned id.
|
||||
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/gum"
|
||||
close_browser
|
||||
write_stale_preferences
|
||||
run_migration || fail "migration repairs the shortcut when no browser is running"
|
||||
|
||||
jq -e --arg ghost "$ghost_id" --arg pinned "$pinned_id" '
|
||||
@@ -99,15 +128,15 @@ jq -e --arg ghost "$ghost_id" --arg pinned "$pinned_id" '
|
||||
pass "migration rebinds the Copy URL shortcut to the pinned extension id"
|
||||
|
||||
# A repaired profile has no ghost registration left, so nothing is pending —
|
||||
# even while a browser is running.
|
||||
# even while that same profile is open.
|
||||
rm "$preferences.omarchy-copy-url-repair.bak"
|
||||
repaired_hash=$(sha256sum "$preferences" | cut -d' ' -f1)
|
||||
printf '#!/bin/bash\nexit 0\n' >"$stub_bin/pgrep"
|
||||
open_browser
|
||||
run_migration || fail "migration reruns cleanly after the repair"
|
||||
[[ $(sha256sum "$preferences" | cut -d' ' -f1) == "$repaired_hash" && ! -e $preferences.omarchy-copy-url-repair.bak ]] ||
|
||||
fail "migration is idempotent after the repair"
|
||||
pass "migration is idempotent after the repair"
|
||||
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/pgrep"
|
||||
close_browser
|
||||
|
||||
# A remapped shortcut keeps the user's chosen key while moving to the pinned id.
|
||||
jq -n --arg ghost "$ghost_id" '{extensions: {commands: {"linux:Ctrl+Alt+P": {command_name: "copy-url", extension: $ghost, global: false}}, settings: {}}}' >"$preferences"
|
||||
@@ -127,60 +156,64 @@ jq -e --arg pinned "$pinned_id" '
|
||||
pass "migration never double-binds the pinned extension"
|
||||
|
||||
# A browser starting mid-repair may write stale Preferences back on exit, so
|
||||
# the migration must stay pending for a later browser-free run to verify.
|
||||
# the migration must stay pending for a later browser-free run to verify. A
|
||||
# stub hands the repair call through and opens the profile right after it.
|
||||
write_stale_preferences
|
||||
cat >"$stub_bin/pgrep" <<'STUB'
|
||||
close_browser
|
||||
rm -f "$preferences.omarchy-copy-url-repair.bak"
|
||||
cat >"$stub_bin/python3" <<'STUB'
|
||||
#!/bin/bash
|
||||
count_file="${PGREP_COUNT_FILE:?}"
|
||||
count=$(( $(cat "$count_file" 2>/dev/null || echo 0) + 1 ))
|
||||
printf '%s\n' "$count" >"$count_file"
|
||||
(( count >= 2 )) && exit 0 || exit 1
|
||||
# Called as `python3 -c <script> <preferences> <pinned_id> <check|repair>`, and
|
||||
# the check calls report a surviving ghost through their exit status.
|
||||
"${REAL_PYTHON}" "$@"
|
||||
status=$?
|
||||
[[ ${5:-} == "repair" ]] && ln -sfn "test-host-1234" "$HOME/.config/chromium/SingletonLock"
|
||||
exit $status
|
||||
STUB
|
||||
rm -f "$test_dir/pgrep-count"
|
||||
if PGREP_COUNT_FILE="$test_dir/pgrep-count" HOME="$home" PATH="$stub_bin:$PATH" \
|
||||
bash -euo pipefail "$migration" >/dev/null 2>&1; then
|
||||
chmod +x "$stub_bin/python3"
|
||||
if HOME="$home" PATH="$stub_bin:$PATH" bash -euo pipefail "$migration" >/dev/null 2>&1; then
|
||||
fail "migration stays pending when a browser starts mid-repair"
|
||||
fi
|
||||
jq -e --arg pinned "$pinned_id" '.extensions.commands["linux:Alt+Shift+L"].extension == $pinned' "$preferences" >/dev/null ||
|
||||
fail "migration still repairs preferences before deferring on a late browser"
|
||||
pass "migration stays pending when a browser starts mid-repair"
|
||||
rm -f "$preferences.omarchy-copy-url-repair.bak"
|
||||
rm -f "$stub_bin/python3" "$preferences.omarchy-copy-url-repair.bak"
|
||||
close_browser
|
||||
|
||||
# A browser that started and exited mid-repair restores stale Preferences
|
||||
# before the final process check; the post-repair file verification catches it.
|
||||
# before the final profile check; the post-repair file verification catches it.
|
||||
write_stale_preferences
|
||||
cp "$preferences" "$test_dir/stale-preferences"
|
||||
cat >"$stub_bin/pgrep" <<'STUB'
|
||||
cat >"$stub_bin/python3" <<'STUB'
|
||||
#!/bin/bash
|
||||
count_file="${PGREP_COUNT_FILE:?}"
|
||||
count=$(( $(cat "$count_file" 2>/dev/null || echo 0) + 1 ))
|
||||
printf '%s\n' "$count" >"$count_file"
|
||||
(( count == 2 )) && cp "$STALE_PREFERENCES" "$REPAIRED_PREFERENCES"
|
||||
exit 1
|
||||
"${REAL_PYTHON}" "$@"
|
||||
status=$?
|
||||
[[ ${5:-} == "repair" ]] && cp "${STALE_PREFERENCES:?}" "${REPAIRED_PREFERENCES:?}"
|
||||
exit $status
|
||||
STUB
|
||||
rm -f "$test_dir/pgrep-count"
|
||||
if PGREP_COUNT_FILE="$test_dir/pgrep-count" STALE_PREFERENCES="$test_dir/stale-preferences" \
|
||||
REPAIRED_PREFERENCES="$preferences" HOME="$home" PATH="$stub_bin:$PATH" \
|
||||
bash -euo pipefail "$migration" >/dev/null 2>&1; then
|
||||
chmod +x "$stub_bin/python3"
|
||||
if HOME="$home" PATH="$stub_bin:$PATH" STALE_PREFERENCES="$test_dir/stale-preferences" \
|
||||
REPAIRED_PREFERENCES="$preferences" bash -euo pipefail "$migration" >/dev/null 2>&1; then
|
||||
fail "migration stays pending when a briefly-lived browser undoes the repair"
|
||||
fi
|
||||
pass "migration stays pending when a briefly-lived browser undoes the repair"
|
||||
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/pgrep"
|
||||
rm -f "$stub_bin/python3"
|
||||
close_browser
|
||||
write_stale_preferences
|
||||
run_migration || fail "migration recovers after a reverted repair"
|
||||
rm -f "$preferences.omarchy-copy-url-repair.bak"
|
||||
|
||||
# A repair attempted while a browser was open leaves its backup behind. A
|
||||
# rerun that sees a clean disk while that browser still runs must stay
|
||||
# pending — the browser can restore the ghost on exit — and only a
|
||||
# A repair attempted while the affected profile was open leaves its backup
|
||||
# behind. A rerun that sees a clean disk while that profile still runs must
|
||||
# stay pending — the browser can restore the ghost on exit — and only a
|
||||
# browser-free rerun verifies the repair and completes.
|
||||
write_stale_preferences
|
||||
run_migration || fail "repair run before the verification scenario"
|
||||
[[ -f $preferences.omarchy-copy-url-repair.bak ]] || fail "verification scenario has a repair backup"
|
||||
printf '#!/bin/bash\nexit 0\n' >"$stub_bin/pgrep"
|
||||
open_browser
|
||||
run_migration && fail "migration must not complete an unverified repair while a browser runs"
|
||||
pass "migration keeps an unverified repair pending while a browser runs"
|
||||
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/pgrep"
|
||||
close_browser
|
||||
run_migration || fail "migration completes once the repair is verified with browsers closed"
|
||||
pass "migration verifies an attempted repair on a browser-free rerun"
|
||||
rm -f "$preferences.omarchy-copy-url-repair.bak"
|
||||
|
||||
Reference in New Issue
Block a user