Files
omarchy/bin/omarchy-restart-shell
T
Afonso OliveiraandClaude Fable 5.1 43b91163f6 Install cleanup traps before the entry revocation and bound the bus probe
The protected entrypoints revoked the sudo timestamp before installing
their cleanup traps, so a signal or failure during that first sudo -k
exited without the cleanup path. Install the traps first.

The shell restart probed the notification bus name with busctl's
default 25 second timeout, so an unresponsive user bus could stall the
restart by that much per probe. Bound each probe to one second.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 20:33:59 +01:00

134 lines
5.7 KiB
Bash
Executable File

#!/bin/bash
# omarchy:summary=Restart the Omarchy shell
# omarchy:examples=omarchy restart shell
# A caller opened after dev link/unlink may disagree with the still-running
# desktop. The user manager receives Hyprland's environment at session start.
session_omarchy_path=$(systemctl --user show-environment 2>/dev/null | sed -n 's/^OMARCHY_PATH=//p' | tail -n 1)
: "${session_omarchy_path:=$OMARCHY_PATH}"
CONFIG_DIR="$session_omarchy_path/shell"
[[ -f $CONFIG_DIR/shell.qml ]] || { echo "Omarchy shell config not found: $CONFIG_DIR" >&2; exit 1; }
# Allow running from outside the session (e.g. over ssh) by deriving the
# Hyprland instance signature from the newest instance runtime dir.
if [[ -z ${HYPRLAND_INSTANCE_SIGNATURE:-} ]]; then
hypr_dir=$(find "${XDG_RUNTIME_DIR:-/run/user/$UID}/hypr" -mindepth 1 -maxdepth 1 -type d -printf '%T@ %p\n' 2>/dev/null | sort -n | tail -n 1 | cut -d' ' -f2-)
[[ -n $hypr_dir ]] && export HYPRLAND_INSTANCE_SIGNATURE=${hypr_dir##*/}
fi
# Restarting a live lock client would kill the lock screen and strand the
# session behind Hyprland's failsafe. But a LOCK session without an active
# locker — the shell died, or its crash handler re-execed a fresh instance
# that holds no lock — sits in that failsafe with no way to authenticate,
# and a restart plus re-lock is the only way back in without a reboot. So
# ask the lock service rather than merely pinging the shell: only a locker
# that reports the lock secure or in progress is worth preserving.
relock=0
if omarchy-hyprland-session-locked; then
locking=$(OMARCHY_PATH="$session_omarchy_path" OMARCHY_SHELL_IPC_TIMEOUT=0.5s omarchy-shell lock status 2>/dev/null |
jq -r '.secure or .requested' 2>/dev/null)
if [[ $locking == "true" ]]; then
echo "Refusing to restart Omarchy shell while the session is locked." >&2
exit 1
fi
relock=1
fi
# The lock plugin loads asynchronously, so a fresh shell answers ping before
# it can lock, and may even refuse early lock requests while its plugins or
# PAM config are still loading. Mirror omarchy-system-sleep-lock: request the
# lock and poll until the session reports secure, re-requesting as needed, so
# recovery never claims success while the failsafe is still up. The deadline
# is generous because slow plugin discovery delays the lock IPC target.
relock_session() {
local state deadline=$((SECONDS + 30))
while (( SECONDS < deadline )); do
state=$(OMARCHY_PATH="$session_omarchy_path" OMARCHY_SHELL_IPC_TIMEOUT=0.5s omarchy-shell lock status 2>/dev/null |
jq -r 'if .secure == true then "secure" elif .requested == true then "locking" else "idle" end' 2>/dev/null)
case $state in
secure) return 0 ;;
locking) ;;
*) OMARCHY_PATH="$session_omarchy_path" OMARCHY_SHELL_IPC_TIMEOUT=0.5s omarchy-shell lock lock >/dev/null 2>&1 ;;
esac
sleep 0.1
done
return 1
}
# A short timeout keeps an unresponsive user bus from stalling the restart;
# busctl would otherwise wait 25 seconds per probe.
notifications_ready() {
[[ $(busctl --user --timeout=1s call org.freedesktop.DBus /org/freedesktop/DBus \
org.freedesktop.DBus NameHasOwner s org.freedesktop.Notifications 2>/dev/null) == "b true" ]]
}
# Core IPC can answer before the notification plugin has registered its bus
# name. Restore an existing notification service before update hooks or setup
# invitations send their one-time toasts; a disabled service need not appear.
notifications_were_running=0
if notifications_ready; then
notifications_were_running=1
fi
# Each kill stops the oldest matching instance and only returns once it has
# fully exited, so the no-duplicate launch below can't race a dying shell.
while timeout 5 quickshell kill -p "$CONFIG_DIR" --any-display >/dev/null 2>&1; do :; done
# Spawn from Hyprland so the shell inherits the canonical session environment,
# not transient variables from a terminal, SSH connection, or development tool.
hyprctl dispatch 'hl.dsp.exec_cmd("omarchy-launch-shell")' >/dev/null
shell_ready=0
for (( attempt = 0; attempt < 20; attempt++ )); do
if OMARCHY_PATH="$session_omarchy_path" OMARCHY_SHELL_IPC_TIMEOUT=0.5s omarchy-shell shell ping >/dev/null 2>&1; then
shell_ready=1
break
fi
sleep 0.1
done
if (( shell_ready == 0 )); then
echo "Omarchy shell did not become ready after restart." >&2
exit 1
fi
# The session stays compositor-locked after the old lock client died, so
# re-acquire the lock and let the user authenticate out of it. This comes
# first and depends on nothing else: a user stranded behind the failsafe must
# not wait on the notification plugin, which may be slow or absent.
if (( relock )) && ! relock_session; then
echo "Omarchy shell restarted, but the session lock was not re-secured." >&2
exit 1
fi
# Core IPC answers before the notification plugin has registered its bus
# name, so wait for it separately before one-time toasts are sent.
if (( notifications_were_running )); then
notifications_restored=0
for (( attempt = 0; attempt < 20; attempt++ )); do
if notifications_ready; then
notifications_restored=1
break
fi
sleep 0.1
done
if (( notifications_restored == 0 )); then
echo "Omarchy shell restarted, but its notification service did not become ready." >&2
exit 1
fi
fi
# Invitation toasts (like Voxtype/fingerprint setup) die with the old
# shell, and their notify-send waiters hang forever: the dying server
# never emits NotificationClosed. A still-running omarchy-*-invitation
# unit is therefore an unanswered invitation — re-run it so its toast
# reappears on the new shell. Answered invitations have already exited
# and been collected, so the glob no longer matches them.
systemctl --user try-restart 'omarchy-*-invitation.service' 2>/dev/null || true
exit 0