ci(rpm): publish punktfunk-host RPM to the Gitea registry (Bazzite)
ci / web (push) Failing after 44s
ci / rust (push) Successful in 1m7s
apple / swift (push) Successful in 1m16s
ci / docs-site (push) Failing after 38s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
deb / build-publish (push) Failing after 2m20s
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
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m21s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (push) Successful in 3m57s

Mirrors the apt pipeline for Fedora Atomic / Bazzite. New `rpm` workflow builds
the host RPM in a Fedora 43 builder image (ci/fedora-rpm.Dockerfile — matches
Bazzite's libavcodec.so.61, with a self-contained 16-symbol libcuda link stub so
no NVIDIA packages are needed in CI) and uploads to Gitea's public RPM registry
(group "bazzite") on every main push (rolling 0.0.1-0.ciN.<sha>) and v* tag
(clean X.Y.Z-1). Bazzite hosts then track it with `rpm-ostree upgrade`.

- packaging/rpm/build-rpm.sh: git-archive tarball + rpmbuild (--nodeps, since the
  toolchain is rustup + dnf, not RPMs); copies to dist/, asserts no cuda/nvidia leak.
- punktfunk.spec: overridable pf_version/pf_release for CI snapshots; exclude
  libcuda.so from auto-Requires (NVENC/EGL come from the driver, out of band) —
  same NVIDIA filter as the .deb; fix a bogus changelog weekday.
- docker.yml builds+pushes the new fedora-rpm image; packaging README + rpm/README
  document the rpm-ostree install/update path (recommended option).

Builder image seeded to the registry so rpm.yml's first run finds it. RPM build +
clean-Requires verified locally in the image (libavcodec.so.61 / libavutil.so.59,
no cuda).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 21:32:46 +00:00
parent e2257a6158
commit 58cb416abb
7 changed files with 258 additions and 4 deletions
+4
View File
@@ -2,6 +2,7 @@
# punktfunk-web — management console (web/Dockerfile, repo-root context)
# punktfunk-docs — documentation site (docs-site/Dockerfile)
# punktfunk-rust-ci — Rust CI builder image consumed by ci.yml
# punktfunk-fedora-rpm — Fedora 43 builder image consumed by rpm.yml (Bazzite RPM)
# Host and clients are intentionally NOT containerized (see CLAUDE.md "What's left").
#
# REGISTRY_TOKEN: repo Actions secret, a PAT with write:package scope.
@@ -38,6 +39,9 @@ jobs:
- image: punktfunk-rust-ci
dockerfile: ci/rust-ci.Dockerfile
context: ci
- image: punktfunk-fedora-rpm
dockerfile: ci/fedora-rpm.Dockerfile
context: ci
steps:
- uses: actions/checkout@v4
+71
View File
@@ -0,0 +1,71 @@
# Build the punktfunk-host RPM and publish it to Gitea's RPM package registry, so Bazzite /
# Fedora Atomic hosts layer + update it with rpm-ostree. Counterpart to deb.yml (apt). Runs in
# the Fedora 43 builder image (ci/fedora-rpm.Dockerfile) so the RPM's auto library Requires
# (libavcodec.so.NN, …) match the target's sonames.
#
# Registry (public, unom org), group "bazzite":
# repo file https://git.unom.io/api/packages/unom/rpm/bazzite.repo
# Box setup (once): see packaging/rpm/README.md
#
# REGISTRY_TOKEN: repo Actions secret, a PAT with write:package scope (shared with docker.yml).
name: rpm
on:
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
env:
REGISTRY: git.unom.io
OWNER: unom
RPM_GROUP: bazzite
jobs:
build-publish:
runs-on: ubuntu-24.04
container:
image: git.unom.io/unom/punktfunk-fedora-rpm:latest
timeout-minutes: 90
env:
CARGO_HOME: /usr/local/cargo
steps:
- uses: actions/checkout@v4
# rpmbuild + git archive need the checkout trusted; cache the crates download.
- name: Prep
run: git config --global --add safe.directory "$PWD"
- uses: actions/cache@v4
with:
path: /usr/local/cargo/registry
key: cargo-home-${{ hashFiles('Cargo.lock') }}
restore-keys: cargo-home-
- name: Version
# Tag v1.2.3 -> 1.2.3-1 (release); main push -> 0.0.1-0.ciN.g<sha>, whose release "0."
# sorts BEFORE the eventual "1" yet increases by run number, so `rpm-ostree upgrade`
# always moves to the newest main build.
run: |
case "$GITHUB_REF" in
refs/tags/v*) V="${GITHUB_REF_NAME#v}"; R="1" ;;
*) V="0.0.1"; R="0.ci${GITHUB_RUN_NUMBER}.g${GITHUB_SHA::8}" ;;
esac
echo "PF_VERSION=$V" >> "$GITHUB_ENV"
echo "PF_RELEASE=$R" >> "$GITHUB_ENV"
echo "rpm $V-$R"
- name: Build RPM
run: PF_VERSION="$PF_VERSION" PF_RELEASE="$PF_RELEASE" bash packaging/rpm/build-rpm.sh
- name: Publish to the Gitea RPM registry
env:
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
# Publish only the main package (skip -debuginfo/-debugsource subpackages).
for rpm in dist/*.rpm; do
case "$rpm" in *debuginfo*|*debugsource*) echo "skip $rpm"; continue;; esac
echo "uploading $rpm"
curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$rpm" \
"https://$REGISTRY/api/packages/$OWNER/rpm/$RPM_GROUP/upload"
done
echo "published to $OWNER/rpm/$RPM_GROUP"
+50
View File
@@ -0,0 +1,50 @@
# CI builder for the punktfunk RPM — Fedora 43 to match Bazzite's base (so the RPM's
# auto-generated library Requires, e.g. libavcodec.so.NN, pin to exactly what the target
# runs). Used by .gitea/workflows/rpm.yml; built+pushed by .gitea/workflows/docker.yml.
#
# docker build -f ci/fedora-rpm.Dockerfile -t punktfunk-fedora-rpm ci
#
# Mirrors ci/rust-ci.Dockerfile (the Ubuntu workspace builder) for the rpmbuild side.
FROM fedora:43
# RPM Fusion (free + nonfree) provides the NVENC-capable ffmpeg-devel the host links against.
RUN dnf -y 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" \
&& dnf -y install \
# rpmbuild + source-tarball tooling; nodejs runs the Gitea Actions JS (checkout/cache)
rpm-build rpmdevtools systemd-rpm-macros git tar gzip nodejs \
# build toolchain + bindgen
gcc gcc-c++ clang clang-devel cmake nasm pkgconf-pkg-config curl ca-certificates \
# ffmpeg (NVENC), capture/audio/display link deps
ffmpeg-devel pipewire-devel wayland-devel libxkbcommon-devel opus-devel \
mesa-libGL-devel mesa-libgbm-devel \
&& dnf clean all
# libcuda link stub — the zerocopy path links a fixed set of cuXxx driver symbols, but CI has
# no GPU and never RUNS CUDA. Rather than drag in the NVIDIA userspace stack, synthesize a stub
# libcuda.so.1 that just defines those symbols (the SAME approach the Ubuntu image takes with the
# real driver lib, minus the driver). On Bazzite the real driver provides libcuda.so.1 at runtime.
# The symbol list is `nm -D --undefined-only` of the built host binary; a new cu* call would fail
# the link with a clear "undefined reference", flagging this list to update.
RUN set -eux; : > /tmp/cuda_stub.c; \
for s in cuCtxCreate_v2 cuCtxSetCurrent cuCtxSynchronize cuDestroyExternalMemory \
cuDeviceGet cuExternalMemoryGetMappedBuffer cuGraphicsGLRegisterImage \
cuGraphicsMapResources cuGraphicsSubResourceGetMappedArray cuGraphicsUnmapResources \
cuGraphicsUnregisterResource cuImportExternalMemory cuInit cuMemAllocPitch_v2 \
cuMemcpy2D_v2 cuMemFree_v2; do \
echo "int $s(void){return 0;}" >> /tmp/cuda_stub.c; \
done; \
gcc -shared -fPIC -Wl,-soname,libcuda.so.1 -o /usr/lib64/libcuda.so.1 /tmp/cuda_stub.c; \
ln -sf libcuda.so.1 /usr/lib64/libcuda.so; \
rm -f /tmp/cuda_stub.c; ldconfig; test -e /usr/lib64/libcuda.so
# Rustup (not Fedora's packaged rust) so rust-toolchain.toml's pinned channel resolves, matching
# the Ubuntu builder. Shared location so jobs running as any uid can use it.
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --no-modify-path --profile minimal \
&& chmod -R a+w "$RUSTUP_HOME" "$CARGO_HOME" \
&& rustc --version && cargo --version
+14 -1
View File
@@ -33,7 +33,20 @@ On **Bazzite** the only genuinely new runtime bits are `ffmpeg-libs` (RPM Fusion
`libei` — the rest of the stack is already there. The default backend is **gamescope**
(`packaging/bazzite/host.env`), which the host spawns headless per session — no desktop login.
## Option A — COPR (per-host, `rpm-ostree install`)
## Option A — Gitea RPM registry (recommended; per-host, `rpm-ostree`)
The host's RPM is published to **unom's self-hosted Gitea RPM registry** (CI builds it on every
push), mirroring the [Debian/apt](debian/README.md) setup. Add one repo file, install, and track
updates with `rpm-ostree upgrade` — no COPR account needed. Full guide: [`rpm/README.md`](rpm/README.md).
```sh
curl -fsSL https://git.unom.io/api/packages/unom/rpm/bazzite.repo \
| sudo tee /etc/yum.repos.d/punktfunk.repo
rpm-ostree install punktfunk && systemctl reboot
# updates: rpm-ostree upgrade && systemctl reboot
```
## Option B — COPR (per-host, `rpm-ostree install`)
1. Create a COPR project, enable **build-from-SCM** pointing at this repo, spec path
`packaging/rpm/punktfunk.spec` (see `copr/README.md`). Under *External Repositories* add
+65
View File
@@ -0,0 +1,65 @@
# 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.0.1-0.ciN.<sha>` build) and on `v*` tags (a clean `X.Y.Z-1`). 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](../copr/README.md) / [bootc](../bootc/Containerfile)
paths — same spec (`punktfunk.spec`) — just self-hosted in Gitea instead of COPR, mirroring the
[Debian/apt](../debian/README.md) setup.
## Install on a Bazzite host (one-time)
```sh
# Trust + add the repo (rpm-ostree reads /etc/yum.repos.d). Public registry, no auth.
curl -fsSL https://git.unom.io/api/packages/unom/rpm/bazzite.repo \
| sudo tee /etc/yum.repos.d/punktfunk.repo
# Layer the package, then reboot into the new deployment.
rpm-ostree install punktfunk
systemctl reboot
```
After reboot, as the desktop user:
```sh
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
```
(See [`../bazzite/README.md`](../bazzite/README.md) for the full appliance walkthrough —
udev/group, `host.env`, the Steam session unit, firewall, verify.)
## Updates
```sh
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
```sh
PF_VERSION=0.0.1 bash packaging/rpm/build-rpm.sh # -> dist/punktfunk-0.0.1-1.fcNN.x86_64.rpm
```
Run it inside the Fedora 43 builder image so the deps resolve and match Bazzite:
```sh
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`.
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Build the punktfunk-host RPM from the committed tree, for the Gitea RPM registry (Bazzite).
#
# Counterpart to ../debian/build-deb.sh. The library Requires (libavcodec.so.NN, …) are
# auto-generated by rpmbuild from the binary it links — so build this in the Fedora 43 image
# (ci/fedora-rpm.Dockerfile) to match Bazzite's sonames. libcuda is excluded in the spec.
#
# Usage: PF_VERSION=0.0.1 [PF_RELEASE=0.ci42.gdeadbee] bash packaging/rpm/build-rpm.sh
# Output: dist/punktfunk-<version>-<release>.<arch>.rpm (+ the -debuginfo/-debugsource subpkgs)
set -euo pipefail
PF_VERSION="${PF_VERSION:-0.0.1}"
PF_RELEASE="${PF_RELEASE:-1}"
ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$ROOTDIR"
TOP="$(mktemp -d)"
trap 'rm -rf "$TOP"' EXIT
mkdir -p "$TOP"/{SOURCES,SPECS,BUILD,BUILDROOT,RPMS,SRPMS}
# Source tarball with the prefix %autosetup expects (punktfunk-<version>/). From HEAD so the
# build is reproducible from a commit (CI checks one out); the spec is read from the working
# tree directly, so spec edits apply without a re-commit.
git archive --format=tar.gz --prefix="punktfunk-${PF_VERSION}/" \
-o "$TOP/SOURCES/punktfunk-${PF_VERSION}.tar.gz" HEAD
# --nodeps: the spec's BuildRequires (cargo, rust, *-devel) are for COPR's mock chroot, which
# resolves them from RPMs. Our builder image provides the toolchain via rustup (so
# rust-toolchain.toml's pinned channel works) and the -devel libs via dnf, neither of which
# rpmbuild's RPM-level check sees — skip it; a genuinely missing dep fails the compile/link.
rpmbuild -bb --nodeps \
--define "_topdir $TOP" \
--define "pf_version ${PF_VERSION}" \
--define "pf_release ${PF_RELEASE}" \
packaging/rpm/punktfunk.spec
mkdir -p dist
find "$TOP/RPMS" -name '*.rpm' -exec cp -v {} dist/ \;
echo "== Requires (must NOT contain libcuda) =="
rpm -qp --requires dist/punktfunk-${PF_VERSION}-*.rpm 2>/dev/null | grep -iE 'cuda|nvidia' \
&& echo " !! NVIDIA/CUDA leak !!" || echo " clean"
+13 -3
View File
@@ -17,8 +17,12 @@
################################################################################
Name: punktfunk
Version: 0.0.1
Release: 1%{?dist}
# Version/Release are overridable so CI can stamp a rolling snapshot: a main build passes
# --define "pf_version 0.0.1" --define "pf_release 0.ci42.gdeadbee"
# (Release starting "0." sorts BEFORE the eventual "1" release), a v* tag passes the clean
# version with "pf_release 1". A plain `rpmbuild` (or COPR) with no defines builds 0.0.1-1.
Version: %{?pf_version}%{!?pf_version:0.0.1}
Release: %{?pf_release}%{!?pf_release:1}%{?dist}
Summary: Low-latency desktop/game streaming host (Moonlight-compatible + punktfunk/1)
License: MIT OR Apache-2.0
@@ -29,6 +33,12 @@ Source0: %{name}-%{version}.tar.gz
# punktfunk-host is Linux-only and links system FFmpeg/PipeWire/Opus.
ExclusiveArch: x86_64 aarch64
# The zerocopy FFI links the NVIDIA driver's libcuda.so.1; rpm's auto-dep generator would turn
# that into a hard Requires on libcuda.so.1 (and we never want to pin the driver — NVENC/EGL come
# from whatever NVIDIA stack the host runs, expressed below as the weak xorg-x11-drv-nvidia-cuda
# Recommends). Drop it from the auto-Requires, mirroring the Debian package's NVIDIA filter.
%global __requires_exclude ^libcuda\\.so.*$
# --- Build toolchain ---------------------------------------------------------
BuildRequires: cargo
BuildRequires: rust
@@ -131,5 +141,5 @@ echo "then enable the host: systemctl --user enable --now punktfunk-host"
echo "Config: cp %{_datadir}/%{name}/host.env.bazzite ~/.config/punktfunk/host.env"
%changelog
* Tue Jun 10 2026 punktfunk <noreply@anthropic.com> - 0.0.1-1
* Wed Jun 10 2026 punktfunk <noreply@anthropic.com> - 0.0.1-1
- Initial RPM: punktfunk-host + udev rule + systemd user unit + headless helpers.