Files
punktfunk/docs-site/content/docs/steamos-host.md
T
enricobuehlerandClaude Fable 5 19392918ff
ci / web (push) Successful in 1m9s
ci / docs-site (push) Successful in 1m14s
ci / rust-arm64 (push) Successful in 3m28s
deb / build-publish-client-arm64 (push) Successful in 2m28s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 10s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 9s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 11s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 29s
android / android (push) Successful in 5m47s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m16s
docker / builders-arm64cross (push) Successful in 9s
deb / build-publish (push) Successful in 6m5s
docker / deploy-docs (push) Successful in 35s
deb / build-publish-host (push) Successful in 6m35s
ci / rust (push) Successful in 6m38s
arch / build-publish (push) Successful in 9m13s
apple / swift (push) Failing after 13m37s
apple / screenshots (push) Skipped
windows-host / package (push) Successful in 17m4s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 20s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m17s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 39s
feat(gamescope/hdr): HDR is attempted by default and every install path ships the patched gamescope
Field report (RX 5700 / SteamOS): 10-bit HEVC "unsupported" — but the GPU was
never the blocker (RADV probed Main10 fine on that box). The session stayed SDR
because stock gamescope offers no 10-bit PQ capture formats and nothing shipped
our pipewire-hdr build outside the Bazzite sysext, while PUNKTFUNK_GAMESCOPE_HDR
additionally defaulted off everywhere. Close all three gaps:

- PUNKTFUNK_GAMESCOPE_HDR now defaults ON (explicit-off grammar, the flip the
  config comment planned post-canary). Safe by construction: the +pfhdr banner
  probe, managed-spawn term, and client 10-bit cap keep stock-gamescope boxes on
  the exact 8-bit path. The nix module's gamescopeHdr (default true now) only
  controls whether the patched binary is on PATH; it no longer sets the env.
- SteamOS: new scripts/steamdeck/build-gamescope.sh builds the HDR gamescope in
  the same pf2 trixie distrobox (apt dep list validated by a full build in a
  debian:trixie container), installs ~/.local/bin/punktfunk-gamescope, and
  maintains PUNKTFUNK_GAMESCOPE_BIN in host.env ONLY while the binary passes its
  on-glass --version/+pfhdr check — a stale absolute override would break
  session spawning, not just HDR. Wired into install.sh (§3b), update.sh
  (retrofit), and rebuild-check.sh (ldd-probes it like the host binary).
- Arch: build-sysext.sh gains a --gamescope fold-in (banner-verified by
  execution, host-image-only), and punktfunk-host optdepends the companion.
  Deliberately NO CI-published host sysext: per the 2026-07-22 packaging
  verdict a prebuilt SteamOS host breaks on A/B soname bumps (and /var is
  per-partition-set), so arch.yml records that instead — SteamOS hosts use the
  distrobox flow above.

Also fixes a latent bug that made build-sysext.sh fail every invocation as
documented: a literal `}` inside `${1:?usage …{host,client}…}` terminates the
expansion early and corrupts $PKG (caught by the new synthetic-package test,
which also covers the fold-in, the unmarked-gamescope refusal, and the
client-image guard).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 13:50:31 +02:00

9.6 KiB
Raw Blame History

title, description
title description
SteamOS (Host) Run a punktfunk host on SteamOS — stream its Game Mode (or desktop) to your other devices. One script, built on-device and ABI-matched to SteamOS.

This is for using a SteamOS device as the host — streaming from it to a laptop, TV, phone, or another device. (For the usual case — streaming to a Steam Deck — see Install a Client, which uses the Flatpak + Decky plugin.)

We support SteamOS as a host mainly with an eye to the upcoming Steam Machine — a living-room, desktop-class SteamOS box is a natural always-on streaming host. The Steam Deck is the SteamOS device we can test on today, so it's what these instructions are validated against; the same on-device build works on any SteamOS 3 system.

New here? Read Security & Safe Use first — a streaming host is remote control of the machine, so keep it on a trusted LAN or VPN and require pairing.

SteamOS is an immutable, read-only Arch base, so the host isn't a system package. Instead a single script builds the host natively inside a Debian-trixie distrobox (ABI-matched to SteamOS's FFmpeg/glibc — the binary then runs natively on SteamOS) and wires it up as systemd user services. Building on-device means a rebuild always matches the running OS, so a SteamOS update can't leave you with a binary linked against the wrong libraries. Encode is VAAPI on the AMD GPU (auto-detected; NVENC on NVIDIA).

Heads up: in our testing the Steam Deck's WiFi tx topped out around ~250 Mbps of goodput regardless of band — enough for 1080p/1440p60, not 4K. This looked like a hardware/driver packet-rate limit rather than a bandwidth ceiling, but it's one device measured on one network: other SteamOS hardware, newer drivers, or a less congested band may do better. A wired dock sidesteps it entirely. See Configuration for bitrate guidance.

Prerequisites

  • A SteamOS device on SteamOS 3 (e.g. a Steam Deck, LCD or OLED). Steady WiFi or, better, a wired dock.
  • distrobox installed (no root needed). If distrobox isn't found:
    curl -sfL https://raw.githubusercontent.com/89luca89/distrobox/main/install | sh -s -- --prefix ~/.local
    
    Make sure ~/.local/bin is on your PATH (re-open the terminal).
  • The first build downloads a container image + toolchain (~1 GB) and takes ~1015 minutes. Later rebuilds are incremental.

1. Get the source

In Desktop Mode open Konsole (or ssh in), then:

git clone https://git.unom.io/unom/punktfunk ~/punktfunk

2. Run the installer

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, the web console, and the plugin/script runner (so the console's plugin store 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), 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) plus a boot-time rebuild check that repairs the host automatically if a SteamOS update ever breaks its library links.

Useful flags:

Flag Effect
--open Accept unpaired clients (trust-on-first-use) — convenient on a fully trusted LAN. Default is PIN pairing required.
--no-gamestream Run a secure native-only host — skip the GameStream/Moonlight-compat planes (see below). Default keeps them on so stock Moonlight works.
--no-web Skip the management web console.
--src=DIR Build from source at DIR instead of ~/punktfunk.

When it finishes it prints the web-console URL and how to pair.

GameStream/Moonlight compat is on by default. The native punktfunk/1 plane (used by punktfunk's own clients — SPAKE2 PIN pairing, per-direction AEAD) is always on and is the secure path. The installer also enables the GameStream/Moonlight-compat planes so stock Moonlight works — but those carry inherent on-path weaknesses (pairing over plain HTTP; legacy control encryption that can reuse GCM nonces), so enable them only on a trusted LAN. If you only ever use native clients, install with --no-gamestream for a host with no GameStream surface at all.

First install — reboot once before streaming. KWin only authorizes Desktop-mode screen capture on a fresh session, and the new input group (native Steam Deck controller passthrough) only takes effect on a new login — so after the first install, reboot the Deck (a re-run that changes nothing doesn't need it). Streaming Game Mode with a generic Xbox pad works right away; Desktop capture and the native Steam Deck controller need the reboot. If a client connects and every session ends with KWin does not expose zkde_screencast_unstable_v1 or the pad shows up as an Xbox 360 controller, you haven't rebooted yet.

3. Pair a device

By default the host requires PIN pairing (secure). Two ways to pair:

  • Web console (printed at the end of step 2): open https://<device-ip>:47992 (self-signed host cert — your browser warns once; trust it and continue), arm pairing, and enter the PIN on your client.
  • From the client directly: pick this host (it advertises over mDNS as _punktfunk._udp) and enter the PIN the host shows.

On a trusted home LAN you can instead install with --open and skip pairing entirely.

Console login password

The installer generates a random console login password (printed at the end of step 2) and writes it to ~/.config/punktfunk/web.env. To read it back or set your own, see The Web Console.

4. Verify

systemctl --user status punktfunk-host          # active (running)
journalctl --user -u punktfunk-host -f          # watch a client connect

Connect from a native client, or from Moonlight (unless you installed with --no-gamestream). In Game Mode the host attaches to the running gamescope session and streams it at your client's resolution; in Desktop Mode it streams the KDE desktop. The host auto-detects which session is live per connection. See Steam / gamescope for the attach-vs-managed detail and known limits.

HDR (10-bit BT.2020 PQ)

The installer also builds punktfunk-gamescope — gamescope plus the small patch that adds the 10-bit PQ formats to its capture node (see HDR on gamescope) — and points the host at it. Nothing to configure: with it in place, a 10-bit-capable client with HDR enabled streams true HDR10 from Game Mode; anything missing and the session streams 8-bit SDR instead (never a mislabelled picture). Check what the host resolved with:

~/punktfunk/target-steamos/release/punktfunk-host hdr-probe

The build is best-effort: if it fails, the installer says so loudly and everything else keeps working in SDR — re-run update.sh to retry. PUNKTFUNK_GAMESCOPE_HDR=0 in ~/.config/punktfunk/host.env forces SDR deliberately.

Updating

After pulling new source, rebuild and restart in one step (config + pairings persist):

git -C ~/punktfunk pull          # or rsync new source in
bash ~/punktfunk/scripts/steamdeck/update.sh

Notes & limits

  • Single session at a time at custom resolutions — two clients requesting different modes will thrash the managed session. Pick one mode per session.
  • Keep the device awake. On handhelds, Game Mode auto-suspends on idle, which drops the host off the network mid stream — disable auto-suspend (Settings → Power) for a headless host.
  • Native Steam Deck controller passthrough presents the client's pad as a real Steam Deck controller (paddles, trackpads, gyro) via a virtual USB device — that needs the input group and the vhci-hcd module live, so it only works after the first-install reboot above; until then the pad 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.
  • 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.

Trouble? See Troubleshooting and Pairing.