Files
punktfunk/scripts/pf-dm-helper
T
enricobuehler f3615f83a5 fix(gamescope): the takeover no longer kills the host it is running in
Field report, Nobara + 0.20.0: switching into Game Mode mid-stream
disconnects the client and the box never lights up again. His journal has
the whole mechanism, ten seconds apart:

  12:34:18.9  freed Steam: stopped the display manager for this stream
  12:34:29.0  systemd[1685]: Stopping punktfunk-host.service

Stopping the display manager ends the user's last login session. The
packaged host is a `systemd --user` unit, so logind then stops
user@1000.service once UserStopDelaySec elapses — 10s by default on Nobara
— and takes the host with it. The stream dies, the scheduled restore never
runs, and the display manager stays down with nothing left to restart it:
a black box that needs a VT to recover. Exactly the two symptoms reported,
and the intermittency is just whether an active Gaming Mode session was
there to trigger the takeover at all.

It never showed on the repro VM because lingering was enabled there for the
earlier sessionless tests (`Linger=yes` — confirmed on the VM today), which
is precisely the thing that breaks the dependency: logind keeps the user
manager alive with no session. Our KDE/GNOME/Arch setup guides already ask
for it; his box, following the KDE route, did not have it.

So the takeover now ensures lingering BEFORE it touches the display
manager: `loginctl enable-linger` directly (allow_active in logind's own
policy — verified working unprivileged on Nobara), else the packaged helper
gains a `linger` verb that enables it for PKEXEC_UID alone, never a
caller-named user. If neither works the takeover is refused with an
actionable error and managed degrades to attach — mirroring the box's own
session is strictly better than a screen that needs a VT. Hosts that cannot
be reached this way (root, a system unit) skip the check: the cgroup test
is a pure `cgroup_under_user_manager` with a unit test, matched against the
real shapes on a Nobara box (`user@1000.service/app.slice/...` vs a
`session-N.scope`).

Also documents the requirement in the gamescope guide's Nobara section.

Checked on Linux: clippy -D warnings, 78 tests, rustfmt; helper verified
with `sh -n`, and the linger enable/disable round-trip run live on the
Nobara VM.
2026-07-27 19:04:20 +02:00

56 lines
2.3 KiB
Bash

#!/bin/sh
# Privileged display-manager verbs for the punktfunk managed gamescope takeover.
#
# On DM-autologin boxes whose display manager does not survive a masked session unit (Nobara's
# plasmalogin, unknown DMs), taking the Gaming Mode session over means stopping the display
# manager for the length of the stream and restarting it afterwards — root-only operations. The
# host invokes this helper via pkexec under its own polkit action
# (io.unom.punktfunk.dm-helper.policy, installed by the packages), the same mechanism these
# distros use for their own session switching (Nobara's os-session-select).
#
# The unit is NEVER caller-controlled: it is derived here from the display-manager.service alias
# symlink, so the polkit grant's blast radius is exactly "the box's own display manager" — a
# local-seat operation, not arbitrary unit management.
set -eu
dm_unit() {
target=$(readlink /etc/systemd/system/display-manager.service) || {
echo "pf-dm-helper: no display-manager.service alias — no display manager to manage" >&2
exit 1
}
basename "$target"
}
case "${1-}" in
stop)
exec systemctl stop "$(dm_unit)"
;;
restore)
dm=$(dm_unit)
# reset-failed first: a relogin loop may have tripped the unit's start limit, and a plain
# restart would be refused until the accounting is cleared.
systemctl reset-failed "$dm" 2>/dev/null || :
exec systemctl restart "$dm"
;;
linger)
# Keep the caller's own `systemd --user` manager alive across the stop. Stopping the display
# manager ends the user's last login session, and logind then stops user@<uid>.service after
# UserStopDelaySec (10s by default) — which takes the host that asked for the takeover with
# it, so nothing is left to restart the display manager and the box stays dark. Lingering is
# what breaks that dependency (the setup docs already ask for it).
#
# The user is NEVER caller-named: PKEXEC_UID is set by pkexec from the authenticated caller,
# so this grant enables lingering for that caller alone.
uid=${PKEXEC_UID:-}
[ -n "$uid" ] || {
echo "pf-dm-helper: no PKEXEC_UID in the environment — refusing to guess a user" >&2
exit 1
}
exec loginctl enable-linger "$uid"
;;
*)
echo "usage: pf-dm-helper stop|restore|linger" >&2
exit 2
;;
esac