From 58cb416abb31d22a842117622792a2ae7830ffd6 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Fri, 12 Jun 2026 21:32:46 +0000 Subject: [PATCH] ci(rpm): publish punktfunk-host RPM to the Gitea registry (Bazzite) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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.) 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) --- .gitea/workflows/docker.yml | 4 ++ .gitea/workflows/rpm.yml | 71 ++++++++++++++++++++++++++++++++++++ ci/fedora-rpm.Dockerfile | 50 +++++++++++++++++++++++++ packaging/README.md | 15 +++++++- packaging/rpm/README.md | 65 +++++++++++++++++++++++++++++++++ packaging/rpm/build-rpm.sh | 41 +++++++++++++++++++++ packaging/rpm/punktfunk.spec | 16 ++++++-- 7 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 .gitea/workflows/rpm.yml create mode 100644 ci/fedora-rpm.Dockerfile create mode 100644 packaging/rpm/README.md create mode 100755 packaging/rpm/build-rpm.sh diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml index 14b2ba5..45a116c 100644 --- a/.gitea/workflows/docker.yml +++ b/.gitea/workflows/docker.yml @@ -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 diff --git a/.gitea/workflows/rpm.yml b/.gitea/workflows/rpm.yml new file mode 100644 index 0000000..7773efc --- /dev/null +++ b/.gitea/workflows/rpm.yml @@ -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, 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" diff --git a/ci/fedora-rpm.Dockerfile b/ci/fedora-rpm.Dockerfile new file mode 100644 index 0000000..19e1a82 --- /dev/null +++ b/ci/fedora-rpm.Dockerfile @@ -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 diff --git a/packaging/README.md b/packaging/README.md index 8ee9f26..f89b881 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -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 diff --git a/packaging/rpm/README.md b/packaging/rpm/README.md new file mode 100644 index 0000000..2909593 --- /dev/null +++ b/packaging/rpm/README.md @@ -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.` 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`. diff --git a/packaging/rpm/build-rpm.sh b/packaging/rpm/build-rpm.sh new file mode 100755 index 0000000..b3deb43 --- /dev/null +++ b/packaging/rpm/build-rpm.sh @@ -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--..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-/). 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" diff --git a/packaging/rpm/punktfunk.spec b/packaging/rpm/punktfunk.spec index 7c4f67d..aaf57fa 100644 --- a/packaging/rpm/punktfunk.spec +++ b/packaging/rpm/punktfunk.spec @@ -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 - 0.0.1-1 +* Wed Jun 10 2026 punktfunk - 0.0.1-1 - Initial RPM: punktfunk-host + udev rule + systemd user unit + headless helpers.