From acce43ebbfe64db80f321bf120619ac4457c8a1f Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 22 Jul 2026 11:56:09 +0200 Subject: [PATCH] =?UTF-8?q?feat(steamdeck):=20self-healing=20reliability?= =?UTF-8?q?=20=E2=80=94=20post-OS-update=20rebuild=20check=20+=20script/do?= =?UTF-8?q?cs=20polish?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rebuild-check.sh + punktfunk-rebuild-check.service (enabled, ordered Before=punktfunk-host): ldd-probes the host binary at session start — milliseconds when healthy, a full update.sh rebuild only when a SteamOS update actually broke its library links. update.sh restarts go --no-block so the check → update.sh → restart chain can't deadlock against the unit ordering. update.sh retrofits the unit. - installer summary: web console is https (the unit serves TLS). - docs: steamos-host.md (runner in the build step, keep-list + auto rebuild = updates survive hands-free), gamescope.md (Gaming Mode touch = single-finger pointer, exact taps, no multi-touch), plugins.mdx (SteamOS runner note), scripts/steamdeck/README.md (rebuild-check, runner payload, keep list, honest packaging trade-off + the CI-prebuilt future direction). Co-Authored-By: Claude Fable 5 --- docs-site/content/docs/gamescope.md | 4 +++ docs-site/content/docs/plugins.mdx | 4 +++ docs-site/content/docs/steamos-host.md | 18 +++++++++---- scripts/steamdeck/README.md | 35 ++++++++++++++++++++------ scripts/steamdeck/install.sh | 24 +++++++++++++++++- scripts/steamdeck/rebuild-check.sh | 31 +++++++++++++++++++++++ scripts/steamdeck/update.sh | 33 +++++++++++++++++++++--- 7 files changed, 133 insertions(+), 16 deletions(-) create mode 100755 scripts/steamdeck/rebuild-check.sh diff --git a/docs-site/content/docs/gamescope.md b/docs-site/content/docs/gamescope.md index 7b2c4a56..7d086ef7 100644 --- a/docs-site/content/docs/gamescope.md +++ b/docs-site/content/docs/gamescope.md @@ -70,6 +70,10 @@ These apply to the **Gaming Mode (gamescope)** path only; the desktop path is un - **gamescope 3.16.22 or newer is required.** Older versions can deadlock during capture. Bazzite's and SteamOS's current gamescope is fine; this only bites if you've pinned an old one. - **The mouse cursor isn't included in the captured image** — a gamescope limitation for now. +- **Touch arrives as a single-finger pointer.** gamescope's virtual input device has no + touchscreen, so the host maps a client's touchscreen to an absolute pointer: taps click exactly + where you touch and drags work, but multi-touch gestures (pinch) aren't available in Gaming + Mode. The desktop path has full multi-touch. - **HDR isn't supported on the gamescope path** — gamescope's capture output is 8-bit. SDR streams normally. diff --git a/docs-site/content/docs/plugins.mdx b/docs-site/content/docs/plugins.mdx index 52a4e3d6..32af6bb9 100644 --- a/docs-site/content/docs/plugins.mdx +++ b/docs-site/content/docs/plugins.mdx @@ -69,6 +69,10 @@ punktfunk-host plugins add playnite # or: rom-manager punktfunk-host plugins enable # turn the runner on (once) ``` +On **SteamOS** the [host installer](/docs/steamos-host) ships the runner automatically (user-scoped +under `~/.local` — the read-only `/usr` can't take the package). If the console reports the runner +isn't installed on an older setup, re-run `scripts/steamdeck/update.sh` once. + diff --git a/docs-site/content/docs/steamos-host.md b/docs-site/content/docs/steamos-host.md index e7fb48aa..58083657 100644 --- a/docs-site/content/docs/steamos-host.md +++ b/docs-site/content/docs/steamos-host.md @@ -56,14 +56,17 @@ bash ~/punktfunk/scripts/steamdeck/install.sh It is idempotent — safe to re-run. In one pass it: 1. creates the `pf2` Debian-trixie distrobox and installs the build toolchain, -2. builds `punktfunk-host` (and the web console), +2. builds `punktfunk-host`, the web console, and the **plugin/script runner** (so the console's + [plugin store](/docs/plugins) works out of the box — the runner service itself stays opt-in), 3. writes config to `~/.config/punktfunk/` (a generated web-console login password), 4. raises the UDP socket buffers to 32 MB, installs the gamepad udev rule + the `vhci-hcd` autoload and adds you to the `input` group (virtual gamepads / **native Steam Deck controller passthrough**), - and seeds the KDE RemoteDesktop grant for Desktop-mode input — this step **prompts for your `sudo` + seeds the KDE RemoteDesktop grant for Desktop-mode input, and **registers all of it on SteamOS's + atomic-update keep list** so OS updates carry it over — this step **prompts for your `sudo` password** (a stock Steam Deck requires one; without it gamepad passthrough and the UDP tuning are skipped), 5. installs + starts the `punktfunk-host` and `punktfunk-web` **systemd user services** (with linger, - so they run without a login session). + so they run without a login session) plus a boot-time **rebuild check** that repairs the host + automatically if a SteamOS update ever breaks its library links. Useful flags: @@ -144,8 +147,13 @@ bash ~/punktfunk/scripts/steamdeck/update.sh degrades to a generic Xbox 360 controller (still fully playable). If you're streaming *to* another Steam Deck, also set Steam Input to **Off** for Punktfunk on that Deck — see [Stream to a Steam Deck](/docs/steam-deck). -- **It survives OS updates**, but a major SteamOS bump can move library versions; if the host fails to - start after an update, just re-run `update.sh` to rebuild against the new base. +- **It survives OS updates — automatically.** SteamOS A/B updates rebuild `/etc` and can move + library versions; the installer defends both sides. The system tuning (gamepad udev rule, + `vhci-hcd`, UDP buffers) is registered on SteamOS's own atomic-update keep list + (`/etc/atomic-update.conf.d/`), so updates carry it over; and a boot-time check + (`punktfunk-rebuild-check`) probes the host binary and re-runs the build only if the new OS + actually broke its library links — you should never need to intervene. (Re-running `update.sh` + by hand still works and is harmless.) - Deeper reference (services, container, manual steps): [`scripts/steamdeck/README.md`](https://git.unom.io/unom/punktfunk/src/branch/main/scripts/steamdeck/README.md). Trouble? See [Troubleshooting](/docs/troubleshooting) and [Pairing](/docs/pairing). diff --git a/scripts/steamdeck/README.md b/scripts/steamdeck/README.md index 72cdd208..c251f79d 100644 --- a/scripts/steamdeck/README.md +++ b/scripts/steamdeck/README.md @@ -22,6 +22,15 @@ is only the build environment; `punktfunk-host` is launched directly, not via `d rebuild always matches the running OS. Encode is **VAAPI** on the Deck's AMD GPU (NVENC on NVIDIA), auto-selected by `PUNKTFUNK_ENCODER=auto`. +The honest trade-off: on-device building costs a slow first install (~10–15 min, ~1 GB of image + +toolchain) and adds moving parts (apt mirrors, rustup, bun) — the price of an install that can +always chase the OS. Both failure modes of that chase are automated away now: +`punktfunk-rebuild-check` rebuilds when an OS update breaks the binary's links, and the +atomic-update keep list preserves the `/etc` tuning. The eventual lighter-weight alternative is a +CI-prebuilt bundle with the volatile libraries (FFmpeg et al.) vendored under an `$ORIGIN` rpath — +OS-update-proof without a toolchain on the device — worth it once SteamOS host volume justifies +per-release artifact signing/hosting; the from-source path would stay as the dev/fallback route. + The web console is the one part that stays in the container at runtime: it's a Nitro **`bun`** build (`bun` both builds **and runs** it — the bun-preset output uses `Bun.serve` with TLS, serving HTTPS (HTTP/1.1 over TLS) with the host's identity cert), so its service does @@ -31,8 +40,9 @@ serving HTTPS (HTTP/1.1 over TLS) with the host's identity cert), so its service | Script | What it does | |--------|--------------| -| `install.sh` | Idempotent installer: ensure the `pf2` distrobox + toolchain → build host (+web) → write config → tune sysctl + `input` group (sudo) → install + start `punktfunk-host` / `punktfunk-web` systemd **user** services with linger. | -| `update.sh` | Rebuild from the current source and restart the services (config + pairings persist). `--pull` does `git pull` first. | +| `install.sh` | Idempotent installer: ensure the `pf2` distrobox + toolchain → build host + web + **plugin runner** → write config → tune sysctl + udev + `vhci-hcd` + `input` group and **register it on SteamOS's atomic-update keep list** (sudo) → install + start `punktfunk-host` / `punktfunk-web` systemd **user** services with linger, plus the **rebuild check** below. | +| `update.sh` | Rebuild everything from the current source and restart the services (config + pairings persist). `--pull` does `git pull` first. Also retrofits anything a newer install.sh writes (runner, keep-list registration, rebuild check) onto older installs. | +| `rebuild-check.sh` | The post-OS-update self-heal (run by `punktfunk-rebuild-check.service` before the host at session start): `ldd`-probes the binary — milliseconds when healthy, a full `update.sh` rebuild only when a SteamOS update actually broke its library links. | ```sh git clone https://git.unom.io/unom/punktfunk ~/punktfunk @@ -57,10 +67,19 @@ default `pf2`), `PUNKTFUNK_MGMT_PORT` (47990), `PUNKTFUNK_WEB_PORT` (47992). here too and persists across updates. - **Services:** `~/.config/systemd/user/punktfunk-host.service` (runs `serve --gamestream --mgmt-bind 0.0.0.0:47990`, `+ --open` if chosen — `--gamestream` adds the Moonlight-compat planes so the Deck's - Game Mode also streams to stock Moonlight; the native `punktfunk/1` plane is always on) and - `punktfunk-web.service`. Linger is enabled so they run without a login session. + Game Mode also streams to stock Moonlight; the native `punktfunk/1` plane is always on), + `punktfunk-web.service`, `punktfunk-rebuild-check.service` (post-OS-update self-heal, enabled), and + `punktfunk-scripting.service` (plugin runner, **opt-in** — enable it once you use plugins/scripts). + Linger is enabled so they run without a login session. +- **Plugin runner:** the deb's payload laid out user-scoped (read-only `/usr` can't take the + package): wrapper `~/.local/bin/punktfunk-scripting`, pinned `bun` in + `~/.local/lib/punktfunk-scripting/`, bundle in `~/.local/share/punktfunk-scripting/`. - **System tuning (sudo):** `/etc/sysctl.d/99-punktfunk-net.conf` (32 MB UDP buffers — the #1 - high-bitrate lever), `/etc/udev/rules.d/60-punktfunk.rules`, and `$USER` in the `input` group. + high-bitrate lever), `/etc/udev/rules.d/60-punktfunk.rules` (`uinput`/`uhid` access), + `/etc/modules-load.d/punktfunk.conf` (`vhci-hcd` for the native Deck pad), `$USER` in the `input` + group — and `/etc/atomic-update.conf.d/punktfunk.conf`, which registers the three files on + SteamOS's atomic-update keep list so A/B OS updates carry them over (verified: without it an + update silently strips them — pads degrade to Xbox 360, buffers drop to 208 KB). ## Operating @@ -83,5 +102,7 @@ host advertises over mDNS as `_punktfunk._udp`, so clients discover it automatic for a headless host. - **WiFi tx ceiling** ≈ 250 Mbps goodput (a Deck hardware/driver packet-rate limit, band-independent); fine for 1080p/1440p60. A wired dock lifts it. -- **After a major SteamOS update**, if the host won't start, run `update.sh` to rebuild against the new - base libraries. +- **After a SteamOS update** nothing should be needed: the `/etc` tuning survives via the + atomic-update keep list, and `punktfunk-rebuild-check` rebuilds the binary automatically if the + new base actually broke its library links (first session start after the update takes the build's + few minutes in that case). A manual `update.sh` remains harmless. diff --git a/scripts/steamdeck/install.sh b/scripts/steamdeck/install.sh index 6e4b71c4..48539141 100755 --- a/scripts/steamdeck/install.sh +++ b/scripts/steamdeck/install.sh @@ -350,6 +350,28 @@ sed 's|^ExecStart=.*|ExecStart=%h/.local/bin/punktfunk-scripting|' \ "$SRC/scripts/punktfunk-scripting.service" > "$UNITS/punktfunk-scripting.service" ok "punktfunk-scripting.service (opt-in: systemctl --user enable --now punktfunk-scripting)" +# Post-OS-update self-heal: SteamOS A/B updates can bump library sonames the host binary links +# (FFmpeg/PipeWire/libva) — this oneshot probes the binary with ldd before punktfunk-host starts +# and re-runs update.sh only when it actually stopped loading. Milliseconds on a normal boot. +cat > "$UNITS/punktfunk-rebuild-check.service" </dev/null || true +systemctl --user enable punktfunk-rebuild-check.service 2>/dev/null || true +ok "punktfunk-rebuild-check.service (auto-rebuild after SteamOS updates)" + systemctl --user daemon-reload loginctl show-user "$USER" 2>/dev/null | grep -q 'Linger=yes' || { sudo loginctl enable-linger "$USER" 2>/dev/null && ok "enabled linger (services run without login)" || warn "could not enable linger — services stop when you log out (sudo loginctl enable-linger $USER)"; } # enable + restart (not `enable --now`): restart picks up unit-file changes on a re-run, where @@ -371,7 +393,7 @@ echo log "Done — punktfunk host is running on this Steam Deck" echo " • Host status: systemctl --user status punktfunk-host" if [ "$WITH_WEB" = 1 ]; then - echo " • Web console: http://${IP:-steamdeck.local}:$WEB_PORT (login: see $CONFIG/web.env)" + echo " • Web console: https://${IP:-steamdeck.local}:$WEB_PORT (login: see $CONFIG/web.env)" echo " • Pair a device: open the web console → Devices → arm pairing → enter the PIN on the client" fi if [ "$OPEN" = 1 ]; then diff --git a/scripts/steamdeck/rebuild-check.sh b/scripts/steamdeck/rebuild-check.sh new file mode 100755 index 00000000..3f064eb4 --- /dev/null +++ b/scripts/steamdeck/rebuild-check.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# punktfunk — SteamOS post-OS-update self-heal (runs before punktfunk-host at session start). +# +# The host binary links SteamOS system libraries (FFmpeg, PipeWire, libva, …). A SteamOS A/B +# update that bumps a soname leaves the binary unable to load — a silently dead host until +# someone remembers to re-run update.sh. This probe is the reliability backstop: +# * healthy binary → exit in milliseconds (every normal boot); +# * loader breakage → run scripts/steamdeck/update.sh (rebuild host + web + runner against +# the new library tree, restart services). The build container, source, and cargo caches +# all live under /home, which SteamOS updates never touch — so the rebuild is warm. +# +# Root is NOT needed: the /etc system tuning survives updates via the atomic-update keep list +# (see punktfunk-atomic-keep.conf); only the binary has to chase the OS libraries. +set -euo pipefail + +# systemd user units get a minimal PATH — distrobox commonly lives in ~/.local/bin. +export PATH="$HOME/.local/bin:$PATH" +SRC="${PUNKTFUNK_SRC:-$HOME/punktfunk}" +BIN="$SRC/target-steamos/release/punktfunk-host" + +if [ ! -x "$BIN" ]; then + echo "punktfunk-host binary missing at $BIN — running a full rebuild" >&2 +elif ! ldd "$BIN" 2>/dev/null | grep -q "not found"; then + exit 0 # every library resolves — nothing to do +else + echo "punktfunk-host no longer loads after a SteamOS update — its missing libraries:" >&2 + ldd "$BIN" 2>/dev/null | grep "not found" >&2 || true + echo "rebuilding against the new OS tree (this takes a few minutes; streaming resumes after)" >&2 +fi + +exec bash "$SRC/scripts/steamdeck/update.sh" diff --git a/scripts/steamdeck/update.sh b/scripts/steamdeck/update.sh index 09772f99..3247594f 100755 --- a/scripts/steamdeck/update.sh +++ b/scripts/steamdeck/update.sh @@ -52,6 +52,29 @@ sed 's|^ExecStart=.*|ExecStart=%h/.local/bin/punktfunk-scripting|' \ systemctl --user daemon-reload ok "plugin runner rebuilt (opt-in service: systemctl --user enable --now punktfunk-scripting)" +# Retrofit the post-OS-update rebuild check (install.sh §5) onto older installs: probes the host +# binary with ldd at session start and re-runs this script when a SteamOS update broke its links. +if [ ! -f "$HOME/.config/systemd/user/punktfunk-rebuild-check.service" ]; then + cat > "$HOME/.config/systemd/user/punktfunk-rebuild-check.service" </dev/null || true + systemctl --user daemon-reload + systemctl --user enable punktfunk-rebuild-check.service 2>/dev/null || true + ok "punktfunk-rebuild-check.service installed (auto-rebuild after SteamOS updates)" +fi + # Retrofit config that install.sh now writes but older installs predate (both idempotent): # RADV_PERFTEST — Van Gogh RADV still gates VK_KHR_video_encode_* behind it; without it the # Vulkan backend can't open and sessions silently fall back to libav VAAPI. The KWin .desktop — @@ -123,8 +146,12 @@ if [ ! -s "$GRANT_DST" ] && [ -s "$GRANT_SRC" ]; then fi log "Restarting services" -systemctl --user restart punktfunk-host.service -ok "punktfunk-host restarted" -if [ "$WEB" = 1 ]; then systemctl --user restart punktfunk-web.service; ok "punktfunk-web restarted"; fi +# --no-block: when this script runs INSIDE punktfunk-rebuild-check.service (ordered +# Before=punktfunk-host), a blocking restart would deadlock — the restart job waits for the +# check unit, which waits for this script, which waits for the restart. Enqueue and move on; +# systemd starts the service the moment the ordering allows. +systemctl --user restart --no-block punktfunk-host.service +ok "punktfunk-host restart queued" +if [ "$WEB" = 1 ]; then systemctl --user restart --no-block punktfunk-web.service; ok "punktfunk-web restart queued"; fi echo log "Updated. Status: systemctl --user status punktfunk-host"