Files
punktfunk/packaging/rpm
enricobuehler 59bcfa1a12
ci / rust (push) Successful in 1m10s
ci / web (push) Successful in 29s
android / android (push) Failing after 1m48s
ci / docs-site (push) Successful in 31s
ci / bench (push) Successful in 1m46s
decky / build-publish (push) Successful in 12s
apple / swift (push) Successful in 53s
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 4s
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 3s
deb / build-publish (push) Failing after 2m39s
flatpak / build-publish (push) Successful in 4m1s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 5m13s
docker / deploy-docs (push) Successful in 20s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 4m51s
fix(ci): rpm signing uses rpm's default signer; flatpak installs node before checkout
Two CI fixes:
- rpm signing (2nd bug): overriding %__gpg_sign_cmd via --define reached gpg with
  %{__plaintext_filename}/%{__signature_filename} UNEXPANDED ("No such file or directory").
  Stop overriding it — use rpm's default signer (which expands those correctly) and just set
  _gpg_name; a passphrase-less key + loopback in gpg.conf makes gpg sign headless. (Requires a
  passphrase-less signing key, as the runbook's %no-protection key is.)
- flatpak: the job runs in fedora:43 which has no node, so actions/checkout (a JS action) failed
  with "node: not found". Install nodejs in a plain `run:` step (shell, no node needed) before
  checkout. Also scope the heavy flatpak-builder run to client/core/manifest changes (+ tags) so
  it stops rebuilding on every unrelated docs/host push (tag pushes still build — paths filters
  only branch pushes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 13:54:43 +00:00
..

punktfunk-host — RPM (Bazzite / Fedora Atomic) via the Gitea registry

punktfunk-host is published as an RPM to Gitea's RPM package registry in the public unom org (group bazzite), so Bazzite / Fedora Atomic hosts layer and update it with rpm-ostree. CI (.gitea/workflows/rpm.yml) builds and publishes on every push to main (a rolling 0.2.0-0.ciN.<sha> build, which outranks the stray 0.1.1 so rpm-ostree upgrade always gets the latest — no version pin needed) and on host-scoped host-v* tags (a clean X.Y.Z-1; the Apple client's v* tags deliberately do not publish a host RPM). The RPM is built in the Fedora 43 image (ci/fedora-rpm.Dockerfile) so its auto-generated library Requires (libavcodec.so.NN, …) match Bazzite's sonames; the NVIDIA driver lib (libcuda.so.1) is excluded — NVENC/EGL come from whatever NVIDIA stack the host runs (a weak Recommends).

This is the same package as the COPR / bootc paths — same spec (punktfunk.spec) — just self-hosted in Gitea instead of COPR, mirroring the Debian/apt setup.

Install on a Bazzite host (one-time)

# Add the repo. Our RPMs are unsigned, but Gitea GPG-signs the repo METADATA — so verify that
# (repo_gpgcheck=1) and skip the per-package signature check (gpgcheck=0). The signed metadata
# carries each package's SHA256, so authenticity still holds. (Don't just curl Gitea's served
# bazzite.repo — it sets gpgcheck=1, which fails on unsigned packages.)
sudo tee /etc/yum.repos.d/punktfunk.repo >/dev/null <<'REPO'
[gitea-unom-bazzite]
name=punktfunk (unom, Bazzite)
baseurl=https://git.unom.io/api/packages/unom/rpm/bazzite
enabled=1
gpgcheck=0
repo_gpgcheck=1
gpgkey=https://git.unom.io/api/packages/unom/rpm/repository.key
REPO

# Layer the host + the web console (pairing/status), then reboot into the new deployment.
# (punktfunk Recommends punktfunk-web; list it explicitly so it's pulled regardless of weak-dep
# settings. The registry carries punktfunk-web because CI builds the spec --with web; COPR can't.)
rpm-ostree install punktfunk punktfunk-web
systemctl reboot

If rpm-ostree can't complete the metadata GPG check non-interactively, set repo_gpgcheck=0 (TLS-only trust to the self-hosted registry).

Enabling per-package signing (gpgcheck=1)

CI is wired to GPG-sign each RPM (packaging/rpm/sign-rpms.sh, run from rpm.yml), but it's dormant until you provide a signing key — until then packages publish unsigned and the repo above uses gpgcheck=0. This is a self-hosted registry served over HTTPS with GPG-signed metadata (repo_gpgcheck=1), so per-package signing is hardening, not a correctness fix. (Note: this is a GPG/OpenPGP key — a step-ca/X.509 cert can't sign RPMs; step-ca is for the registry/console TLS.)

One-time setup:

# 1. Generate a DEDICATED, passphrase-less signing key (separate from the Gitea registry key).
gpg --batch --gen-key <<EOF
%no-protection
Key-Type: eddsa
Key-Curve: ed25519
Name-Real: punktfunk packages
Name-Email: packages@unom.io
Expire-Date: 0
%commit
EOF
gpg --armor --export-secret-keys packages@unom.io   # -> paste into the CI secret below
gpg --armor --export             packages@unom.io > RPM-GPG-KEY-punktfunk   # the PUBLIC key

# 2. In the repo's Gitea Actions secrets, add RPM_GPG_PRIVATE_KEY = the armored PRIVATE key
#    (and RPM_GPG_PASSPHRASE only if the key has one). The next CI run signs + self-verifies.

# 3. Publish RPM-GPG-KEY-punktfunk where clients can fetch it, then on each host import it and
#    flip the repo to gpgcheck=1:
sudo rpm --import https://git.unom.io/.../RPM-GPG-KEY-punktfunk
sudo sed -i 's/^gpgcheck=0/gpgcheck=1/' /etc/yum.repos.d/punktfunk.repo

Do not flip gpgcheck=1 before a signed build has published, or installs will fail.

After reboot, as the desktop user:

ujust add-user-to-input-group           # virtual gamepads need /dev/uinput (re-login).
                                        # Bazzite is atomic — use ujust, NOT `usermod -aG input`.
mkdir -p ~/.config/punktfunk
cp /usr/share/punktfunk/host.env.bazzite ~/.config/punktfunk/host.env   # gamescope defaults
systemctl --user enable --now punktfunk-host
# Web console — enable it and read the auto-generated login password (then open http://<host-ip>:3000):
systemctl --user enable --now punktfunk-web
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'

(See ../bazzite/README.md for the full appliance walkthrough — udev/group, host.env, the Steam session unit, firewall, verify.)

Updates

rpm-ostree upgrade            # pulls the newest punktfunk with the system update
systemctl reboot             # rpm-ostree changes apply on reboot

Layered packages are re-resolved against their repos on every rpm-ostree upgrade, so the box tracks new builds automatically (Bazzite's auto-update timer does this for you). To pin or stop tracking: rpm-ostree override / rpm-ostree uninstall punktfunk.

Build an RPM locally

PF_VERSION=0.0.1 bash packaging/rpm/build-rpm.sh                # host + client
PF_VERSION=0.0.1 PF_WITH_WEB=1 bash packaging/rpm/build-rpm.sh  # + the noarch punktfunk-web (needs bun on PATH)
# -> dist/punktfunk-0.0.1-1.fcNN.x86_64.rpm  (+ punktfunk-web-0.0.1-1.fcNN.noarch.rpm with PF_WITH_WEB=1)

Run it inside the Fedora 43 builder image so the deps resolve and match Bazzite:

docker build -f ci/fedora-rpm.Dockerfile -t punktfunk-fedora-rpm ci
docker run --rm -v "$PWD:/src" -w /src punktfunk-fedora-rpm \
  bash -lc 'git config --global --add safe.directory /src && PF_VERSION=0.0.1 bash packaging/rpm/build-rpm.sh'

A plain rpmbuild/COPR build with no pf_version/pf_release defines produces 0.0.1-1.