aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorLibravatar sommerfeld <sommerfeld@sommerfeld.dev>2026-05-29 11:18:13 +0100
committerLibravatar sommerfeld <sommerfeld@sommerfeld.dev>2026-05-29 11:18:13 +0100
commit01df321e907b6c8568bb8622eb44a5c1486a0631 (patch)
treebb9d76bf9bddd982fc079e045414e138154fa0ef
parent263f39704ae5e8f44a79d64dce2be048009b4df6 (diff)
downloaddotfiles-01df321e907b6c8568bb8622eb44a5c1486a0631.tar.gz
dotfiles-01df321e907b6c8568bb8622eb44a5c1486a0631.tar.bz2
dotfiles-01df321e907b6c8568bb8622eb44a5c1486a0631.zip
feat(suspend): hold inhibit lock while any zellij session exists
The SSH-shell inhibitor in dot_zprofile is bound to the lifetime of the login shell, so it disappears the moment the user detaches a zellij session and disconnects — defeating the whole point of using zellij for persistent remote work. Add a user-scope path+service+watcher trio that ties the inhibit lock to the existence of zellij sessions instead: - dot_local/bin/executable_zellij-inhibit-watcher Polls `zellij list-sessions --short` every 15s, exits when none remain. Override poll interval via $ZELLIJ_INHIBIT_POLL. - dot_config/systemd/user/zellij-inhibit-suspend.service Wraps the watcher in `systemd-inhibit --what=sleep:idle:handle-lid-switch --mode=block`. When the watcher exits, the service stops and the lock is released. - dot_config/systemd/user/zellij-inhibit-suspend.path Activates the service whenever $XDG_RUNTIME_DIR/zellij becomes non-empty (i.e. zellij creates its first session socket). Re-fires on every empty→non-empty transition. Enable via systemd-units/user.txt (the .path unit; the service is on-demand). The existing SSH-shell inhibitor is kept as a backstop for non-zellij remote sessions and is now documented as such. VM (nix/vm.nix) deliberately not updated: the Ubuntu remote-dev VM never suspends, so the inhibit machinery would be inert there.
-rw-r--r--dot_config/systemd/user/zellij-inhibit-suspend.path12
-rw-r--r--dot_config/systemd/user/zellij-inhibit-suspend.service19
-rw-r--r--dot_config/zsh/dot_zprofile13
-rwxr-xr-xdot_local/bin/executable_zellij-inhibit-watcher20
-rw-r--r--systemd-units/user.txt6
5 files changed, 65 insertions, 5 deletions
diff --git a/dot_config/systemd/user/zellij-inhibit-suspend.path b/dot_config/systemd/user/zellij-inhibit-suspend.path
new file mode 100644
index 0000000..72c509c
--- /dev/null
+++ b/dot_config/systemd/user/zellij-inhibit-suspend.path
@@ -0,0 +1,12 @@
+[Unit]
+Description=Activate suspend inhibitor whenever zellij has a live session
+
+[Path]
+# %t expands to $XDG_RUNTIME_DIR (typically /run/user/$UID); zellij keeps
+# its per-version session sockets under this directory. Whenever the dir
+# transitions from empty to non-empty, the service is (re)activated.
+DirectoryNotEmpty=%t/zellij
+Unit=zellij-inhibit-suspend.service
+
+[Install]
+WantedBy=default.target
diff --git a/dot_config/systemd/user/zellij-inhibit-suspend.service b/dot_config/systemd/user/zellij-inhibit-suspend.service
new file mode 100644
index 0000000..9e9ab40
--- /dev/null
+++ b/dot_config/systemd/user/zellij-inhibit-suspend.service
@@ -0,0 +1,19 @@
+[Unit]
+Description=Hold a systemd-inhibit lock while zellij sessions exist
+Documentation=man:systemd-inhibit(1) man:zellij(1)
+# Independent of any graphical session: this is meant to run on
+# headless SSH-attached hosts too.
+
+[Service]
+Type=simple
+ExecStart=systemd-inhibit \
+ --what=sleep:idle:handle-lid-switch \
+ --who=zellij \
+ --why=Active\x20zellij\x20sessions \
+ --mode=block \
+ %h/.local/bin/zellij-inhibit-watcher
+# Don't auto-restart: the .path unit reactivates us on the next session.
+Restart=no
+
+[Install]
+WantedBy=default.target
diff --git a/dot_config/zsh/dot_zprofile b/dot_config/zsh/dot_zprofile
index 02991cb..a26209b 100644
--- a/dot_config/zsh/dot_zprofile
+++ b/dot_config/zsh/dot_zprofile
@@ -151,11 +151,14 @@ case $(uname -n) in
esac
# ── SSH: inhibit suspend/idle while connected ────────────────────────────────
-# A remote session is useless if the laptop suspends mid-command. Wrap the
-# login shell in `systemd-inhibit` so a lock is held for the entire SSH
-# session lifetime (lid close, idle timeout, swayidle, lid-switch handlers
-# all honour it). The lock is released the instant the shell exits, so a
-# clean disconnect restores normal power behaviour.
+# Backstop for SSH sessions that don't use zellij. (The dedicated
+# zellij-inhibit-suspend.path user unit already covers any host that has
+# at least one live zellij session — that one survives detach/disconnect,
+# which this in-shell inhibitor does not.)
+#
+# Wrap the login shell in `systemd-inhibit` so a lock is held for the
+# entire SSH session lifetime; the lock is released the instant the
+# shell exits.
if [[ -n $SSH_CONNECTION && -z $__SSH_SUSPEND_INHIBITED ]] \
&& command -v systemd-inhibit >/dev/null 2>&1; then
export __SSH_SUSPEND_INHIBITED=1
diff --git a/dot_local/bin/executable_zellij-inhibit-watcher b/dot_local/bin/executable_zellij-inhibit-watcher
new file mode 100755
index 0000000..0af20dd
--- /dev/null
+++ b/dot_local/bin/executable_zellij-inhibit-watcher
@@ -0,0 +1,20 @@
+#!/bin/sh
+# Block until no zellij sessions remain.
+#
+# Used as the ExecStart payload of zellij-inhibit-suspend.service: the
+# service wraps this script with `systemd-inhibit`, so the inhibit lock
+# is held for exactly the lifetime of this process. When the last zellij
+# session ends, this script exits 0, the service stops, and the lock is
+# released.
+#
+# A user-level `.path` unit re-activates the service whenever the zellij
+# socket directory becomes non-empty again, so the lock is automatically
+# reacquired on the next `zellij` invocation.
+set -eu
+
+poll=${ZELLIJ_INHIBIT_POLL:-15}
+
+while sessions=$(zellij list-sessions --short 2>/dev/null) &&
+ [ -n "$sessions" ]; do
+ sleep "$poll"
+done
diff --git a/systemd-units/user.txt b/systemd-units/user.txt
index 009b2b3..77ba788 100644
--- a/systemd-units/user.txt
+++ b/systemd-units/user.txt
@@ -18,3 +18,9 @@ wob.service
# --- mail (overridden via drop-ins in dot_config/systemd/user/) ---
protonmail-bridge.service
+
+# --- suspend inhibitor: keep host awake while any zellij session exists ---
+# Enable the .path unit; it activates the service on-demand when zellij's
+# socket dir becomes non-empty. The service wraps a poller in
+# systemd-inhibit and exits when no sessions remain, releasing the lock.
+zellij-inhibit-suspend.path