54b75c9be4
apple / swift (push) Successful in 55s
windows-host / package (push) Successful in 2m31s
android / android (push) Successful in 4m40s
ci / rust (push) Successful in 4m43s
ci / web (push) Successful in 30s
ci / docs-site (push) Successful in 34s
deb / build-publish (push) Successful in 2m9s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 14s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 21s
ci / bench (push) Successful in 4m44s
docker / deploy-docs (push) Successful in 19s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m6s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m19s
Follows the security audit (#5/#9): the GameStream-compat plane carries inherent on-path weaknesses that can't be fixed on the wire without breaking stock Moonlight — its pairing runs over plain HTTP (#9, MITM-able during the pairing window) and its legacy control encryption can reuse GCM nonces (#5, a passive eavesdropper can recover/forge input). The native punktfunk/1 plane (SPAKE2 PIN pairing + per-direction AEAD nonces) has neither. So flip the default to secure-by-default: - `serve` → native punktfunk/1 plane + management API ONLY (no GameStream surface). - `serve --gamestream` → ALSO the GameStream/Moonlight-compat planes (nvhttp pairing, RTSP, ENet control, _nvstream mDNS). Opt-in, logged with a trusted-LAN caveat. `--moonlight` is an alias. - The native plane is now ALWAYS on in `serve` (`--native` is a kept-for-compat no-op); the unified GameStream+native host is `serve --gamestream`. `gamestream::serve` gates the GameStream spawns (nvhttp/rtsp/control/mdns) on the flag; the native plane + mgmt + native-pairing handle always run. To avoid silently regressing validated Moonlight deployments, the explicit deployment configs PRESERVE Moonlight via `--gamestream` (each documents dropping it for a secure native-only host): the Linux systemd unit, the Steam Deck installer, and the Windows service default (DEFAULT_HOST_CMD). The bare `serve` default (new/manual use) is secure. Docs swept to match (host-cli, moonlight, quickstart, install, packaging READMEs, CLAUDE.md, README, …): Moonlight setup now instructs `--gamestream`; native/console refs use bare `serve`. OpenAPI regenerated (a stale "run `serve --native`" string). fmt + clippy clean; 94 host tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
84 lines
5.0 KiB
Markdown
84 lines
5.0 KiB
Markdown
# punktfunk host on a Steam Deck
|
||
|
||
Run a punktfunk **host** on a Steam Deck — stream its Game Mode (or KDE desktop) *to* other devices.
|
||
(Streaming *to* a Deck is the client; use the Flatpak + [Decky plugin](../../clients/decky/) instead.)
|
||
|
||
User-facing guide: **docs-site → "Steam Deck (Host)"** (`docs-site/content/docs/steam-deck-host.md`).
|
||
This README is the deep reference for what the scripts do and how to operate them by hand.
|
||
|
||
## Why build on-device (not a package or prebuilt binary)
|
||
|
||
SteamOS 3 is an **immutable, read-only Arch** base:
|
||
|
||
- No `pacman -S` for system libs; `/usr` is read-only and reset on A/B updates.
|
||
- A **prebuilt binary is fragile** — it links the system FFmpeg/glibc, and a SteamOS update can bump
|
||
those sonames out from under it (the same class of breakage as the NVIDIA-driver-after-update issue).
|
||
- The host needs **unsandboxed** `/dev/uinput` + `/dev/uhid`, PipeWire, the compositor, and VAAPI — so
|
||
Flatpak (the normal Deck app channel) doesn't fit. Flatpak/Decky are for the *client*.
|
||
|
||
So the host is built **natively inside a Debian-trixie distrobox** (`pf2`), chosen because its
|
||
FFmpeg/glibc ABI matches SteamOS's — the resulting binary runs **natively on SteamOS** (the container
|
||
is only the build environment; `punktfunk-host` is launched directly, not via `distrobox enter`). A
|
||
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 web console is the one part that stays in the container at runtime: it's a Nitro **node-server**
|
||
build (`bun` builds it; **`node` runs it** — bun mis-resolves Nitro's externalized server deps like
|
||
`srvx` at request time), so its service does `distrobox enter pf2 -- … node .output/server/index.mjs`.
|
||
Both `bun` and `nodejs` are provisioned in the container.
|
||
|
||
## Scripts
|
||
|
||
| 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. |
|
||
|
||
```sh
|
||
git clone https://git.unom.io/unom/punktfunk ~/punktfunk
|
||
bash ~/punktfunk/scripts/steamdeck/install.sh # PIN pairing required (secure default)
|
||
bash ~/punktfunk/scripts/steamdeck/install.sh --open # trusted LAN: accept unpaired clients
|
||
bash ~/punktfunk/scripts/steamdeck/install.sh --no-web # host only, no web console
|
||
bash ~/punktfunk/scripts/steamdeck/update.sh # after pulling new source
|
||
```
|
||
|
||
Env overrides: `PUNKTFUNK_SRC` (source dir, default `~/punktfunk`), `PUNKTFUNK_BOX` (container name,
|
||
default `pf2`), `PUNKTFUNK_MGMT_PORT` (47990), `PUNKTFUNK_WEB_PORT` (3000).
|
||
|
||
## What gets installed
|
||
|
||
- **Binary:** `~/punktfunk/target-steamos/release/punktfunk-host` (built in `pf2`, run natively).
|
||
- **Config:** `~/.config/punktfunk/host.env` (encoder/compositor) and `web.env` (generated web login
|
||
password + session secret). Trust material (`cert.pem`, `mgmt-token`, `punktfunk1-paired.json`) lives
|
||
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.
|
||
- **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.
|
||
|
||
## Operating
|
||
|
||
```sh
|
||
systemctl --user status punktfunk-host punktfunk-web
|
||
journalctl --user -u punktfunk-host -f # watch sessions / pairing PIN
|
||
systemctl --user restart punktfunk-host # after editing host.env
|
||
```
|
||
|
||
Pair from the web console (Devices → arm pairing) or directly from a client with the host's PIN. The
|
||
host advertises over mDNS as `_punktfunk._udp`, so clients discover it automatically.
|
||
|
||
## Gotchas
|
||
|
||
- **distrobox required.** If missing: `curl -sfL https://raw.githubusercontent.com/89luca89/distrobox/main/install | sh -s -- --prefix ~/.local` (then ensure `~/.local/bin` is on PATH).
|
||
- **First build is slow** (~10–15 min + ~1 GB toolchain/image). Incremental afterwards.
|
||
- **No passwordless sudo** → the installer skips the sysctl/udev/input steps with a warning; high
|
||
bitrates will drop packets until you apply `99-punktfunk-net.conf` and join `input` yourself.
|
||
- **Game Mode auto-suspend** drops the host off the network on idle — disable it (Settings → Power)
|
||
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.
|