feat(headless): boot-appliance systemd units (KDE session + host, no login)
ci / rust (push) Has been cancelled

Make a headless box a self-contained streaming appliance: after boot, with no
display manager / login / manual script, the headless KWin Plasma session and
the punktfunk host both come up so a client can just connect and stream the
desktop.

- New scripts/punktfunk-kde-session.service: a Type=simple user unit that runs
  run-headless-kde.sh (kwin --virtual on wayland-kde + Plasma + portals + a
  supervised plasmashell). The script foregrounds on `wait $KWIN_PID`, so
  Restart=always keeps the desktop alive across a KWin crash.
- scripts/punktfunk-host.service: ExecStart now `serve --native` (the unified
  GameStream + punktfunk/1 host, matching how it's actually run), After= the
  kde-session unit (soft ordering — the host listens immediately and only needs
  the compositor per session, so a missing unit on the gamescope backend is
  harmless), and appliance install docs (kwin vs gamescope backend).

Boot still requires `sudo loginctl enable-linger $USER` (the one thing that
starts user units without a login) — documented in both unit headers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 21:16:12 +00:00
parent 2557ce1ee5
commit 8c58afa2ac
2 changed files with 51 additions and 8 deletions
+18 -8
View File
@@ -1,19 +1,29 @@
# punktfunk streaming host — systemd USER unit. # punktfunk streaming host — systemd USER unit (`serve --native` = GameStream + punktfunk/1).
# #
# Install: # Install (against an already-running compositor session):
# mkdir -p ~/.config/systemd/user && cp scripts/punktfunk-host.service ~/.config/systemd/user/ # mkdir -p ~/.config/systemd/user && cp scripts/punktfunk-host.service ~/.config/systemd/user/
# cp scripts/host.env.example ~/.config/punktfunk/host.env # then edit # cp scripts/host.env.example ~/.config/punktfunk/host.env # then edit for your backend
# systemctl --user daemon-reload && systemctl --user enable --now punktfunk-host # systemctl --user daemon-reload && systemctl --user enable --now punktfunk-host
# #
# The unit assumes the compositor session (e.g. headless KWin on wayland-kde) is already up; # Self-contained boot appliance (no login, no manual steps after boot):
# for a fully self-contained appliance, pair it with a kwin_wayland user unit it can After=. # - kwin backend (stream the Plasma desktop): also install + enable
# punktfunk-kde-session.service (it brings up the headless KWin session this After=s), and set
# PUNKTFUNK_COMPOSITOR=kwin + WAYLAND_DISPLAY=wayland-kde in host.env.
# - gamescope backend (stream a nested app, no desktop): set PUNKTFUNK_COMPOSITOR=gamescope in
# host.env — the host spawns gamescope per session, so no kde-session unit is needed.
# Then `sudo loginctl enable-linger "$USER"` so user units start at boot, and reboot.
#
# The host LISTENS as soon as it starts and only touches the compositor per session (on a client
# connect), so the After= below is a soft ordering, not a hard readiness gate — the kde-session
# unit (when present) just needs to be up by the time a client streams (seconds later, user-driven).
# A missing After= unit (e.g. gamescope backend, no kde-session installed) is simply ignored.
[Unit] [Unit]
Description=punktfunk GameStream host Description=punktfunk GameStream + punktfunk/1 host
After=pipewire.service After=pipewire.service punktfunk-kde-session.service
[Service] [Service]
EnvironmentFile=%h/.config/punktfunk/host.env EnvironmentFile=%h/.config/punktfunk/host.env
ExecStart=%h/punktfunk/target/release/punktfunk-host serve ExecStart=%h/punktfunk/target/release/punktfunk-host serve --native
Restart=on-failure Restart=on-failure
RestartSec=2 RestartSec=2
+33
View File
@@ -0,0 +1,33 @@
# punktfunk headless KDE Plasma session — systemd USER unit.
#
# Brings up `kwin --virtual` on WAYLAND_DISPLAY=wayland-kde plus the full Plasma desktop
# (portals, polkit agent, a supervised plasmashell) via run-headless-kde.sh, so the punktfunk
# host's *kwin* backend can stream the live desktop. This is what makes a headless box a
# self-contained streaming appliance: no display manager, no login, no manual script — the
# session comes up at boot. Pair it with punktfunk-host.service (kwin backend) + linger.
#
# The script foregrounds on `wait $KWIN_PID` and re-spawns plasmashell for KWin's lifetime, so
# Type=simple + Restart=always keeps the desktop alive (a KWin crash brings the whole session
# back). It sets its own env (XDG_CURRENT_DESKTOP=KDE, WAYLAND_DISPLAY=wayland-kde,
# XDG_MENU_PREFIX=plasma-, …) — see run-headless-kde.sh.
#
# Appliance install (boot with no interaction):
# cp scripts/punktfunk-kde-session.service scripts/punktfunk-host.service ~/.config/systemd/user/
# cp scripts/host.env.example ~/.config/punktfunk/host.env # edit for the kwin backend
# systemctl --user daemon-reload
# systemctl --user enable punktfunk-kde-session punktfunk-host
# sudo loginctl enable-linger "$USER" # <-- starts user units at boot WITHOUT a login
# reboot # (or: systemctl --user start … to bring up now)
[Unit]
Description=punktfunk headless KDE Plasma session (kwin --virtual on wayland-kde)
After=pipewire.service pipewire-pulse.service dbus.service
Wants=pipewire.service
[Service]
Type=simple
ExecStart=/usr/bin/bash %h/punktfunk/scripts/headless/run-headless-kde.sh 1920x1080
Restart=always
RestartSec=3
[Install]
WantedBy=default.target