diff --git a/bin/omarchy-upgrade-to-quattro b/bin/omarchy-upgrade-to-quattro index b431ef34..00083d33 100755 --- a/bin/omarchy-upgrade-to-quattro +++ b/bin/omarchy-upgrade-to-quattro @@ -1432,6 +1432,7 @@ retire systemd/user/bt-agent.service 0406b577a1225dc2a9f86638d3c346eb3635168576f retire systemd/user/omarchy-recover-internal-monitor.service b9b92cedc44cf3cb6216948629be55b53d16746e31896dc6469fd49ba55e82f4 retire systemd/user/omarchy-recover-internal-monitor.service e1483079b9f2aefcd43b4722c75a31643e5f3bb5a2eada5f52f1d7d201e8c289 retire systemd/user/omarchy-sleep-lock.service 6870b232a6c0474b59187882e6d25ae771bba735098bcbedef8a2b73b97e2b6a +retire systemd/user/omarchy-sleep-lock.service bcd1a76cb5c63514922bc5e11af22ae480fc6d06a99863364e02bdf3c7bdceaf retire uwsm/default 2e89b03a3710b70cf754ea0c8db4388bb35a7519870036abff5ea8a584eafbc0 retire uwsm/default 3622ba134d29b639a155424a5226215e213c8508144e98001ffe128a8967c9b5 retire uwsm/default 73712c5a677532bd4afcee5800ae9b76e3d02a2130659a96a8b7fbe2fd2c3d53 diff --git a/default/systemd/user/omarchy-sleep-lock.service b/default/systemd/user/omarchy-sleep-lock.service index 8bf696fe..72b17426 100644 --- a/default/systemd/user/omarchy-sleep-lock.service +++ b/default/systemd/user/omarchy-sleep-lock.service @@ -1,7 +1,13 @@ [Unit] Description=Lock Omarchy before suspend -After=dbus.socket +# The monitor calls into the running Omarchy shell. Wait until UWSM has imported +# OMARCHY_PATH and WAYLAND_DISPLAY, but keep the default target ordering so the +# monitor starts before graphical-session.target is reached. +After=dbus.socket wayland-session-waitenv.service Requires=dbus.socket +PartOf=graphical-session.target +ConditionEnvironment=OMARCHY_PATH +ConditionEnvironment=WAYLAND_DISPLAY [Service] Type=simple diff --git a/migrations/1785608166.sh b/migrations/1785608166.sh new file mode 100644 index 00000000..cdfc6e61 --- /dev/null +++ b/migrations/1785608166.sh @@ -0,0 +1,79 @@ +echo "Repair the pre-suspend lock monitor's graphical session environment" + +# The old unit started before UWSM finished importing OMARCHY_PATH and +# WAYLAND_DISPLAY. A full unit retained from before Omarchy 4 also shadows the +# corrected package unit, so add the lifecycle constraints as a drop-in without +# discarding any user customizations. +user_config_home="${XDG_CONFIG_HOME:-$HOME/.config}" +sleep_lock_unit="$user_config_home/systemd/user/omarchy-sleep-lock.service" +sleep_lock_dropin_dir="$user_config_home/systemd/user/omarchy-sleep-lock.service.d" +sleep_lock_dropin="$sleep_lock_dropin_dir/90-omarchy-session-environment.conf" + +if [[ -f $sleep_lock_unit ]]; then + mkdir -p "$sleep_lock_dropin_dir" + printf '%s\n' \ + '[Unit]' \ + 'After=dbus.socket wayland-session-waitenv.service' \ + 'Requires=dbus.socket' \ + 'PartOf=graphical-session.target' \ + 'ConditionEnvironment=OMARCHY_PATH' \ + 'ConditionEnvironment=WAYLAND_DISPLAY' >"$sleep_lock_dropin" +fi + +# With no live user manager there cannot be an inherited monitor to replace. +# The package unit (and compatibility drop-in, when needed) will be loaded by +# the fresh manager at the next login. +user_manager_socket="${XDG_RUNTIME_DIR:-/run/user/$UID}/systemd/private" +if ! error=$(systemctl --user show-environment 2>&1); then + if [[ -S $user_manager_socket ]]; then + echo "Could not reach the running user service manager: $error" + echo "The pre-suspend lock repair will be retried by omarchy-migrate." + exit 1 + fi + exit 0 +fi + +if ! error=$(systemctl --user daemon-reload 2>&1); then + echo "Could not reload the user service manager: $error" + echo "The pre-suspend lock repair will be retried by omarchy-migrate." + exit 1 +fi + +if ! graphical_state=$(systemctl --user show --property=ActiveState --value graphical-session.target 2>&1); then + echo "Could not inspect graphical-session.target: $graphical_state" + echo "The pre-suspend lock repair will be retried by omarchy-migrate." + exit 1 +fi + +if [[ $graphical_state == "active" ]]; then + if ! error=$(systemctl --user reset-failed omarchy-sleep-lock.service 2>&1); then + echo "Could not reset omarchy-sleep-lock.service: $error" + echo "The pre-suspend lock repair will be retried by omarchy-migrate." + exit 1 + elif ! error=$(systemctl --user restart omarchy-sleep-lock.service 2>&1); then + echo "Could not restart omarchy-sleep-lock.service: $error" + echo "The pre-suspend lock repair will be retried by omarchy-migrate." + exit 1 + elif ! sleep_lock_state=$(systemctl --user show --property=ActiveState --value omarchy-sleep-lock.service 2>&1); then + echo "Could not inspect omarchy-sleep-lock.service: $sleep_lock_state" + echo "The pre-suspend lock repair will be retried by omarchy-migrate." + exit 1 + elif [[ $sleep_lock_state != "active" ]]; then + echo "omarchy-sleep-lock.service did not stay active after restart." + echo "The pre-suspend lock repair will be retried by omarchy-migrate." + exit 1 + fi +else + # A pre-fix monitor was not tied to graphical-session.target and can outlive + # logout under a lingering user manager. Stop it so the next target start + # creates a process with the next graphical session's environment. + if ! error=$(systemctl --user stop omarchy-sleep-lock.service 2>&1); then + echo "Could not stop stale omarchy-sleep-lock.service: $error" + echo "The pre-suspend lock repair will be retried by omarchy-migrate." + exit 1 + elif ! error=$(systemctl --user reset-failed omarchy-sleep-lock.service 2>&1); then + echo "Could not reset omarchy-sleep-lock.service: $error" + echo "The pre-suspend lock repair will be retried by omarchy-migrate." + exit 1 + fi +fi diff --git a/test/shell.d/sleep-lock-environment-migration-test.sh b/test/shell.d/sleep-lock-environment-migration-test.sh new file mode 100755 index 00000000..e778c1c0 --- /dev/null +++ b/test/shell.d/sleep-lock-environment-migration-test.sh @@ -0,0 +1,162 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/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/systemctl" <<'STUB' +#!/bin/bash + +printf '%s\n' "$*" >>"$SYSTEMCTL_CALLS" + +case "$*" in + '--user show-environment') + [[ ${MANAGER_AVAILABLE:-true} == "true" ]] + ;; + '--user daemon-reload') + if [[ ${FAIL_SYSTEMCTL_ACTION:-} == "daemon-reload" ]]; then + echo "reload failed" >&2 + exit 1 + fi + ;; + '--user show --property=ActiveState --value graphical-session.target') + if [[ ${FAIL_SYSTEMCTL_ACTION:-} == "show-graphical" ]]; then + echo "state lookup failed" >&2 + exit 1 + fi + printf '%s\n' "${GRAPHICAL_STATE:-inactive}" + ;; + '--user show --property=ActiveState --value omarchy-sleep-lock.service') + printf '%s\n' "${SLEEP_LOCK_STATE:-inactive}" + ;; + '--user restart omarchy-sleep-lock.service') + if [[ ${FAIL_SYSTEMCTL_ACTION:-} == "restart" ]]; then + echo "restart failed" >&2 + exit 1 + fi + ;; + '--user stop omarchy-sleep-lock.service') + if [[ ${FAIL_SYSTEMCTL_ACTION:-} == "stop" ]]; then + echo "stop failed" >&2 + exit 1 + fi + ;; + '--user reset-failed omarchy-sleep-lock.service') ;; + *) exit 1 ;; +esac +STUB +chmod +x "$stub_bin/systemctl" + +migration="$ROOT/migrations/1785608166.sh" + +run_migration() { + local home="$1" calls="$2" + shift 2 + + mkdir -p "$home/run" + HOME="$home" XDG_CONFIG_HOME="$home/.config" XDG_RUNTIME_DIR="$home/run" \ + SYSTEMCTL_CALLS="$calls" PATH="$stub_bin:$PATH" \ + "$@" bash -euo pipefail "$migration" +} + +active_home="$test_tmp/active-home" +active_calls="$test_tmp/active-calls" +mkdir -p "$active_home/.config/systemd/user" +printf '%s\n' '[Service]' 'ExecStart=/usr/bin/omarchy-system-sleep-monitor' \ + >"$active_home/.config/systemd/user/omarchy-sleep-lock.service" + +run_migration "$active_home" "$active_calls" \ + env GRAPHICAL_STATE=active SLEEP_LOCK_STATE=active >/dev/null + +dropin="$active_home/.config/systemd/user/omarchy-sleep-lock.service.d/90-omarchy-session-environment.conf" +grep -Fx 'After=dbus.socket wayland-session-waitenv.service' "$dropin" >/dev/null || + fail "sleep lock migration orders a retained unit after the session environment import" +grep -Fx 'PartOf=graphical-session.target' "$dropin" >/dev/null || + fail "sleep lock migration ties a retained unit to the graphical session lifecycle" +grep -Fx 'ConditionEnvironment=OMARCHY_PATH' "$dropin" >/dev/null || + fail "sleep lock migration gates a retained unit on the Omarchy path" +grep -Fx 'ConditionEnvironment=WAYLAND_DISPLAY' "$dropin" >/dev/null || + fail "sleep lock migration gates a retained unit on the Wayland display" +pass "sleep lock migration repairs retained full-unit overrides with a drop-in" + +grep -Fx -- '--user daemon-reload' "$active_calls" >/dev/null || + fail "sleep lock migration reloads the live user manager" +grep -Fx -- '--user reset-failed omarchy-sleep-lock.service' "$active_calls" >/dev/null || + fail "sleep lock migration cannot recover a start-limited monitor" +grep -Fx -- '--user restart omarchy-sleep-lock.service' "$active_calls" >/dev/null || + fail "sleep lock migration leaves an active monitor with its inherited environment" +grep -Fx -- '--user stop omarchy-sleep-lock.service' "$active_calls" >/dev/null && + fail "sleep lock migration stops the monitor inside an active graphical session" +pass "sleep lock migration replaces the monitor inside an active graphical session" + +inactive_home="$test_tmp/inactive-home" +inactive_calls="$test_tmp/inactive-calls" +mkdir -p "$inactive_home" + +run_migration "$inactive_home" "$inactive_calls" \ + env GRAPHICAL_STATE=inactive >/dev/null + +grep -Fx -- '--user stop omarchy-sleep-lock.service' "$inactive_calls" >/dev/null || + fail "sleep lock migration leaves a stale monitor running after logout" +grep -Fx -- '--user restart omarchy-sleep-lock.service' "$inactive_calls" >/dev/null && + fail "sleep lock migration starts the monitor outside a graphical session" +pass "sleep lock migration stops a monitor left behind after logout" + +failed_calls="$test_tmp/failed-calls" +if run_migration "$test_tmp/failed-home" "$failed_calls" \ + env GRAPHICAL_STATE=active SLEEP_LOCK_STATE=active FAIL_SYSTEMCTL_ACTION=restart \ + >"$test_tmp/failed-output" 2>&1; then + fail "sleep lock migration marks a failed active-session repair complete" +fi +grep -F 'will be retried by omarchy-migrate' "$test_tmp/failed-output" >/dev/null || + fail "sleep lock migration does not explain that a failed repair remains pending" +pass "sleep lock migration keeps an active-session repair failure retryable" + +reload_failed_calls="$test_tmp/reload-failed-calls" +if run_migration "$test_tmp/reload-failed-home" "$reload_failed_calls" \ + env FAIL_SYSTEMCTL_ACTION=daemon-reload >"$test_tmp/reload-failed-output" 2>&1; then + fail "sleep lock migration ignores a failed user-manager reload" +fi +grep -F 'Could not reload the user service manager' "$test_tmp/reload-failed-output" >/dev/null || + fail "sleep lock migration does not report a failed user-manager reload" +pass "sleep lock migration keeps a failed user-manager reload retryable" + +stop_failed_calls="$test_tmp/stop-failed-calls" +if run_migration "$test_tmp/stop-failed-home" "$stop_failed_calls" \ + env GRAPHICAL_STATE=inactive FAIL_SYSTEMCTL_ACTION=stop \ + >"$test_tmp/stop-failed-output" 2>&1; then + fail "sleep lock migration ignores a stale monitor that could not be stopped" +fi +grep -F 'Could not stop stale omarchy-sleep-lock.service' "$test_tmp/stop-failed-output" >/dev/null || + fail "sleep lock migration does not report a stale monitor stop failure" +pass "sleep lock migration keeps a stale-monitor stop failure retryable" + +state_failed_calls="$test_tmp/state-failed-calls" +if run_migration "$test_tmp/state-failed-home" "$state_failed_calls" \ + env FAIL_SYSTEMCTL_ACTION=show-graphical >"$test_tmp/state-failed-output" 2>&1; then + fail "sleep lock migration ignores a failed session-state inspection" +fi +grep -F 'Could not inspect graphical-session.target' "$test_tmp/state-failed-output" >/dev/null || + fail "sleep lock migration does not report a failed session-state inspection" +pass "sleep lock migration keeps an indeterminate session repair retryable" + +deferred_home="$test_tmp/deferred-home" +deferred_calls="$test_tmp/deferred-calls" +mkdir -p "$deferred_home/.config/systemd/user" +touch "$deferred_home/.config/systemd/user/omarchy-sleep-lock.service" + +run_migration "$deferred_home" "$deferred_calls" \ + env MANAGER_AVAILABLE=false >/dev/null + +[[ -f $deferred_home/.config/systemd/user/omarchy-sleep-lock.service.d/90-omarchy-session-environment.conf ]] || + fail "sleep lock migration does not persist the repair without a live user manager" +deferred_call_count=$(wc -l <"$deferred_calls") +(( deferred_call_count == 1 )) || + fail "sleep lock migration tries to mutate a user manager that is not running" +pass "sleep lock migration defers safely when no user manager is running" diff --git a/test/shell.d/systemd-test.sh b/test/shell.d/systemd-test.sh index a232fd54..7d4f67c7 100755 --- a/test/shell.d/systemd-test.sh +++ b/test/shell.d/systemd-test.sh @@ -16,6 +16,16 @@ sleep_service="$ROOT/default/systemd/user/omarchy-sleep-lock.service" grep -Fx 'ExecStart=/usr/bin/omarchy-system-sleep-monitor' "$sleep_service" >/dev/null pass "sleep lock service uses the package-backed monitor path" +grep -Fx 'After=dbus.socket wayland-session-waitenv.service' "$sleep_service" >/dev/null || + fail "sleep lock monitor starts before UWSM imports the graphical session environment" +grep -Fx 'PartOf=graphical-session.target' "$sleep_service" >/dev/null || + fail "sleep lock monitor survives logout with a stale Wayland environment" +grep -Fx 'ConditionEnvironment=OMARCHY_PATH' "$sleep_service" >/dev/null || + fail "sleep lock monitor can start without the Omarchy shell path" +grep -Fx 'ConditionEnvironment=WAYLAND_DISPLAY' "$sleep_service" >/dev/null || + fail "sleep lock monitor can start without a Wayland display" +pass "sleep lock service follows the initialized graphical session" + first_run_units="$ROOT/install/user/first-run/enable-user-units.sh" grep -Fx 'systemctl --user daemon-reload' "$first_run_units" >/dev/null grep -F 'omarchy-sleep-lock.service' "$first_run_units" >/dev/null @@ -23,6 +33,7 @@ pass "first-run reloads and enables the sleep lock service" upgrade_to_quattro="$ROOT/bin/omarchy-upgrade-to-quattro" grep -F '6870b232a6c0474b59187882e6d25ae771bba735098bcbedef8a2b73b97e2b6a' "$upgrade_to_quattro" >/dev/null +grep -F 'bcd1a76cb5c63514922bc5e11af22ae480fc6d06a99863364e02bdf3c7bdceaf' "$upgrade_to_quattro" >/dev/null grep -F 'ExecStart=%h/.local/share/omarchy/bin/omarchy-system-sleep-monitor' "$upgrade_to_quattro" >/dev/null grep -F 'ExecStart=/usr/bin/omarchy-system-sleep-monitor' "$upgrade_to_quattro" >/dev/null grep -F 'reset-failed omarchy-sleep-lock.service' "$upgrade_to_quattro" >/dev/null