Files
enricobuehler 60406c9d72
ci / web (pull_request) Successful in 2m8s
apple / swift (pull_request) Successful in 2m18s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / bun-nix (pull_request) Successful in 31s
ci / docs-drift (pull_request) Successful in 36s
ci / rust-arm64 (pull_request) Successful in 2m57s
ci / docs-site (pull_request) Successful in 1m43s
android / android (pull_request) Successful in 6m54s
ci / rust (pull_request) Successful in 8m37s
Merge origin/main into the docs WP2 branch — bazzite.md was rewritten as bazzite.mdx here and re-touched by #342 there; #342's fact (the Gaming Mode takeover no longer needs the punktfunk group) is ported into bazzite.mdx and the four distro pages, and the Apple client's PUNKTFUNK_CORE_LOG_LEVEL (from #338) is baselined so docs-drift is green on main again.
2026-08-19 20:21:44 +02:00
..

punktfunk on Bazzite — packaging and install paths

The packager/ops view of the Bazzite (Fedora Atomic) target: which install path to ship, how each one is built and updated, and the gotchas of layered/bootc hosts. Setting a host up — udev and the input group, host.env, the service, the firewall, streaming the KDE desktop, verifying — is the docs' job: follow docs.punktfunk.unom.io/docs/bazzite (firewall specifics are on Fedora §4, same firewalld service definitions). Nothing here duplicates those pages. For the higher-level packaging rationale ("why not Flatpak", the build), see ../README.md.

⚠️ COPR note (Path C only). The legacy layering path's commands reference a COPR project named enricobuehler/punktfunk that is operator-run and may not be published (see packaging/copr/README.md); layer from the Gitea RPM registry instead (../rpm/README.md, the repo file https://git.unom.io/api/packages/unom/rpm/bazzite.repo) — it's what CI actually publishes to. Paths A (sysext) and B (bootc) don't involve the COPR at all.

1. Choose an install path

There are three paths on Bazzite, driven by different files in packaging/:

Path Driven by What it does Best for
A — systemd-sysext recommended packaging/bazzite/punktfunk-sysext.sh + build-sysext.sh (published by .gitea/workflows/rpm.yml) Overlays the host onto /usr as a system extension — no layering, no reboot, one-command updates Everyone; the default
B — bootc / OCI image packaging/bootc/Containerfile Bakes punktfunk into a FROM bazzite-nvidia image once; you bootc switch any number of hosts onto it Fleets, reproducible appliances, no per-host drift
C — rpm-ostree layering (legacy) packaging/rpm/ + the Gitea RPM registry Layers the punktfunk RPM onto your deployment with rpm-ostree install Only if you specifically want the RPM database to own the files

Why A over C: the Bazzite docs treat layering as a last resort — every layered package makes every OS update slower and can block upgrades entirely until removed. A sysext never enters an rpm-ostree transaction: it merges/unmerges at runtime, survives OS updates, and updating punktfunk is one command with no reboot (layering needs one per update). It's the mechanism the Fedora Atomic maintainers ship via fedora-sysexts.

Install, day-2 commands, channel switching, rollback, feed-signature refusals and the major-rebase behavior are all on the docs page — that's the walkthrough to hand a user. Packager-side facts:

  • CI (.gitea/workflows/rpm.yml) builds the image per Fedora major and publishes it to the feed …/packages/unom/generic/punktfunk-sysext/f<ver>[-canary]/SHA256SUMS plus a detached OpenPGP signature from packages@unom.io (AF245C506F4E4763, the RPM signing key). The public key is baked into punktfunk-sysext.sh; the script refuses a feed it can't verify (PUNKTFUNK_SYSEXT_ALLOW_UNSIGNED=1 is the documented escape hatch for pre-signing feeds).
  • The image embeds ID=fedora + VERSION_ID (matched through Bazzite's ID_LIKE), so after a major rebase the old image is refused instead of merging soname-broken binaries; feeds exist per Fedora major, from the same CI matrix as the RPM groups.
  • SELinux labels are baked into the image at build time (squashfs pseudo-xattrs computed from the targeted policy) — without them udev couldn't read the gamepad rule under enforcing. Validated live on Bazzite 43.
  • Install also applies what the RPM scriptlets would have (udev reload, sysctl) and seeds the two /etc files a sysext can't carry (the gamescope-session drop-in, the tray autostart entry), staged under /usr/share/punktfunk/etc/.

Path B — bootc image (FROM bazzite-nvidia)

The image is built off-host (on any machine with podman) from packaging/bootc/Containerfile, which bases on ghcr.io/ublue-os/bazzite-nvidia:stable (override with --build-arg BASE_IMAGE=…), enables RPM Fusion free + nonfree, adds the Gitea RPM repo (--build-arg PUNKTFUNK_RPM_GROUP=…, default bazzite), and installs the host and the web console (punktfunk punktfunk-web). It uses the Gitea registry rather than the COPR specifically because the registry carries punktfunk-web (COPR's mock chroot can't build it — no bun).

# Build + push (run from the repo root, on your builder machine):
podman build -t ghcr.io/<you>/bazzite-punktfunk -f packaging/bootc/Containerfile .
podman push  ghcr.io/<you>/bazzite-punktfunk

# On each target Bazzite host:
sudo bootc switch ghcr.io/<you>/bazzite-punktfunk && systemctl reboot

⚠️ The image installs from the Gitea RPM registry (group bazzite), so Path B depends on that registry being populated — CI (.gitea/workflows/rpm.yml) publishes punktfunk + punktfunk-web on every push to main. Packages are unsigned with GPG-signed metadata (repo_gpgcheck=1), matching packaging/rpm/README.md.

Path C — rpm-ostree layering (legacy)

Run on the Bazzite host. (Commands verbatim from packaging/README.md.)

# 1. RPM Fusion (free + nonfree) — provides the NVENC-capable ffmpeg-libs.
#    Usually already enabled on Bazzite; harmless to re-run.
rpm-ostree install \
  https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \
  https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm

# 2. Enable the punktfunk COPR repo  ⚠️ requires the COPR to be published (see callout above)
sudo wget -O /etc/yum.repos.d/_copr_punktfunk.repo \
  https://copr.fedorainfracloud.org/coprs/enricobuehler/punktfunk/repo/fedora-$(rpm -E %fedora)/

# 3. Layer punktfunk and reboot to activate the new deployment.
rpm-ostree install punktfunk
systemctl reboot

The reboot is mandatoryrpm-ostree install stages a new deployment that only takes effect on the next boot. This is normal atomic-distro behavior, not a punktfunk quirk.

Updating a Path-C host — rpm-ostree upgrade is NOT enough

⚠️ rpm-ostree upgrade will not update punktfunk on its own. upgrade bumps the base image and only re-resolves layered packages when the base changes. A Bazzite base can sit frozen for months (a pinned :stable tag, a paused rebase), so rpm-ostree upgrade keeps reporting "No updates available" and your layered punktfunk stays put even after new RPMs land in the repo. (Diagnose: rpm-ostree status shows the base Version: unchanged, while dnf -q repoquery --upgrades punktfunk lists newer builds.)

To actually pull a newer host on a static base, force rpm-ostree to re-resolve just the punktfunk layer — remove + re-add the same names in one transaction:

sudo rpm-ostree refresh-md --force
sudo rpm-ostree update \
  --uninstall punktfunk --uninstall punktfunk-web \
  --install   punktfunk --install   punktfunk-web
systemctl reboot

Or just run the helper, which detects what's layered and does the above. The punktfunk RPM installs it, so on a layered box it's already there — no checkout needed (it's the same command the web console's update hint prints):

sudo /usr/share/punktfunk/update-punktfunk.sh          # stage; reboot when ready
sudo /usr/share/punktfunk/update-punktfunk.sh --reboot # stage + reboot now

From a repo checkout (e.g. before the first install, or to run a newer helper than the layered RPM carries) the same script runs directly — it only shells out to rpm-ostree/rpm/systemctl:

sudo bash packaging/bazzite/update-punktfunk.sh --reboot

Channel gotcha: the re-resolve picks the highest version across every enabled /etc/yum.repos.d/punktfunk*.repo. If punktfunk-canary.repo is enabled alongside the stable punktfunk.repo, canary's <next-minor>.0-0.ciN outranks the stable X.Y.Z-1 and the box silently tracks canary. Enable exactly one channel — set enabled=0 in the other repo file.

2. First-run setup

All of it — input/punktfunk groups, host.env, enabling the service, the firewall, KDE desktop mode, verifying the first stream — lives on the docs page and applies identically to all three install paths. Don't restate it here; a fact stated twice is a fact that drifts (see "Where facts live" in CONTRIBUTING.md).

3. Gotchas

All user-facing ones live on the docs site now — the ffmpeg-libs weak dependency (NVENC fails at runtime without RPM Fusion's build: Troubleshooting) and the ds_inhibit SELinux storm with DualSense-type pads (Troubleshooting; the dontaudit-vs-allow rationale is the header of punktfunk-ds-inhibit.cil).

One packager-only note: scripts/punktfunk-host.service (the upstream/dev unit) assumes the binary at %h/punktfunk/target/release/punktfunk-host; the packaged binary is /usr/bin/punktfunk-host. If systemctl --user cat punktfunk-host shows ExecStart pointing into a home dir, drop an override (systemctl --user edit punktfunk-host) setting ExecStart=/usr/bin/punktfunk-host serve.

Appendix — if the COPR isn't published yet

The COPR (enricobuehler/punktfunk) is operator-run and may not be live. If rpm-ostree install punktfunk can't find the package, build the RPM yourself on a Fedora machine/toolbox (not Debian/Ubuntu — the host links system FFmpeg/PipeWire and won't build there), per packaging/README.md:

git archive --format=tar.gz --prefix=punktfunk-0.3.0/ \
  -o ~/rpmbuild/SOURCES/punktfunk-0.3.0.tar.gz HEAD    # 0.3.0 = the spec's default version
rpmbuild -ba packaging/rpm/punktfunk.spec    # needs the spec's BuildRequires + RPM Fusion

To publish the COPR for others (so rpm-ostree install punktfunk / the bootc image work), follow packaging/copr/README.md — create the project, point build-from-SCM at the repo with spec path packaging/rpm/punktfunk.spec, add RPM Fusion nonfree as an external repo, and select chroots matching your Bazzite Fedora base (rpm -E %fedora).