The stale code a default install/upgrade got was a TAG LEAK: deb.yml/rpm.yml shared
`tags: ['v*']` with the Apple-client release.yml, so the v0.1.0/v0.1.1 tags cut to ship
the macOS app ALSO published host packages versioned 0.1.1 — which outranks every rolling
0.0.1~ciN / 0.0.1-0.ciN build in both registries (dpkg/rpm version compares confirm), so
`apt install`/`rpm-ostree install` silently fetched ~99-commits-stale code while the READMEs
claimed auto-tracking. Two fixes:
- Decouple host publishing from Apple `v*` tags: deb.yml/rpm.yml now trigger on `host-v*`
only, so a client tag can never poison the host channel again.
- Bump the rolling base 0.0.1 -> 0.2.0 (deb `0.2.0~ciN`, rpm `0.2.0-0.ciN`): sits ABOVE the
stray 0.1.1 yet BELOW a future 0.2.0 tag, and still climbs monotonically by run number — so
`apt upgrade`/`rpm-ostree upgrade` genuinely move forward. Spec default + build scripts +
PKGBUILD pkgver bumped to match.
Build provenance (so a stale/shadowed host is detectable): build.rs stamps PUNKTFUNK_BUILD_VERSION
(set by CI = the full package version, e.g. 0.2.0~ci120.g802e98d; falls back to the crate version
for a plain `cargo build`) into the binary via rustc-env. Surfaced in `punktfunk-host --version`,
the startup log, and the mgmt /health + /host `version` field (was a hardcoded CARGO_PKG_VERSION).
Deliberately env-driven, not git-derived — the RPM builds from a git-archive tarball with no .git.
Version computed BEFORE the build in deb.yml; the spec %build exports it from %{version}-%{release}
(and gains --locked for reproducibility parity with the .deb path). Validated: plain build reports
0.0.1, env-stamped build reports 0.2.0~ci999.gdeadbee.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
punktfunk on Arch Linux / SteamOS
Packaging for punktfunk on Arch and Arch-derived immutable distros (SteamOS 3, etc.). The
PKGBUILD is a split package producing punktfunk-host (the gaming-rig host) and
punktfunk-client (the GTK4 couch/Deck client) — mirrors the rpm subpackages
(packaging/rpm/punktfunk.spec) and the deb build scripts. On a Steam Deck you want
punktfunk-client (it's what the Decky plugin launches); on a gaming
rig, punktfunk-host.
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
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.
⚠️ Host encode is NVENC-only today.
crates/punktfunk-host/src/encode/linux.rsimplementshevc_nvenc/av1_nvenc/h264_nvenc+ a CUDA zero-copy path — there is no VAAPI encoder. Sopunktfunk-hostworks on Arch + NVIDIA (incl.bazzite-deck-nvidia); an AMD Deck-as-host can't encode until ahevc_vaapibackend is added (a code change, not packaging). The client is unaffected —punktfunk-clientdecodes via VAAPI on AMD/Intel (the Deck) with a software fallback, so streaming to a Deck works today.
Arch Linux (mutable)
cd packaging/arch
# Build the working tree (CI / dev) — no git fetch:
PF_SRCDIR="$(git rev-parse --show-toplevel)" makepkg -f --holdver
# …or build the tagged release the AUR way:
makepkg -si
# …add the web console too (needs bun / bun-bin):
PF_WITH_WEB=1 PF_SRCDIR="$(git rev-parse --show-toplevel)" makepkg -f --holdver
Then the standard first-run (printed by the install scriptlet):
sudo usermod -aG input "$USER" # virtual gamepads; re-login after
mkdir -p ~/.config/punktfunk
cp /usr/share/punktfunk/host.env.bazzite ~/.config/punktfunk/host.env # gamescope backend
systemctl --user enable --now punktfunk-host
# Web console (if you installed the punktfunk-web package): enable it + read the login password.
systemctl --user enable --now punktfunk-web
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p' # open http://<host-ip>:3000
NVENC/EGL come from the NVIDIA driver: sudo pacman -S --needed nvidia-utils. Arch's stock
ffmpeg already has NVENC built in — no RPM-Fusion-style swap needed (unlike Fedora).
Runtime dependency map (Fedora/Debian → Arch)
| Need | Arch package |
|---|---|
| FFmpeg + NVENC | ffmpeg (NVENC built in) |
| PipeWire + Pulse + session mgr | pipewire pipewire-pulse wireplumber |
| Opus / input injection | opus libei |
| GL/EGL + gbm + xkb + wayland | libglvnd mesa libxkbcommon wayland |
| NVIDIA driver (NVENC/EGL/CUDA) | nvidia-utils (optdepend — never a hard dep) |
| Compositor backends | gamescope (≥3.16.22) / kwin / mutter / sway (optdepends) |
SteamOS 3 (immutable) — use a systemd-sysext
SteamOS has a read-only /usr on A/B partitions, and every OS update reimages the rootfs —
so steamos-readonly disable + pacman (and flatpak/distrobox) are fragile or unusable for a
host that needs /dev/uinput, /dev/uhid, the host PipeWire socket, the GPU render node, and the
right to spawn a compositor. The update-survivable, SteamOS-blessed mechanism is a
systemd-sysext: an overlay image merged read-only over /usr at boot, living in the writable
/var/lib/extensions/ (so it persists across A/B updates, no readonly-disable).
Build the package, then wrap its /usr payload into a sysext image:
# 1. build the pacman package (needs an Arch environment / container)
cd packaging/arch && PF_SRCDIR="$(git rev-parse --show-toplevel)" makepkg -f --holdver
# 2. turn it into a sysext .raw (extracts the package's /usr into an image + extension-release)
bash build-sysext.sh punktfunk-host-*.pkg.tar.zst
# 3. on the SteamOS box:
sudo cp punktfunk-host.raw /var/lib/extensions/
sudo systemctl enable --now systemd-sysext # merges it; survives OS updates
systemctl --user enable --now punktfunk-host # the user unit is now under /usr/lib
The udev rule, sysctl, and systemd user unit all live under /usr/lib, so the merged sysext
exposes them. systemd-sysext refresh re-merges after a reboot.
Steam Deck — the client (what the Decky plugin launches)
To stream to a Deck, you install punktfunk-client there — same sysext mechanism, but
wrapping the client package instead. The split makepkg produces both .pkg.tar.zst files; on the
Deck use the client one:
cd packaging/arch && PF_SRCDIR="$(git rev-parse --show-toplevel)" makepkg -f --holdver
bash build-sysext.sh punktfunk-client-*.pkg.tar.zst # → punktfunk-client.raw
# on the Deck:
sudo cp punktfunk-client.raw /var/lib/extensions/
sudo systemctl enable --now systemd-sysext
sudo pacman -S --needed libva-mesa-driver # VAAPI hw decode on the Deck's AMD APU
Now punktfunk-client is on PATH, so the Decky plugin finds and
launches it (punktfunk-client --connect host:port) — gamescope composites its video like a game.
The client needs no /dev/uinput or compositor-spawning rights (it captures input and decodes),
so it's a much lighter sysext than the host.
Files
PKGBUILD— split package:punktfunk-host+punktfunk-client(builds the working tree viaPF_SRCDIR, or a git tag for AUR).punktfunk-host.install/punktfunk-client.install— pacman scriptlets (udev reload + sysctl + first-run hint), mirror the RPM%post/ deb postinst.build-sysext.sh— wraps either built.pkg.tar.zstinto asystemd-sysext.rawfor SteamOS (derives the name from the package, so it works for host or client).