Files
punktfunk/packaging/arch/README.md
T
enricobuehler e27718b406
apple / swift (push) Successful in 1m10s
apple / screenshots (push) Successful in 5m45s
android / android (push) Successful in 4m2s
arch / build-publish (push) Successful in 5m37s
ci / web (push) Successful in 1m4s
ci / docs-site (push) Successful in 1m9s
ci / rust (push) Successful in 4m39s
deb / build-publish (push) Successful in 2m56s
decky / build-publish (push) Successful in 14s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 4s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
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 4s
ci / bench (push) Successful in 4m41s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 10m8s
docker / deploy-docs (push) Successful in 6s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m54s
packaging: ship firewalld services on rpm + deb too, share from packaging/linux
Mirror the Arch firewalld service definitions into the RPM spec and the Debian
host package so every Linux packager installs them, and move the two XML files
to the shared packaging/linux/ home (alongside the .desktop files both the
PKGBUILD and deb scripts already source there) so there's one source of truth
instead of three drifting copies.

- rpm: install punktfunk-{gamestream,native}.xml to /usr/lib/firewalld/services/,
  list them in %files host, and print the firewalld enable command in %post
  (gated on firewall-cmd). Fedora/RHEL run firewalld by default, so this is where
  it matters most; Bazzite inherits it via the sysext built from the package /usr.
- deb: install both XMLs in build-deb.sh and add the same firewalld-gated hint to
  the postinst. Debian/Ubuntu ship no active firewall, so it's a no-op unless the
  admin runs firewalld.
- PKGBUILD + arch README updated to the packaging/linux/ path.
- Firewall docs (bazzite README now leads with --add-service; debian README gains
  a firewalld block) point at the shipped services; XML comments made
  distro-neutral. Never auto-enabled — packages don't touch the admin's firewall.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 22:37:45 +00:00

12 KiB
Raw Blame History

punktfunk on Arch Linux / SteamOS

Packaging for punktfunk on Arch and Arch-derived immutable distros. The PKGBUILD is a split package producing punktfunk-host (the gaming-rig host) and punktfunk-client (the native GTK4/libadwaita Linux client) — mirrors the rpm subpackages (packaging/rpm/punktfunk.spec) and the deb build scripts. On a Steam Deck used as a client you want punktfunk-client (it's what the Decky plugin launches); on a gaming 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 (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 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). bun is also the runtime — the console serves HTTPS (HTTP/1.1 over TLS) via Bun.serve, so the package vendors the bun binary (no nodejs dependency). A default makepkg builds only host+client with no JS tooling — mirroring the RPM spec's %bcond_with web.

Host encode: NVENC on NVIDIA, VAAPI on AMD/Intel (PUNKTFUNK_ENCODER=auto picks one). The host now has a VAAPI encoder + zero-copy dmabuf path alongside NVENC/CUDA, so punktfunk-host works on Arch + NVIDIA and AMD/Intel (incl. the Steam Deck — see the on-device path above). The client decodes via VAAPI on AMD/Intel with a software fallback.

CI (.gitea/workflows/arch.yml) builds this PKGBUILD in an archlinux:base-devel container on every push and publishes the packages to the Gitea Arch package registry — a plain pacman repo, so an Arch box installs and updates punktfunk with pacman -Syu like everything else. Two repos mirror the deb/rpm channels: punktfunk (release tags) and punktfunk-canary (rolling main-branch builds, versioned X.Y.Z-0.<run#> so a later release always outranks them). Enable exactly one.

The registry signs the repo database and every package, so first import its key into pacman's keyring (a one-time step — after this, packages install signature-verified):

# 1. Trust the registry signing key.
curl -fsS https://git.unom.io/api/packages/unom/arch/repository.key \
  | sudo pacman-key --add -
sudo pacman-key --lsign-key E0CA04465C99C936E0B0C6510A317015A34DDD69

# 2. Add the repo (pick ONE channel — punktfunk for releases, punktfunk-canary for main builds).
#    printf, not a heredoc, so this works in fish too (CachyOS's default shell has no `<<EOF`).
printf '\n[punktfunk]\nServer = https://git.unom.io/api/packages/unom/arch/$repo/$arch\n' \
  | sudo tee -a /etc/pacman.conf >/dev/null

# 3. Sync + install.
sudo pacman -Sy punktfunk-host        # gaming rig
sudo pacman -Sy punktfunk-client      # the native GTK4 Linux client
sudo pacman -Sy punktfunk-web         # optional browser management console

(No SigLevel line needed — pacman's default Required DatabaseOptional verifies the signed packages against the key you just trusted. Arch is rolling, so the packages are built against current Arch sonames — keep the box itself updated too.)

Then the same first-run steps as a source build (printed by the install scriptlet): input group, host.env, systemctl --user enable --now punktfunk-host — see the next section.

Build from source — 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 https://<host-ip>:47992

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.

Firewall

Stock Arch ships no firewall — every port is open by default, so there is nothing to do. Spins that enable one do not get their ports opened for you: an Arch package never touches the admin's running firewall. CachyOS is the common case — its installer turns on firewalld by default, so out of the box the host is unreachable until you allow it.

The punktfunk-host package ships firewalld service definitions (installed to /usr/lib/firewalld/services/) so enabling is one command — pick the plane your host serves:

# Reload once so firewalld picks up the just-installed service definition, add it, reload to apply.
sudo firewall-cmd --reload
sudo firewall-cmd --permanent --add-service=punktfunk-gamestream   # Moonlight/GameStream host
#                              --add-service=punktfunk-native       # …or the native-only host
sudo firewall-cmd --reload

punktfunk-gamestream opens the fixed Moonlight ports + mDNS; punktfunk-native opens the QUIC control port (UDP 9777) + mDNS. Enable both if the host runs serve --gamestream (which serves both planes). The data plane is an ephemeral UDP port negotiated per session, so there is no fixed data port in either service; a restrictive firewall must additionally allow a UDP range (the project does not pin one). The mgmt REST API (TCP 47990) binds to loopback by default — leave it closed unless you move it off loopback with --mgmt-bind IP:PORT (which then requires --mgmt-token).

For a non-firewalld setup, open the ports directly. The native punktfunk/1 plane:

  • QUIC control plane: UDP 9777 (serve --native-port N to change).
  • Data plane: an ephemeral UDP port — negotiated per session, so there is no fixed port to open. For a restrictive firewall you'd need to allow a UDP range (the repo does not pin one).

And the GameStream / Moonlight ports (fixed) — only needed if you run the host with serve --gamestream (opt-in, trusted LAN only); bare serve is native-only and doesn't open these:

Port Proto Purpose
47984 TCP HTTPS nvhttp (paired, mutual-TLS)
47989 TCP HTTP nvhttp (/serverinfo, /pair PIN flow)
48010 TCP RTSP handshake
4799848010 UDP Video RTP (+ FEC), ENet control (47999), audio (48000)
5353 UDP mDNS auto-discovery

The mgmt API (TCP 47990) binds to loopback by default — leave it closed unless you move it off loopback with --mgmt-bind IP:PORT (which then requires --mgmt-token).

With ufw:

sudo ufw allow 9777/udp                                 # punktfunk/1 control plane
sudo ufw allow 47984/tcp && sudo ufw allow 47989/tcp && sudo ufw allow 48010/tcp
sudo ufw allow 47998:48010/udp
sudo ufw allow 5353/udp
# plus the ephemeral punktfunk/1 data port — open a UDP range you reserve for it.

With raw nftables (add to your inet filter input chain):

udp dport 9777 accept                  # punktfunk/1 control plane
tcp dport { 47984, 47989, 48010 } accept
udp dport { 47998-48010, 5353 } accept
# plus the ephemeral punktfunk/1 data port (a reserved UDP range).

Files

  • PKGBUILD — split package: punktfunk-host + punktfunk-client (builds the working tree via PF_SRCDIR, or a git tag for AUR).
  • punktfunk-host.install / punktfunk-client.install — pacman scriptlets (udev reload + sysctl + first-run hint, incl. the firewalld enable command when firewalld is present), mirror the RPM %post / deb postinst.
  • The firewalld service definitions (punktfunk-gamestream.xml / punktfunk-native.xml) are shared across all Linux packaging and live in ../linux/; the host package installs them to /usr/lib/firewalld/services/ (not auto-enabled; see Firewall above).
  • build-sysext.sh — wraps either built .pkg.tar.zst into a systemd-sysext .raw for SteamOS (derives the name from the package, so it works for host or client).