From 1c76b8c7b429aa79bc43952636e3ad81244aecdc Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sat, 25 Jul 2026 13:45:35 +0200 Subject: [PATCH] build(rpm): let the spec build a client-only aarch64 RPM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The blocker was never ExclusiveArch — it was that %build builds host + tray + client together and %install lays down the host unconditionally, with no way to express "client only". `%bcond_without host` (default ON, so an x86_64 build is unchanged) now gates the host binary, the tray, the headless-session data, the firewalld services, the main %post and the bare %files section. Omitting %files for the MAIN package is the load-bearing part: it is what stops rpm emitting an empty `punktfunk` alongside punktfunk-client. build-rpm.sh exposes it as PF_WITHOUT_HOST=1 and skips the libcuda stub regeneration and the libcuda leak check, neither of which means anything without the host. Verified two ways. `rpmspec -P` shows the default expansion still carrying 3 host install lines, the tray build and one bare %files, while --without host carries none of those and one %files client. And a real rpmbuild in a native aarch64 Fedora 43 container produced punktfunk-client-*.aarch64.rpm with correct aarch64 sonames (libc.so.6(GLIBC_2.17), libSDL3.so.0, libavcodec.so.61) and no main package. Not a cross-compile: %build runs cargo for the build machine's arch, so this wants an arm64 builder. No CI leg yet — deliberate. Co-Authored-By: Claude Opus 5 (1M context) --- packaging/rpm/README.md | 21 +++++++++++++++++++++ packaging/rpm/build-rpm.sh | 21 ++++++++++++++++----- packaging/rpm/punktfunk.spec | 31 +++++++++++++++++++++++++++---- 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/packaging/rpm/README.md b/packaging/rpm/README.md index 2a5bf3e3..effba626 100644 --- a/packaging/rpm/README.md +++ b/packaging/rpm/README.md @@ -123,3 +123,24 @@ docker run --rm -v "$PWD:/src" -w /src punktfunk-fedora-rpm \ A plain `rpmbuild`/COPR build with no `pf_version`/`pf_release` defines produces `0.3.0-1` (the spec defaults). + +### aarch64 — the client RPM + +The **client** builds for aarch64; the **host** does not (its encode stack is NVENC/QSV/AMF, all +x86). `PF_WITHOUT_HOST=1` drops the host binary, the tray, the headless-session data, the +firewalld services and the main package's `%files`, leaving exactly one RPM: `punktfunk-client`. +Omitting the main `%files` is what keeps rpm from emitting an empty `punktfunk` next to it. + +This is **not** a cross-compile — `%build` runs cargo for the host architecture, so run it on an +arm64 machine (or an emulated arm64 container, which is very slow): + +```sh +docker build --platform linux/arm64 -f ci/fedora-rpm.Dockerfile -t punktfunk-fedora-rpm-arm64 ci +docker run --rm --platform linux/arm64 -v "$PWD:/src" -w /src punktfunk-fedora-rpm-arm64 \ + bash -lc 'git config --global --add safe.directory /src && \ + PF_VERSION=0.0.1 PF_WITHOUT_HOST=1 bash packaging/rpm/build-rpm.sh' +# -> dist/punktfunk-client-0.0.1-1.fcNN.aarch64.rpm +``` + +`PF_WITHOUT_HOST=1` works on x86_64 too, if you only want the client RPM. The flag is orthogonal +to the architecture; it is just that aarch64 has no other option. diff --git a/packaging/rpm/build-rpm.sh b/packaging/rpm/build-rpm.sh index 79cf9229..c500183b 100755 --- a/packaging/rpm/build-rpm.sh +++ b/packaging/rpm/build-rpm.sh @@ -19,6 +19,11 @@ WEB_OPT=() # requirement as web; default off so a bare `rpmbuild`/COPR still works. SCRIPTING_OPT=() [ "${PF_WITH_SCRIPTING:-0}" = "1" ] && SCRIPTING_OPT=(--with scripting) +# PF_WITHOUT_HOST=1 drops the host binary, the tray and the main package, leaving only +# punktfunk-client. This is what an aarch64 build uses: the client is portable, the host's encode +# stack (NVENC/QSV/AMF) is x86. Harmless on x86_64 too, if you only want the client RPM. +HOST_OPT=() +[ "${PF_WITHOUT_HOST:-0}" = "1" ] && HOST_OPT=(--without host) ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)" cd "$ROOTDIR" @@ -39,7 +44,7 @@ git archive --format=tar.gz --prefix="punktfunk-${PF_VERSION}/" \ # undefined cuStreamCreateWithPriority/cuMemcpy2DAsync_v2/…; this regen supersedes it.) Defining # extra unused symbols is harmless; a missing one fails the link. Only when /usr/lib64 is writable # (CI image runs as root) — COPR/mock provides the real cuda-cudart-devel stub instead. -if [ "$(id -u)" = 0 ] && [ -d /usr/lib64 ]; then +if [ "${PF_WITHOUT_HOST:-0}" != "1" ] && [ "$(id -u)" = 0 ] && [ -d /usr/lib64 ]; then CU_SYMS="$(grep -rhoE '\bcu[A-Z][A-Za-z0-9_]*' crates/punktfunk-host/src/ | sort -u || true)" if [ -n "$CU_SYMS" ]; then STUB_C="$(mktemp --suffix=.c)" @@ -55,7 +60,7 @@ fi # 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 "${WEB_OPT[@]}" "${SCRIPTING_OPT[@]}" \ +rpmbuild -bb --nodeps "${WEB_OPT[@]}" "${SCRIPTING_OPT[@]}" "${HOST_OPT[@]}" \ --define "_topdir $TOP" \ --define "pf_version ${PF_VERSION}" \ --define "pf_release ${PF_RELEASE}" \ @@ -63,6 +68,12 @@ rpmbuild -bb --nodeps "${WEB_OPT[@]}" "${SCRIPTING_OPT[@]}" \ 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" +# The libcuda leak check applies to the HOST package (the only thing that links the driver); a +# client-only build has no such package to inspect. +if [ "${PF_WITHOUT_HOST:-0}" = "1" ]; then + echo "== client-only build: no host RPM, libcuda check not applicable ==" +else + 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" +fi diff --git a/packaging/rpm/punktfunk.spec b/packaging/rpm/punktfunk.spec index a8719e3f..140ebbf8 100644 --- a/packaging/rpm/punktfunk.spec +++ b/packaging/rpm/punktfunk.spec @@ -31,10 +31,10 @@ URL: https://git.unom.io/unom/punktfunk # COPR SCM builds provide the checkout; for a tarball build, drop a git archive here: Source0: %{name}-%{version}.tar.gz -# punktfunk-host is Linux-only and links system FFmpeg/PipeWire/Opus. x86_64 only for now: encode -# is NVENC (desktop NVIDIA) and no aarch64 build is produced/published by CI — claiming aarch64 -# here would advertise an arch we never ship. Re-add aarch64 once there's an arm64 build leg. -ExclusiveArch: x86_64 +# punktfunk-host is Linux-only and links system FFmpeg/PipeWire/Opus. The HOST is x86_64 only — +# its encode stack is NVENC/QSV/AMF — but the CLIENT builds and runs fine on aarch64, so the spec +# accepts both arches and `--without host` (below) selects the client-only build. +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 @@ -55,6 +55,14 @@ ExclusiveArch: x86_64 # Debian punktfunk-scripting .deb. %bcond_with scripting +# The HOST half of this spec (the punktfunk package itself + the tray). ON by default, so an +# ordinary x86_64 build is unchanged. `--without host` drops the host binary, the tray, the +# headless-session data, the firewalld services and the main %%files section entirely, leaving +# only punktfunk-client — which is what an aarch64 build produces, since the host's encode stack +# (NVENC/QSV/AMF) is x86 and the client's is not. Omitting the main %%files is what stops rpm +# from emitting an empty `punktfunk` package alongside the client. +%bcond_without host + # --- Build toolchain --------------------------------------------------------- BuildRequires: cargo BuildRequires: rust @@ -211,14 +219,21 @@ export PUNKTFUNK_BUILD_VERSION="%{version}-%{release}" # with real RFI (clean P-frame recovery anchor via DPB reference slots; design/linux-vulkan-video-encode.md). # Pure Rust `ash` (no new lib / no link-time dep); default on for HEVC (PUNKTFUNK_VULKAN_ENCODE=0 opts # back to libav VAAPI), and a failed open falls back to VAAPI so unsupported devices degrade gracefully. +%if %{with host} cargo build --release --locked --features punktfunk-host/nvenc,punktfunk-host/vulkan-encode \ -p punktfunk-host -p punktfunk-client-linux -p punktfunk-client-session +%else +# Client-only (aarch64): no host crate, so none of the encode features apply. +cargo build --release --locked -p punktfunk-client-linux -p punktfunk-client-session +%endif # The status tray in its OWN cargo invocation — load-bearing, not tidiness. Cargo unifies features # across everything in one build, so co-building the tray with the host pulls the host's # ashpd -> zbus/tokio onto the tray's shared zbus; the tray (ksni async-io + blocking, no tokio # runtime by design) then panics at startup ("there is no reactor running, must be called from the # context of a Tokio 1.x runtime"). Built alone, its zbus stays on async-io. (Same split the .deb does.) +%if %{with host} cargo build --release --locked -p punktfunk-tray +%endif %if %{with web} # Management web console: build the Nitro SSR bundle with bun (the `bun` preset + our Bun.serve @@ -243,6 +258,7 @@ fi %endif %install +%if %{with host} # Binary install -Dm0755 target/release/punktfunk-host %{buildroot}%{_bindir}/punktfunk-host @@ -294,6 +310,7 @@ for sz in 22x22 48x48; do install -Dm0644 "$png" %{buildroot}%{_datadir}/icons/hicolor/$sz/apps/"$(basename "$png")" done done +%endif # --- client subpackage --- install -Dm0755 target/release/punktfunk-client %{buildroot}%{_bindir}/punktfunk-client @@ -311,6 +328,7 @@ install -Dm0644 scripts/70-punktfunk-client.rules \ install -Dm0644 scripts/99-punktfunk-client-net.conf \ %{buildroot}%{_prefix}/lib/sysctl.d/99-punktfunk-client-net.conf +%if %{with host} # Headless session helpers + example config + OpenAPI doc (reference material). install -d %{buildroot}%{_datadir}/%{name}/headless install -Dm0755 scripts/headless/run-headless-kde.sh %{buildroot}%{_datadir}/%{name}/headless/run-headless-kde.sh @@ -344,6 +362,7 @@ install -Dm0644 packaging/linux/punktfunk-native.xml \ # Web console opener (TCP 47992) — only meaningful with the web subpackage, opened deliberately. install -Dm0644 packaging/linux/punktfunk-web.xml \ %{buildroot}%{_prefix}/lib/firewalld/services/punktfunk-web.xml +%endif %if %{with web} # --- web console subpackage (punktfunk-web) --- @@ -383,6 +402,7 @@ chmod 0755 %{buildroot}%{_bindir}/punktfunk-scripting install -Dm0644 scripts/punktfunk-scripting.service %{buildroot}%{_userunitdir}/punktfunk-scripting.service %endif +%if %{with host} %files %license LICENSE-MIT LICENSE-APACHE THIRD-PARTY-NOTICES.txt %doc README.md packaging/README.md @@ -407,6 +427,7 @@ install -Dm0644 scripts/punktfunk-scripting.service %{buildroot}%{_userunitdir}/ %config(noreplace) /etc/gamescope-session-plus/sessions.d/steam %dir %{_datadir}/%{name} %{_datadir}/%{name}/* +%endif %files client %license LICENSE-MIT LICENSE-APACHE THIRD-PARTY-NOTICES.txt @@ -450,6 +471,7 @@ udevadm trigger --subsystem-match=hidraw 2>/dev/null || : # rpm-ostree it takes effect on the next boot into the layered deployment). sysctl -p %{_prefix}/lib/sysctl.d/99-punktfunk-client-net.conf >/dev/null 2>&1 || : +%if %{with host} %post # Reload udev so /dev/uinput picks up the new rule without a reboot (best-effort). udevadm control --reload-rules 2>/dev/null || : @@ -474,6 +496,7 @@ if command -v punktfunk-host >/dev/null 2>&1; then echo "$conflict" fi fi +%endif %if %{with web} %post web