feat(steamdeck): self-healing reliability — post-OS-update rebuild check + script/docs polish
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
</Tab>
|
||||
<Tab value="Windows">
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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" <<EOF
|
||||
# Generated by scripts/steamdeck/install.sh — rebuild the host if a SteamOS update broke its libs.
|
||||
[Unit]
|
||||
Description=punktfunk SteamOS post-update rebuild check
|
||||
Before=punktfunk-host.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=$SRC/scripts/steamdeck/rebuild-check.sh
|
||||
# A cold-ish rebuild is minutes, not seconds.
|
||||
TimeoutStartSec=1800
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
EOF
|
||||
chmod +x "$SRC/scripts/steamdeck/rebuild-check.sh" 2>/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
|
||||
|
||||
Executable
+31
@@ -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"
|
||||
@@ -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" <<EOF
|
||||
# Generated by scripts/steamdeck/update.sh — rebuild the host if a SteamOS update broke its libs.
|
||||
[Unit]
|
||||
Description=punktfunk SteamOS post-update rebuild check
|
||||
Before=punktfunk-host.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=$SRC/scripts/steamdeck/rebuild-check.sh
|
||||
TimeoutStartSec=1800
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
EOF
|
||||
chmod +x "$SRC/scripts/steamdeck/rebuild-check.sh" 2>/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"
|
||||
|
||||
Reference in New Issue
Block a user