From 19deac75fea8833e29e1dc49d4c9709523938d0e Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Mon, 27 Jul 2026 23:23:31 +0200 Subject: [PATCH] feat(packaging): a desktop-login host restarts with its desktop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shipped unit's PartOf=punktfunk-kde-session.service covers the APPLIANCE route, where we start the compositor ourselves. A host on a machine somebody logs into has no such unit: when Plasma or GNOME restarts, the daemon keeps running while holding a Wayland socket and a portal D-Bus connection that both died with the old compositor, and it cannot recover either in-process. It fails quietly — the host still listens, still answers, and every session it then serves dies at capture. A host that mirrors a monitor idles for days between sessions, which is the shape that finds this at the worst moment. The drop-in binds the host to graphical-session.target: PartOf takes it down with the session, WantedBy brings it back with the new one. Shipped under /usr/share rather than as an active drop-in, because it is wrong for the appliance route (which may never reach that target at all, and would then leave the host permanently stopped) — the operator opts in. Closes the restart half of design/per-monitor-portal-capture.md §6. Co-Authored-By: Claude Opus 5 (1M context) --- packaging/arch/PKGBUILD | 6 +++ packaging/debian/build-deb.sh | 6 +++ packaging/rpm/punktfunk.spec | 5 +++ scripts/punktfunk-host-desktop-session.conf | 42 +++++++++++++++++++++ scripts/punktfunk-host.service | 12 ++++++ 5 files changed, 71 insertions(+) create mode 100644 scripts/punktfunk-host-desktop-session.conf diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index 941ab092..397e857a 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -161,6 +161,12 @@ package_punktfunk-host() { install -Dm0644 "$R/scripts/punktfunk-host.service" "$pkgdir/usr/lib/systemd/user/punktfunk-host.service" sed -i 's#%h/punktfunk/target/release/punktfunk-host#/usr/bin/punktfunk-host#' \ "$pkgdir/usr/lib/systemd/user/punktfunk-host.service" + # Optional drop-in for a DESKTOP-LOGIN host: binds the host to graphical-session.target so a + # Plasma/GNOME restart restarts it instead of leaving it on a dead compositor connection. Shipped + # under /usr/share (NOT as an active drop-in) because it is wrong for the appliance route — the + # operator copies it into ~/.config/systemd/user/punktfunk-host.service.d/ when they want it. + install -Dm0644 "$R/scripts/punktfunk-host-desktop-session.conf" \ + "$pkgdir/usr/share/punktfunk/punktfunk-host-desktop-session.conf" install -Dm0644 "$R/scripts/punktfunk-kde-session.service" "$pkgdir/usr/lib/systemd/user/punktfunk-kde-session.service" sed -i 's#%h/punktfunk/scripts/headless/run-headless-kde.sh#/usr/share/punktfunk/headless/run-headless-kde.sh#' \ "$pkgdir/usr/lib/systemd/user/punktfunk-kde-session.service" diff --git a/packaging/debian/build-deb.sh b/packaging/debian/build-deb.sh index 7d66222f..865fb65b 100755 --- a/packaging/debian/build-deb.sh +++ b/packaging/debian/build-deb.sh @@ -67,6 +67,12 @@ install -Dm0644 scripts/punktfunk-host.service "$STAGE/usr/lib/systemd/user/ # /usr/bin. Rewrite it so a fresh apt install (no hand-rolled unit) starts the installed binary. sed -i 's#%h/punktfunk/target/release/punktfunk-host#/usr/bin/punktfunk-host#' \ "$STAGE/usr/lib/systemd/user/punktfunk-host.service" +# Optional drop-in for a DESKTOP-LOGIN host: binds the host to graphical-session.target so a +# Plasma/GNOME restart restarts it instead of leaving it on a dead compositor connection. Shipped +# under /usr/share (NOT as an active drop-in) because it is wrong for the appliance route — the +# operator copies it into ~/.config/systemd/user/punktfunk-host.service.d/ when they want it. +install -Dm0644 scripts/punktfunk-host-desktop-session.conf \ + "$STAGE/usr/share/punktfunk-host/punktfunk-host-desktop-session.conf" # Optional headless KWin session unit (the kwin --virtual appliance), as the RPM/Arch ship. # Repoint its ExecStart from the dev source tree to the packaged script. NOT enabled by default. install -Dm0644 scripts/punktfunk-kde-session.service "$STAGE/usr/lib/systemd/user/punktfunk-kde-session.service" diff --git a/packaging/rpm/punktfunk.spec b/packaging/rpm/punktfunk.spec index 140ebbf8..8d3a0eb9 100644 --- a/packaging/rpm/punktfunk.spec +++ b/packaging/rpm/punktfunk.spec @@ -284,6 +284,11 @@ install -Dm0644 scripts/punktfunk-host.service %{buildroot}%{_userunitdir}/punkt # The source unit's ExecStart points at the dev source tree; a packaged install has the binary at # %{_bindir}. Rewrite it so a fresh install (no hand-rolled unit) starts the installed binary. sed -i 's#%h/punktfunk/target/release/punktfunk-host#%{_bindir}/punktfunk-host#' %{buildroot}%{_userunitdir}/punktfunk-host.service +# Optional drop-in for a DESKTOP-LOGIN host: binds the host to graphical-session.target so a +# Plasma/GNOME restart restarts it instead of leaving it on a dead compositor connection. Shipped +# under %{_datadir}/%{name} (NOT as an active drop-in) because it is wrong for the appliance route — +# the operator copies it into ~/.config/systemd/user/punktfunk-host.service.d/ when they want it. +install -Dm0644 scripts/punktfunk-host-desktop-session.conf %{buildroot}%{_datadir}/%{name}/punktfunk-host-desktop-session.conf # Optional headless KDE session unit (the kwin streaming appliance): brings up `kwin --virtual` on # wayland-kde via the packaged run-headless-kde.sh, so the host's kwin backend has a session whose diff --git a/scripts/punktfunk-host-desktop-session.conf b/scripts/punktfunk-host-desktop-session.conf new file mode 100644 index 00000000..7b456b19 --- /dev/null +++ b/scripts/punktfunk-host-desktop-session.conf @@ -0,0 +1,42 @@ +# systemd USER drop-in: bind the host to the DESKTOP LOGIN session's lifetime. +# +# Install (on top of punktfunk-host.service): +# mkdir -p ~/.config/systemd/user/punktfunk-host.service.d +# cp scripts/punktfunk-host-desktop-session.conf \ +# ~/.config/systemd/user/punktfunk-host.service.d/desktop-session.conf +# systemctl --user daemon-reload +# systemctl --user reenable punktfunk-host # picks up the [Install] below +# systemctl --user restart punktfunk-host +# +# WHY: the shipped unit's `PartOf=punktfunk-kde-session.service` covers the APPLIANCE route, where we +# start the compositor ourselves. A host on a machine somebody logs into has no such unit — so when +# Plasma or GNOME restarts (a crash, a session logout/login, "restart the shell"), the daemon keeps +# running while holding a Wayland socket and a portal D-Bus connection that both died with the old +# compositor. It cannot recover those in-process. It is a quiet failure — the host still listens, still +# answers, and every session it then serves fails at capture. A host that mirrors a monitor +# (design/per-monitor-portal-capture.md) sits idle for days between sessions, which is exactly the +# shape that gets discovered at the worst moment. +# +# `graphical-session.target` is the session's own lifetime, reached by GNOME and Plasma under systemd +# session management. `PartOf=` stops us when it goes down; the `WantedBy=` below starts us again when +# the new session brings it back up. So a compositor restart becomes a host restart, with a fresh +# connection to the compositor that is actually there. +# +# NOT for the appliance/headless route (a pinned PUNKTFUNK_COMPOSITOR, punktfunk-kde-session.service, +# a gamescope box): those have their own session unit and may never reach graphical-session.target at +# all, and this drop-in would leave the host permanently stopped. +# +# Sway / Hyprland / any session NOT managed by systemd never reaches graphical-session.target either. +# There the drop-in is harmless but does nothing; start the host from the compositor's own config +# instead (`exec systemctl --user start punktfunk-host` after `systemctl --user import-environment`), +# so it dies and comes back with the session. + +[Unit] +After=graphical-session.target +PartOf=graphical-session.target + +[Install] +# In ADDITION to the base unit's WantedBy=default.target, so the host still comes up at login even +# before the graphical session is ready — it listens without touching the compositor and only opens +# one per client connect, so an early start costs nothing. +WantedBy=graphical-session.target diff --git a/scripts/punktfunk-host.service b/scripts/punktfunk-host.service index d1148b9d..bd1a5c16 100644 --- a/scripts/punktfunk-host.service +++ b/scripts/punktfunk-host.service @@ -8,6 +8,18 @@ # cp scripts/host.env.example ~/.config/punktfunk/host.env # defaults are right for a desktop # systemctl --user daemon-reload && systemctl --user enable --now punktfunk-host # +# On that desktop-login route, ALSO install scripts/punktfunk-host-desktop-session.conf as a drop-in: +# it binds the host to graphical-session.target, so a Plasma/GNOME restart restarts the host instead +# of leaving it holding a dead Wayland socket + portal connection (the PartOf= below only covers the +# appliance route, where we start the compositor ourselves). The drop-in's header has the commands. +# +# The host does NOT need the login shell's environment: it detects the live session itself per +# connect and derives WAYLAND_DISPLAY, XDG_RUNTIME_DIR, DBUS_SESSION_BUS_ADDRESS, XDG_CURRENT_DESKTOP, +# HYPRLAND_INSTANCE_SIGNATURE and SWAYSOCK from the running compositor (vdisplay::apply_session_env). +# So host.env carries policy, not session plumbing, and `systemctl --user import-environment` is not +# a prerequisite — the host pushes the live values into the manager env itself when a session switch +# needs the portal to re-read them. +# # Self-contained boot appliance (no login, no manual steps after boot). These routes PIN the # backend via PUNKTFUNK_COMPOSITOR — correct for a dedicated single-session box, but it turns off # live-session auto-detection, so never do it on a desktop that switches sessions (Game Mode etc.):