feat(steamdeck): one-command host install + docs (build-on-device)
SteamOS is immutable read-only Arch, and the Deck is AMD (VAAPI) — so none of the checked-in packaging (arch/sysext is NVENC-first + client-oriented, deb/rpm are soname-mismatched) actually installs a working host on a Steam Deck. The proven path (distrobox-built native binary + systemd-run units) was 100% manual. Make it one command. - scripts/steamdeck/install.sh — idempotent installer: ensure the pf2 Debian-trixie distrobox + toolchain → build host (+web console) → write config (generated web login password) → raise UDP buffers to 32 MB + udev + input group (sudo, skipped gracefully if unavailable) → install + start punktfunk-host / punktfunk-web systemd USER services with linger. Flags: --open (accept unpaired clients), --no-web, --src=DIR. Builds on-device so a rebuild always matches the running SteamOS (no prebuilt-binary fragility across OS updates); VAAPI on the Deck's AMD GPU. - scripts/steamdeck/update.sh — rebuild from current source + restart (config/pairings persist). - scripts/steamdeck/README.md — deep reference (why on-device, what's installed, gotchas). - docs-site: new "Steam Deck (Host)" guide + sidebar entry; install.md splits Arch from the Steam Deck host path; packaging/arch/README points Deck-host users here and corrects the stale "NVENC-only" note (VAAPI host encode landed). Live-validated on the Deck: installer runs clean, both services come up, host listens (QUIC :9777 + mgmt :47990), web serves (302→login); on a client connect it takes over the Game-Mode gamescope session at the client's mode, captures via PipeWire, and VAAPI-encodes (hevc_vaapi) — full pipeline confirmed in the host journal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,8 @@ steps, the web console) — those are the source of truth, so this page doesn't
|
|||||||
|--------|-----------------|------------------------|-------|
|
|--------|-----------------|------------------------|-------|
|
||||||
| **Ubuntu / Debian** | apt | `sudo apt install punktfunk-host` | [Ubuntu — GNOME](/docs/ubuntu-gnome) · [Ubuntu — KDE](/docs/ubuntu-kde) · [packaging/debian](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/debian/README.md) |
|
| **Ubuntu / Debian** | apt | `sudo apt install punktfunk-host` | [Ubuntu — GNOME](/docs/ubuntu-gnome) · [Ubuntu — KDE](/docs/ubuntu-kde) · [packaging/debian](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/debian/README.md) |
|
||||||
| **Fedora / Bazzite** | rpm-ostree | `rpm-ostree install punktfunk punktfunk-web` | [Fedora — KDE](/docs/fedora-kde) · [Bazzite](/docs/bazzite) · [packaging/rpm](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/rpm/README.md) |
|
| **Fedora / Bazzite** | rpm-ostree | `rpm-ostree install punktfunk punktfunk-web` | [Fedora — KDE](/docs/fedora-kde) · [Bazzite](/docs/bazzite) · [packaging/rpm](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/rpm/README.md) |
|
||||||
| **Arch / Steam Deck** | PKGBUILD / sysext | `makepkg -si` (Arch) · sysext `.raw` (SteamOS/Deck) | [packaging/arch](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/arch/README.md) |
|
| **Arch** | PKGBUILD | `makepkg -si` | [packaging/arch](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/arch/README.md) |
|
||||||
|
| **Steam Deck (host)** | on-device script | `bash scripts/steamdeck/install.sh` | [Steam Deck (Host)](/docs/steam-deck-host) |
|
||||||
|
|
||||||
Each registry is public — no auth, you just trust the repo's signing key. Adding the repo is a
|
Each registry is public — no auth, you just trust the repo's signing key. Adding the repo is a
|
||||||
one-time step covered in the linked guide; after that, normal `apt upgrade` / `rpm-ostree upgrade`
|
one-time step covered in the linked guide; after that, normal `apt upgrade` / `rpm-ostree upgrade`
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"ubuntu-kde",
|
"ubuntu-kde",
|
||||||
"fedora-kde",
|
"fedora-kde",
|
||||||
"bazzite",
|
"bazzite",
|
||||||
|
"steam-deck-host",
|
||||||
"running-as-a-service",
|
"running-as-a-service",
|
||||||
"---Connecting---",
|
"---Connecting---",
|
||||||
"clients",
|
"clients",
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
title: "Steam Deck (Host)"
|
||||||
|
description: "Run a punktfunk host on a Steam Deck — stream its Game Mode (or desktop) to your other devices. One script, built on-device for SteamOS."
|
||||||
|
---
|
||||||
|
|
||||||
|
This is for using a **Steam Deck as the host** — streaming *from* it to a laptop, TV, phone, or
|
||||||
|
another Deck. (For the usual case — streaming *to* a Deck — see [Install a Client](/docs/install-client),
|
||||||
|
which uses the Flatpak + Decky plugin.)
|
||||||
|
|
||||||
|
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 Deck's AMD GPU
|
||||||
|
(auto-detected; NVENC on NVIDIA).
|
||||||
|
|
||||||
|
> **Heads up:** the Deck's WiFi *tx* tops out around ~250 Mbps of goodput regardless of band (it's a
|
||||||
|
> hardware/driver packet-rate limit, not bandwidth) — plenty for 1080p/1440p60, not 4K. A wired dock
|
||||||
|
> lifts that. See [Configuration](/docs/configuration) for bitrate guidance.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- A Steam Deck on **SteamOS 3** (LCD or OLED). Steady WiFi or, better, a wired dock.
|
||||||
|
- **distrobox** installed (no root needed). If `distrobox` isn't found:
|
||||||
|
```sh
|
||||||
|
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 ~10–15 minutes. Later
|
||||||
|
rebuilds are incremental.
|
||||||
|
|
||||||
|
## 1. Get the source
|
||||||
|
|
||||||
|
In Desktop Mode open **Konsole** (or ssh in), then:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://git.unom.io/unom/punktfunk ~/punktfunk
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Run the installer
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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),
|
||||||
|
3. writes config to `~/.config/punktfunk/` (a generated web-console login password),
|
||||||
|
4. raises the UDP socket buffers to 32 MB and adds you to the `input` group (needs `sudo`; skipped
|
||||||
|
with a warning if unavailable),
|
||||||
|
5. installs + starts the `punktfunk-host` and `punktfunk-web` **systemd user services** (with linger,
|
||||||
|
so they run without a login session).
|
||||||
|
|
||||||
|
Useful flags:
|
||||||
|
|
||||||
|
| Flag | Effect |
|
||||||
|
|------|--------|
|
||||||
|
| `--open` | Accept **unpaired** clients (trust-on-first-use) — convenient on a fully trusted LAN. Default is PIN pairing required. |
|
||||||
|
| `--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.
|
||||||
|
|
||||||
|
## 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 `http://<deck-ip>:3000`, log in with the
|
||||||
|
generated password (in `~/.config/punktfunk/web.env`), go to **Devices → arm pairing**, and enter
|
||||||
|
the PIN on your client.
|
||||||
|
- **From the client directly**: pick this Deck (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.
|
||||||
|
|
||||||
|
## 4. Verify
|
||||||
|
|
||||||
|
```sh
|
||||||
|
systemctl --user status punktfunk-host # active (running)
|
||||||
|
journalctl --user -u punktfunk-host -f # watch a client connect
|
||||||
|
```
|
||||||
|
|
||||||
|
Connect from any client ([Moonlight](/docs/moonlight) or a [native client](/docs/clients)). 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.
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
After pulling new source, rebuild and restart in one step (config + pairings persist):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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 Deck awake.** Game Mode auto-suspends on idle, which drops the host off the network mid
|
||||||
|
stream — disable auto-suspend (Settings → Power) for a headless host.
|
||||||
|
- **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.
|
||||||
|
- 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).
|
||||||
+15
-12
@@ -1,23 +1,26 @@
|
|||||||
# punktfunk on Arch Linux / SteamOS
|
# punktfunk on Arch Linux / SteamOS
|
||||||
|
|
||||||
Packaging for punktfunk on Arch and Arch-derived immutable distros (SteamOS 3, etc.). The
|
Packaging for punktfunk on Arch and Arch-derived immutable distros. The `PKGBUILD` is a **split
|
||||||
`PKGBUILD` is a **split package** producing **`punktfunk-host`** (the gaming-rig host) and
|
package** producing **`punktfunk-host`** (the gaming-rig host) and **`punktfunk-client`** (the GTK4
|
||||||
**`punktfunk-client`** (the GTK4 couch/Deck client) — mirrors the rpm subpackages
|
couch/Deck client) — mirrors the rpm subpackages (`packaging/rpm/punktfunk.spec`) and the deb build
|
||||||
(`packaging/rpm/punktfunk.spec`) and the deb build scripts. On a **Steam Deck you want
|
scripts. On a **Steam Deck used as a client you want `punktfunk-client`** (it's what the
|
||||||
`punktfunk-client`** (it's what the [Decky plugin](../../clients/decky/) launches); on a gaming
|
[Decky plugin](../../clients/decky/) launches); on a gaming rig, `punktfunk-host`.
|
||||||
rig, `punktfunk-host`.
|
|
||||||
|
> **Steam Deck as a HOST:** don't use this PKGBUILD — SteamOS's read-only root makes `makepkg`/sysext
|
||||||
|
> awkward, and a prebuilt binary breaks on OS library bumps. Use the on-device build script instead:
|
||||||
|
> **[`scripts/steamdeck/install.sh`](../../scripts/steamdeck/)** (it builds in a Debian-trixie distrobox
|
||||||
|
> ABI-matched to SteamOS and uses **VAAPI** on the Deck's AMD GPU). The Deck host path is the one
|
||||||
|
> exception to "host encode is NVENC-only" below.
|
||||||
|
|
||||||
A third member, **`punktfunk-web`** (the browser management console — pairing + status), is
|
A third member, **`punktfunk-web`** (the browser management console — pairing + status), is
|
||||||
**opt-in**: build it by setting `PF_WITH_WEB=1`, which requires **`bun`** at build time (`bun-bin`
|
**opt-in**: build it by setting `PF_WITH_WEB=1`, which requires **`bun`** at build time (`bun-bin`
|
||||||
from the AUR if it isn't in your repos; the console then runs on plain `nodejs`). A default
|
from the AUR if it isn't in your repos; the console then runs on plain `nodejs`). A default
|
||||||
`makepkg` builds only host+client with no JS tooling — mirroring the RPM spec's `%bcond_with web`.
|
`makepkg` builds only host+client with no JS tooling — mirroring the RPM spec's `%bcond_with web`.
|
||||||
|
|
||||||
> ⚠️ **Host encode is NVENC-only today.** `crates/punktfunk-host/src/encode/linux.rs` implements
|
> **Host encode: NVENC on NVIDIA, VAAPI on AMD/Intel** (`PUNKTFUNK_ENCODER=auto` picks one). The host
|
||||||
> `hevc_nvenc`/`av1_nvenc`/`h264_nvenc` + a CUDA zero-copy path — there is **no VAAPI encoder**. So
|
> now has a VAAPI encoder + zero-copy dmabuf path alongside NVENC/CUDA, so `punktfunk-host` works on
|
||||||
> `punktfunk-host` works on **Arch + NVIDIA** (incl. `bazzite-deck-nvidia`); an **AMD Deck-as-host**
|
> Arch + NVIDIA **and** AMD/Intel (incl. the Steam Deck — see the on-device path above). The client
|
||||||
> can't encode until a `hevc_vaapi` backend is added (a code change, not packaging). The **client
|
> decodes via VAAPI on AMD/Intel with a software fallback.
|
||||||
> is unaffected** — `punktfunk-client` decodes via **VAAPI on AMD/Intel** (the Deck) with a software
|
|
||||||
> fallback, so streaming *to* a Deck works today.
|
|
||||||
|
|
||||||
## Arch Linux (mutable)
|
## Arch Linux (mutable)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# 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 run
|
||||||
|
by `bun`, so its service does `distrobox enter pf2 -- … bun run .output/server/index.mjs`.
|
||||||
|
|
||||||
|
## 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 --native --mgmt-bind
|
||||||
|
0.0.0.0:47990`, `+ --open` if chosen) 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.
|
||||||
Executable
+236
@@ -0,0 +1,236 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# punktfunk — Steam Deck HOST installer (stream FROM the Deck to other devices).
|
||||||
|
#
|
||||||
|
# SteamOS is an immutable, read-only Arch base, so the host can't be a system package and a
|
||||||
|
# prebuilt binary would break on an OS library bump. Instead we build the host natively inside a
|
||||||
|
# Debian-trixie distrobox (ABI-matched to SteamOS's FFmpeg/glibc) — the binary then runs natively
|
||||||
|
# on SteamOS — and wire it up as proper systemd USER services. A rebuild always matches the
|
||||||
|
# running OS. AMD encode uses VAAPI; NVIDIA uses NVENC (auto-detected).
|
||||||
|
#
|
||||||
|
# Run it on the Deck (Desktop Mode "Konsole", or over ssh). Idempotent — safe to re-run to update
|
||||||
|
# config or pick up new options. To rebuild after pulling new source, use update.sh.
|
||||||
|
#
|
||||||
|
# bash scripts/steamdeck/install.sh # secure default: PIN pairing required
|
||||||
|
# bash scripts/steamdeck/install.sh --open # trusted LAN: accept unpaired clients (TOFU)
|
||||||
|
# bash scripts/steamdeck/install.sh --no-web # skip the management web console
|
||||||
|
# PUNKTFUNK_SRC=~/src/punktfunk bash scripts/steamdeck/install.sh # source elsewhere
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
|
||||||
|
ok() { printf '\033[1;32m ok\033[0m %s\n' "$*"; }
|
||||||
|
warn() { printf '\033[1;33m !!\033[0m %s\n' "$*" >&2; }
|
||||||
|
die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }
|
||||||
|
have() { command -v "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
# --- options ---------------------------------------------------------------
|
||||||
|
SRC="${PUNKTFUNK_SRC:-$HOME/punktfunk}"
|
||||||
|
BOX="${PUNKTFUNK_BOX:-pf2}"
|
||||||
|
BOX_IMAGE="${PUNKTFUNK_BOX_IMAGE:-docker.io/library/debian:trixie}"
|
||||||
|
MGMT_PORT="${PUNKTFUNK_MGMT_PORT:-47990}"
|
||||||
|
WEB_PORT="${PUNKTFUNK_WEB_PORT:-3000}"
|
||||||
|
OPEN=0
|
||||||
|
WITH_WEB=1
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--open) OPEN=1 ;;
|
||||||
|
--no-web) WITH_WEB=0 ;;
|
||||||
|
--src=*) SRC="${arg#--src=}" ;;
|
||||||
|
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
|
||||||
|
*) die "unknown option: $arg (try --help)" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
TARGET_DIR="$SRC/target-steamos"
|
||||||
|
BIN="$TARGET_DIR/release/punktfunk-host"
|
||||||
|
CONFIG="$HOME/.config/punktfunk"
|
||||||
|
UNITS="$HOME/.config/systemd/user"
|
||||||
|
XRD="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
||||||
|
|
||||||
|
# --- 0. preflight ----------------------------------------------------------
|
||||||
|
log "Preflight"
|
||||||
|
[ -f /etc/os-release ] && . /etc/os-release || true
|
||||||
|
case "${ID:-}${ID_LIKE:-}" in
|
||||||
|
*steamos*|*arch*) ok "SteamOS / Arch base detected (${PRETTY_NAME:-unknown})" ;;
|
||||||
|
*) warn "This installer targets SteamOS; '${PRETTY_NAME:-unknown}' may differ — on a normal distro use the apt/rpm packages instead." ;;
|
||||||
|
esac
|
||||||
|
[ -d "$SRC/crates/punktfunk-host" ] || die "no punktfunk source at $SRC. Clone or rsync it there first, or pass --src=DIR (see scripts/steamdeck/README.md)."
|
||||||
|
ok "source: $SRC"
|
||||||
|
if ! have distrobox; then
|
||||||
|
die "distrobox not found. Install it once (no root needed):
|
||||||
|
curl -sfL https://raw.githubusercontent.com/89luca89/distrobox/main/install | sh -s -- --prefix ~/.local
|
||||||
|
then re-run this script (ensure ~/.local/bin is on PATH)."
|
||||||
|
fi
|
||||||
|
DISTROBOX="$(command -v distrobox)" # baked into the web unit (may be /usr/bin or ~/.local/bin)
|
||||||
|
ok "distrobox: $DISTROBOX"
|
||||||
|
|
||||||
|
# --- 1. build container + toolchain ---------------------------------------
|
||||||
|
log "Build container '$BOX' ($BOX_IMAGE)"
|
||||||
|
if distrobox list 2>/dev/null | awk -F'|' '{gsub(/ /,"",$2); print $2}' | grep -qx "$BOX"; then
|
||||||
|
ok "container '$BOX' exists"
|
||||||
|
else
|
||||||
|
log "creating '$BOX' (first time — pulls the image)…"
|
||||||
|
distrobox create --yes --name "$BOX" --image "$BOX_IMAGE" --home "$HOME"
|
||||||
|
ok "created '$BOX'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Provisioning build dependencies in '$BOX' (idempotent; apt + rustup + bun)"
|
||||||
|
# One non-interactive provisioning pass. APT deps mirror the Linux host build (FFmpeg/PipeWire/
|
||||||
|
# DRM/EGL/VAAPI dev libs); rustup + bun are per-user under the shared $HOME.
|
||||||
|
distrobox enter "$BOX" -- bash -lc '
|
||||||
|
set -e
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y -qq --no-install-recommends \
|
||||||
|
build-essential pkg-config clang curl git ca-certificates \
|
||||||
|
libavcodec-dev libavformat-dev libavutil-dev libavfilter-dev libswscale-dev libavdevice-dev \
|
||||||
|
libpipewire-0.3-dev libspa-0.2-dev \
|
||||||
|
libgbm-dev libegl-dev libgl-dev libdrm-dev libva-dev \
|
||||||
|
libxkbcommon-dev libudev-dev libssl-dev libopus-dev libsdl2-dev >/dev/null
|
||||||
|
command -v rustc >/dev/null 2>&1 || command -v ~/.cargo/bin/rustc >/dev/null 2>&1 || \
|
||||||
|
curl --proto =https --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path >/dev/null
|
||||||
|
command -v bun >/dev/null 2>&1 || command -v ~/.bun/bin/bun >/dev/null 2>&1 || \
|
||||||
|
curl -fsSL https://bun.sh/install | bash >/dev/null
|
||||||
|
'
|
||||||
|
ok "build deps ready"
|
||||||
|
|
||||||
|
# --- 2. build host (+ web) -------------------------------------------------
|
||||||
|
log "Building punktfunk-host (release) — first build is slow (~10-15 min)"
|
||||||
|
distrobox enter "$BOX" -- bash -lc "
|
||||||
|
set -e
|
||||||
|
export PATH=\$HOME/.cargo/bin:\$PATH CARGO_TARGET_DIR='$TARGET_DIR'
|
||||||
|
cd '$SRC' && cargo build -r -p punktfunk-host
|
||||||
|
"
|
||||||
|
[ -x "$BIN" ] || die "build did not produce $BIN"
|
||||||
|
ok "host binary: $BIN"
|
||||||
|
|
||||||
|
if [ "$WITH_WEB" = 1 ]; then
|
||||||
|
log "Building the management web console (bun)"
|
||||||
|
distrobox enter "$BOX" -- bash -lc "
|
||||||
|
set -e
|
||||||
|
export PATH=\$HOME/.bun/bin:\$PATH
|
||||||
|
cd '$SRC/web' && bun install --frozen-lockfile && bun run build
|
||||||
|
"
|
||||||
|
[ -f "$SRC/web/.output/server/index.mjs" ] || die "web build did not produce web/.output/server/index.mjs"
|
||||||
|
ok "web console built"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 3. config -------------------------------------------------------------
|
||||||
|
log "Configuration ($CONFIG)"
|
||||||
|
mkdir -p "$CONFIG"
|
||||||
|
if [ ! -f "$CONFIG/host.env" ]; then
|
||||||
|
cat > "$CONFIG/host.env" <<'EOF'
|
||||||
|
# punktfunk Steam Deck host config (sourced by the punktfunk-host user service).
|
||||||
|
# Auto encoder: VAAPI on the Deck's AMD GPU, NVENC on NVIDIA.
|
||||||
|
PUNKTFUNK_ENCODER=auto
|
||||||
|
# The host auto-detects the live session (Game Mode gamescope / Desktop KDE) per connect.
|
||||||
|
# Override the compositor only if detection misbehaves: PUNKTFUNK_COMPOSITOR=gamescope
|
||||||
|
EOF
|
||||||
|
ok "wrote host.env"
|
||||||
|
else
|
||||||
|
ok "host.env exists (left as-is)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$WITH_WEB" = 1 ] && [ ! -f "$CONFIG/web.env" ]; then
|
||||||
|
# Random login password + session secret for the web console, generated once.
|
||||||
|
# `|| true` swallows the SIGPIPE `tr` takes when `head` closes the pipe (pipefail would abort).
|
||||||
|
WEB_PW="$(LC_ALL=C tr -dc 'a-z0-9' </dev/urandom 2>/dev/null | head -c 12 || true)"
|
||||||
|
WEB_SECRET="$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom 2>/dev/null | head -c 32 || true)"
|
||||||
|
cat > "$CONFIG/web.env" <<EOF
|
||||||
|
PUNKTFUNK_UI_PASSWORD=$WEB_PW
|
||||||
|
PUNKTFUNK_UI_SECRET=$WEB_SECRET
|
||||||
|
EOF
|
||||||
|
chmod 600 "$CONFIG/web.env"
|
||||||
|
ok "wrote web.env (generated login password)"
|
||||||
|
else
|
||||||
|
[ "$WITH_WEB" = 1 ] && ok "web.env exists (login password unchanged)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 4. system tuning (needs sudo; skipped gracefully if unavailable) ------
|
||||||
|
log "System tuning (UDP buffers + input group) — needs sudo"
|
||||||
|
if sudo -n true 2>/dev/null; then
|
||||||
|
printf 'net.core.wmem_max=33554432\nnet.core.rmem_max=33554432\n' \
|
||||||
|
| sudo tee /etc/sysctl.d/99-punktfunk-net.conf >/dev/null
|
||||||
|
sudo sysctl -q -p /etc/sysctl.d/99-punktfunk-net.conf >/dev/null
|
||||||
|
ok "UDP socket buffers raised to 32 MB (persisted)"
|
||||||
|
if [ -f "$SRC/scripts/60-punktfunk.rules" ]; then
|
||||||
|
sudo install -m644 "$SRC/scripts/60-punktfunk.rules" /etc/udev/rules.d/60-punktfunk.rules
|
||||||
|
sudo udevadm control --reload-rules && sudo udevadm trigger || true
|
||||||
|
ok "installed udev rule (virtual gamepads)"
|
||||||
|
fi
|
||||||
|
id -nG "$USER" | grep -qw input || { sudo usermod -aG input "$USER"; warn "added $USER to 'input' group — log out/in (or reboot) for gamepad support"; }
|
||||||
|
else
|
||||||
|
warn "passwordless sudo unavailable — skipping UDP-buffer + udev tuning."
|
||||||
|
warn "Without it, high-bitrate streaming drops packets. Apply manually later:"
|
||||||
|
warn " echo -e 'net.core.wmem_max=33554432\\nnet.core.rmem_max=33554432' | sudo tee /etc/sysctl.d/99-punktfunk-net.conf && sudo sysctl --system"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 5. systemd user services ---------------------------------------------
|
||||||
|
log "Installing systemd user services"
|
||||||
|
mkdir -p "$UNITS"
|
||||||
|
SERVE_ARGS="serve --native --mgmt-bind 0.0.0.0:$MGMT_PORT"
|
||||||
|
[ "$OPEN" = 1 ] && SERVE_ARGS="$SERVE_ARGS --open"
|
||||||
|
cat > "$UNITS/punktfunk-host.service" <<EOF
|
||||||
|
# Generated by scripts/steamdeck/install.sh — punktfunk Steam Deck host (native binary).
|
||||||
|
[Unit]
|
||||||
|
Description=punktfunk host (GameStream + punktfunk/1)
|
||||||
|
After=pipewire.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
EnvironmentFile=%h/.config/punktfunk/host.env
|
||||||
|
Environment=XDG_RUNTIME_DIR=$XRD
|
||||||
|
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=$XRD/bus
|
||||||
|
ExecStart=$BIN $SERVE_ARGS
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=2
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
EOF
|
||||||
|
ok "punktfunk-host.service ($SERVE_ARGS)"
|
||||||
|
|
||||||
|
if [ "$WITH_WEB" = 1 ]; then
|
||||||
|
# The console is a Nitro/Node server run by bun; it lives in the build container (bun + node
|
||||||
|
# libs) and proxies to the host's loopback HTTPS mgmt API.
|
||||||
|
cat > "$UNITS/punktfunk-web.service" <<EOF
|
||||||
|
# Generated by scripts/steamdeck/install.sh — punktfunk web console (bun in the '$BOX' distrobox).
|
||||||
|
[Unit]
|
||||||
|
Description=punktfunk management web console
|
||||||
|
After=punktfunk-host.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=$DISTROBOX enter $BOX -- bash -lc 'export PATH=\$HOME/.bun/bin:\$PATH; cd $SRC/web; set -a; . $CONFIG/mgmt-token; . $CONFIG/web.env; set +a; export PUNKTFUNK_MGMT_URL=https://127.0.0.1:$MGMT_PORT NODE_TLS_REJECT_UNAUTHORIZED=0 PORT=$WEB_PORT HOST=0.0.0.0 NITRO_PORT=$WEB_PORT NITRO_HOST=0.0.0.0; exec bun run .output/server/index.mjs'
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=3
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
EOF
|
||||||
|
ok "punktfunk-web.service (port $WEB_PORT)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
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)"; }
|
||||||
|
systemctl --user enable --now punktfunk-host.service
|
||||||
|
ok "punktfunk-host started"
|
||||||
|
if [ "$WITH_WEB" = 1 ]; then
|
||||||
|
# The host writes the mgmt token on first start; give it a moment so the web unit finds it.
|
||||||
|
for _ in $(seq 1 10); do [ -f "$CONFIG/mgmt-token" ] && break; sleep 0.5; done
|
||||||
|
systemctl --user enable --now punktfunk-web.service
|
||||||
|
ok "punktfunk-web started"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 6. summary ------------------------------------------------------------
|
||||||
|
IP="$(ip -4 route get 1.1.1.1 2>/dev/null | sed -n 's/.* src \([0-9.]*\).*/\1/p' | head -1 || true)"
|
||||||
|
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 " • Pair a device: open the web console → Devices → arm pairing → enter the PIN on the client"
|
||||||
|
fi
|
||||||
|
if [ "$OPEN" = 1 ]; then
|
||||||
|
echo " • Mode: --open (unpaired clients accepted — trusted LAN only)"
|
||||||
|
else
|
||||||
|
echo " • Pairing required (secure default). From a client, pick this host and enter the PIN the host shows."
|
||||||
|
fi
|
||||||
|
echo " • Update later: bash $SRC/scripts/steamdeck/update.sh"
|
||||||
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# punktfunk — Steam Deck HOST update: rebuild from the current source + restart the services.
|
||||||
|
# Run on the Deck after pulling/rsyncing new source. Pairings, config, and the web login persist.
|
||||||
|
#
|
||||||
|
# bash scripts/steamdeck/update.sh # rebuild host (+web if installed) and restart
|
||||||
|
# bash scripts/steamdeck/update.sh --pull # `git pull` first (if the source is a git checkout)
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
|
||||||
|
ok() { printf '\033[1;32m ok\033[0m %s\n' "$*"; }
|
||||||
|
die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
SRC="${PUNKTFUNK_SRC:-$HOME/punktfunk}"
|
||||||
|
BOX="${PUNKTFUNK_BOX:-pf2}"
|
||||||
|
TARGET_DIR="$SRC/target-steamos"
|
||||||
|
[ -d "$SRC/crates/punktfunk-host" ] || die "no punktfunk source at $SRC (set PUNKTFUNK_SRC)"
|
||||||
|
WEB=0; [ -f "$HOME/.config/systemd/user/punktfunk-web.service" ] && WEB=1
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--pull" ]; then
|
||||||
|
if [ -d "$SRC/.git" ]; then log "git pull"; git -C "$SRC" pull --ff-only; ok "pulled"; else die "$SRC is not a git checkout — rsync new source then run without --pull"; fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Rebuilding host (release)"
|
||||||
|
distrobox enter "$BOX" -- bash -lc "set -e; export PATH=\$HOME/.cargo/bin:\$PATH CARGO_TARGET_DIR='$TARGET_DIR'; cd '$SRC' && cargo build -r -p punktfunk-host"
|
||||||
|
ok "host rebuilt"
|
||||||
|
if [ "$WEB" = 1 ]; then
|
||||||
|
log "Rebuilding web console"
|
||||||
|
distrobox enter "$BOX" -- bash -lc "set -e; export PATH=\$HOME/.bun/bin:\$PATH; cd '$SRC/web' && bun install --frozen-lockfile && bun run build"
|
||||||
|
ok "web rebuilt"
|
||||||
|
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
|
||||||
|
echo
|
||||||
|
log "Updated. Status: systemctl --user status punktfunk-host"
|
||||||
Reference in New Issue
Block a user