From 4ff6f447a8eb4ffe98ba2dd008ff55095926be1c Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Fri, 12 Jun 2026 23:18:12 +0000 Subject: [PATCH] ci(packaging): punktfunk-client .deb + RPM subpackage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hook the Linux client into the existing packaging CI: - deb.yml builds both binaries and publishes punktfunk-host AND punktfunk-client to the Gitea apt registry; new packaging/debian/build-client-deb.sh mirrors the host script (shlibdeps auto-Depends — GTK4/libadwaita/SDL3/FFmpeg/PipeWire sonames; no NVIDIA filter, the client links no CUDA). Built and inspected locally on Ubuntu 26.04. - punktfunk.spec gains a "client" subpackage (binary + desktop entry + udev rule); rpm.yml's publish loop picks it up unchanged. - New shared assets: packaging/linux/io.unom.Punktfunk.desktop and scripts/70-punktfunk-client.rules — DualSense hidraw uaccess (USB + Bluetooth, steam-devices style) so SDL's HIDAPI driver gets touchpad/motion/lightbar/triggers instead of degrading to evdev. - Builder images learn the client link deps (rust-ci already had them; fedora-rpm adds gtk4/libadwaita/SDL3-devel) with idempotent install steps in deb.yml/rpm.yml since jobs run against the previous push's image. Workspace check CI (build/clippy/test) already covers the crate since f09def4. Co-Authored-By: Claude Fable 5 --- .gitea/workflows/deb.yml | 44 +++++--- .gitea/workflows/rpm.yml | 7 +- ci/fedora-rpm.Dockerfile | 2 + packaging/debian/build-client-deb.sh | 120 ++++++++++++++++++++++ packaging/linux/io.unom.Punktfunk.desktop | 10 ++ packaging/rpm/punktfunk.spec | 42 +++++++- scripts/70-punktfunk-client.rules | 12 +++ 7 files changed, 217 insertions(+), 20 deletions(-) create mode 100644 packaging/debian/build-client-deb.sh create mode 100644 packaging/linux/io.unom.Punktfunk.desktop create mode 100644 scripts/70-punktfunk-client.rules diff --git a/.gitea/workflows/deb.yml b/.gitea/workflows/deb.yml index 02bbeff..1e45d98 100644 --- a/.gitea/workflows/deb.yml +++ b/.gitea/workflows/deb.yml @@ -1,7 +1,8 @@ -# Build the punktfunk-host .deb and publish it to Gitea's Debian package registry, so the -# Ubuntu hosts get new builds via `apt update && apt upgrade`. Runs inside the same Ubuntu -# 26.04 rust-ci builder image as ci.yml, so dpkg-shlibdeps pins the runtime lib package names -# (libavcodec62, libpipewire-0.3-0t64, …) to exactly what the target boxes run. +# Build the punktfunk-host and punktfunk-client .debs and publish them to Gitea's Debian +# package registry, so Ubuntu boxes get new builds via `apt update && apt upgrade`. Runs +# inside the same Ubuntu 26.04 rust-ci builder image as ci.yml, so dpkg-shlibdeps pins the +# runtime lib package names (libavcodec62, libpipewire-0.3-0t64, …) to exactly what the +# target boxes run. # # Registry (public, unom org): https://git.unom.io/unom/-/packages # Box setup (once): see packaging/debian/README.md @@ -30,9 +31,15 @@ jobs: steps: - uses: actions/checkout@v4 - # dpkg-shlibdeps (Depends resolution) + dpkg-deb live in dpkg-dev. - - name: dpkg-dev - run: apt-get update && apt-get install -y --no-install-recommends dpkg-dev + # dpkg-shlibdeps (Depends resolution) + dpkg-deb live in dpkg-dev. The client's link + # deps are also baked into the rust-ci image, but this job runs against the image + # from the PREVIOUS push (docker.yml bootstrap note) — keep it green across image + # changes; a no-op once the image has them. + - name: dpkg-dev + client link deps + run: | + apt-get update + apt-get install -y --no-install-recommends dpkg-dev \ + libgtk-4-dev libadwaita-1-dev libsdl3-dev # Share ci.yml's cache keys so the release build reuses its registry + target artifacts. - name: Cache keys @@ -50,10 +57,10 @@ jobs: key: cargo-target-${{ env.rustc }}-${{ hashFiles('Cargo.lock') }} restore-keys: cargo-target-${{ env.rustc }}- - - name: Build release host + - name: Build release host + client run: | git config --global --add safe.directory "$PWD" - cargo build --release -p punktfunk-host --locked + cargo build --release -p punktfunk-host -p punktfunk-client-linux --locked - name: Version # Tag v1.2.3 -> 1.2.3 (a real release); a main push -> 0.0.1~ciN.g, which sorts @@ -68,16 +75,19 @@ jobs: echo "VERSION=$V" >> "$GITHUB_ENV" echo "package version $V" - - name: Build .deb - run: VERSION="$VERSION" bash packaging/debian/build-deb.sh + - name: Build .debs + run: | + VERSION="$VERSION" bash packaging/debian/build-deb.sh + VERSION="$VERSION" bash packaging/debian/build-client-deb.sh - name: Publish to the Gitea apt registry env: TOKEN: ${{ secrets.REGISTRY_TOKEN }} run: | - DEB="$(ls dist/*.deb)" - echo "uploading $DEB" - # PAT owner (enricobuehler), not the push actor — matches docker.yml's registry login. - curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$DEB" \ - "https://$REGISTRY/api/packages/$OWNER/debian/pool/$DISTRIBUTION/$COMPONENT/upload" - echo "published $DEB to $OWNER/debian $DISTRIBUTION/$COMPONENT" + for DEB in dist/*.deb; do + echo "uploading $DEB" + # PAT owner (enricobuehler), not the push actor — matches docker.yml's registry login. + curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$DEB" \ + "https://$REGISTRY/api/packages/$OWNER/debian/pool/$DISTRIBUTION/$COMPONENT/upload" + done + echo "published to $OWNER/debian $DISTRIBUTION/$COMPONENT" diff --git a/.gitea/workflows/rpm.yml b/.gitea/workflows/rpm.yml index 2c769a6..118af4d 100644 --- a/.gitea/workflows/rpm.yml +++ b/.gitea/workflows/rpm.yml @@ -33,8 +33,13 @@ jobs: - uses: actions/checkout@v4 # rpmbuild + git archive need the checkout trusted; cache the crates download. + # The client link deps are also baked into the fedora-rpm image, but this job runs + # against the image from the PREVIOUS push (docker.yml bootstrap note) — keep it + # green across image changes; a no-op once the image has them. - name: Prep - run: git config --global --add safe.directory "$PWD" + run: | + git config --global --add safe.directory "$PWD" + dnf -y install gtk4-devel libadwaita-devel SDL3-devel - uses: actions/cache@v4 with: path: /usr/local/cargo/registry diff --git a/ci/fedora-rpm.Dockerfile b/ci/fedora-rpm.Dockerfile index 19e1a82..3087481 100644 --- a/ci/fedora-rpm.Dockerfile +++ b/ci/fedora-rpm.Dockerfile @@ -19,6 +19,8 @@ RUN dnf -y install \ # ffmpeg (NVENC), capture/audio/display link deps ffmpeg-devel pipewire-devel wayland-devel libxkbcommon-devel opus-devel \ mesa-libGL-devel mesa-libgbm-devel \ + # punktfunk-client link deps (GTK4 shell + SDL3 gamepads) + gtk4-devel libadwaita-devel SDL3-devel \ && dnf clean all # libcuda link stub — the zerocopy path links a fixed set of cuXxx driver symbols, but CI has diff --git a/packaging/debian/build-client-deb.sh b/packaging/debian/build-client-deb.sh new file mode 100644 index 0000000..ee61538 --- /dev/null +++ b/packaging/debian/build-client-deb.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# Build the punktfunk-client .deb (the native GTK4 client) for Ubuntu/Debian desktops. +# +# Counterpart to build-deb.sh (the host package); same conventions: runtime Depends are +# computed by dpkg-shlibdeps from the binary's DT_NEEDED (GTK4/libadwaita, SDL3, the +# FFmpeg/PipeWire/Opus sonames), so build inside the Ubuntu 26.04 rust-ci image to pin +# the package names the target boxes ship. The client links no NVIDIA libs — no filter +# needed. +# +# Usage: VERSION=0.0.1~ci42.gdeadbee [ARCH=amd64] bash packaging/debian/build-client-deb.sh +# Output: dist/punktfunk-client__.deb +set -euo pipefail + +VERSION="${VERSION:?set VERSION (e.g. 0.0.1 or 0.0.1~ci42.gdeadbee)}" +ARCH="${ARCH:-amd64}" +PKG="punktfunk-client" +CRATE="punktfunk-client-linux" +ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)" +cd "$ROOTDIR" + +BIN="target/release/$PKG" +if [ ! -x "$BIN" ]; then + echo "==> building $CRATE (release)" + cargo build --release -p "$CRATE" --locked +fi + +STAGE="$(mktemp -d)" +trap 'rm -rf "$STAGE"' EXIT +DOCDIR="$STAGE/usr/share/doc/$PKG" + +# --- file layout -------------------------------------------------------------- +install -Dm0755 "$BIN" "$STAGE/usr/bin/$PKG" +install -Dm0644 packaging/linux/io.unom.Punktfunk.desktop \ + "$STAGE/usr/share/applications/io.unom.Punktfunk.desktop" +# DualSense hidraw access (full pad fidelity through SDL's HIDAPI driver). +install -Dm0644 scripts/70-punktfunk-client.rules \ + "$STAGE/usr/lib/udev/rules.d/70-punktfunk-client.rules" +install -Dm0644 LICENSE-MIT "$DOCDIR/LICENSE-MIT" +install -Dm0644 LICENSE-APACHE "$DOCDIR/LICENSE-APACHE" +install -Dm0644 README.md "$DOCDIR/README.md" + +cat > "$DOCDIR/copyright" < %s\n' \ + "$PKG" "$VERSION" "$VERSION" "$(date -uR 2>/dev/null || echo 'Thu, 01 Jan 1970 00:00:00 +0000')" \ + | gzip -9n > "$DOCDIR/changelog.Debian.gz" + +# --- dependencies -------------------------------------------------------------- +SHLIB_TMP="$(mktemp -d)" +mkdir -p "$SHLIB_TMP/debian" +cat > "$SHLIB_TMP/debian/control" </dev/null \ + | sed -n 's/^shlibs:Depends=//p')" +rm -rf "$SHLIB_TMP" +[ -n "$SHDEPS" ] || { echo "dpkg-shlibdeps produced no deps — is dpkg-dev installed?" >&2; exit 1; } + +# Manual additions shlibdeps can't see: the PipeWire daemon + session manager are runtime +# services (audio playback / mic capture degrade gracefully without them — Recommends). +RECOMMENDS="pipewire, wireplumber, pipewire-pulse" + +INSTALLED_KB="$(du -k -s "$STAGE" | cut -f1)" + +install -d "$STAGE/DEBIAN" +cat > "$STAGE/DEBIAN/control" < +Installed-Size: $INSTALLED_KB +Section: net +Priority: optional +Homepage: https://git.unom.io/unom/punktfunk +Depends: $SHDEPS +Recommends: $RECOMMENDS +Description: Low-latency desktop/game streaming client (punktfunk/1, GTK4) + The native Linux client for punktfunk, a Linux-first low-latency desktop and + game streaming stack. Discovers hosts on the LAN (mDNS), trusts them via + certificate pinning with a SPAKE2 PIN pairing ceremony, and streams HEVC video + (GF(2^16) Leopard FEC + AES-GCM over UDP, QUIC control plane) with Opus audio, + microphone passthrough, and full gamepad support including DualSense touchpad, + motion, adaptive triggers and lightbar through SDL3. + . + The host creates a virtual output at exactly this client's resolution and + refresh rate — no scaling. See the punktfunk-host package for the host side. +EOF + +cat > "$STAGE/DEBIAN/postinst" <<'EOF' +#!/bin/sh +set -e +if [ "$1" = "configure" ]; then + # Pick up the DualSense hidraw rule without a reboot (best-effort, no-op in containers). + udevadm control --reload-rules 2>/dev/null || true + udevadm trigger --subsystem-match=hidraw 2>/dev/null || true + update-desktop-database /usr/share/applications 2>/dev/null || true +fi +exit 0 +EOF +chmod 0755 "$STAGE/DEBIAN/postinst" + +mkdir -p dist +OUT="dist/${PKG}_${VERSION}_${ARCH}.deb" +dpkg-deb --root-owner-group --build "$STAGE" "$OUT" >/dev/null +echo "built $OUT" +echo " Depends: $SHDEPS" +dpkg-deb -I "$OUT" | sed -n 's/^/ /p' | grep -E 'Version|Installed-Size' || true diff --git a/packaging/linux/io.unom.Punktfunk.desktop b/packaging/linux/io.unom.Punktfunk.desktop new file mode 100644 index 0000000..ac4eeca --- /dev/null +++ b/packaging/linux/io.unom.Punktfunk.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Name=Punktfunk +Comment=Stream a remote punktfunk host +Exec=punktfunk-client +Icon=video-display +Terminal=false +Categories=Network;Game; +Keywords=streaming;remote;game;moonlight; +StartupNotify=true diff --git a/packaging/rpm/punktfunk.spec b/packaging/rpm/punktfunk.spec index 8ab60da..b8b3027 100644 --- a/packaging/rpm/punktfunk.spec +++ b/packaging/rpm/punktfunk.spec @@ -65,6 +65,10 @@ BuildRequires: pkgconfig(libavutil) # Zero-copy GPU path: src/zerocopy/ links libGL + libgbm (mesa) via hand-rolled FFI. BuildRequires: pkgconfig(gl) BuildRequires: pkgconfig(gbm) +# The client subpackage (GTK4 shell + SDL3 gamepads). +BuildRequires: pkgconfig(gtk4) +BuildRequires: pkgconfig(libadwaita-1) +BuildRequires: pkgconfig(sdl3) # It ALSO links the NVIDIA CUDA driver lib (-lcuda) via FFI, so libcuda.so must be present # at LINK time. A normal NVIDIA host (or Bazzite -nvidia) has it; a headless COPR/koji builder # without a GPU does NOT — point %build at the CUDA toolkit stub (…/stubs/libcuda.so) there, @@ -96,14 +100,28 @@ exact resolution and refresh via a per-compositor backend (KWin, gamescope, Mutt Sway/wlroots), captured zero-copy (dmabuf -> CUDA -> NVENC) and split-encoded above ~1 Gpix/s. Input (mouse/keyboard/gamepads) is injected back into the session. +%package client +Summary: Low-latency desktop/game streaming client (punktfunk/1, GTK4) +# Audio playback / mic capture want the PipeWire daemon; degrade gracefully without it. +Recommends: pipewire +Recommends: wireplumber + +%description client +The native Linux client for punktfunk. Discovers hosts on the LAN (mDNS), trusts +them via certificate pinning with a SPAKE2 PIN pairing ceremony, and streams HEVC +video (GF(2^16) Leopard FEC + AES-GCM over UDP, QUIC control plane) with Opus +audio, microphone passthrough, and full gamepad support including DualSense +touchpad, motion, adaptive triggers and lightbar through SDL3. The host creates a +virtual output at exactly this client's resolution and refresh rate — no scaling. + %prep %autosetup -n %{name}-%{version} %build -# Release build of the host binary only (the workspace also has the core lib + clients). +# Release build of the host + client binaries (the workspace also has the core lib). # cargo fetches crates over the network; COPR build hosts allow this. export RUSTFLAGS="%{?build_rustflags}" -cargo build --release -p punktfunk-host +cargo build --release -p punktfunk-host -p punktfunk-client-linux %install # Binary @@ -119,6 +137,14 @@ install -Dm0644 scripts/99-punktfunk-net.conf %{buildroot}%{_prefix}/lib/sysctl. # systemd *user* unit (the host runs in the graphical session, not as root). install -Dm0644 scripts/punktfunk-host.service %{buildroot}%{_userunitdir}/punktfunk-host.service +# --- client subpackage --- +install -Dm0755 target/release/punktfunk-client %{buildroot}%{_bindir}/punktfunk-client +install -Dm0644 packaging/linux/io.unom.Punktfunk.desktop \ + %{buildroot}%{_datadir}/applications/io.unom.Punktfunk.desktop +# DualSense hidraw access (full pad fidelity through SDL's HIDAPI driver). +install -Dm0644 scripts/70-punktfunk-client.rules \ + %{buildroot}%{_udevrulesdir}/70-punktfunk-client.rules + # 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 @@ -137,6 +163,18 @@ install -Dm0644 docs/api/openapi.json %{buildroot}%{_datadir}/% %dir %{_datadir}/%{name} %{_datadir}/%{name}/* +%files client +%license LICENSE-MIT LICENSE-APACHE +%{_bindir}/punktfunk-client +%{_datadir}/applications/io.unom.Punktfunk.desktop +%{_udevrulesdir}/70-punktfunk-client.rules + +%post client +# Pick up the DualSense hidraw rule without a reboot (best-effort; on rpm-ostree it +# applies on the next boot into the layered deployment). +udevadm control --reload-rules 2>/dev/null || : +udevadm trigger --subsystem-match=hidraw 2>/dev/null || : + %post # Reload udev so /dev/uinput picks up the new rule without a reboot (best-effort). udevadm control --reload-rules 2>/dev/null || : diff --git a/scripts/70-punktfunk-client.rules b/scripts/70-punktfunk-client.rules new file mode 100644 index 0000000..d3f45c1 --- /dev/null +++ b/scripts/70-punktfunk-client.rules @@ -0,0 +1,12 @@ +# punktfunk-client: hidraw access for the seated user's DualSense (SDL's HIDAPI driver +# needs it for touchpad / motion / lightbar / player LEDs / adaptive triggers — without it +# SDL silently degrades to plain evdev, which has none of those). evdev joystick nodes are +# already uaccess-tagged by systemd; hidraw nodes are root-only by default and systemd +# declined a generic gamepad hwdb (systemd#22681), so we ship the rule, steam-devices +# style: the ATTRS match covers USB, the KERNELS match covers Bluetooth. +# DualSense (054c:0ce6) +KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0ce6", MODE="0660", TAG+="uaccess" +KERNEL=="hidraw*", KERNELS=="*054C:0CE6*", MODE="0660", TAG+="uaccess" +# DualSense Edge (054c:0df2) +KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0df2", MODE="0660", TAG+="uaccess" +KERNEL=="hidraw*", KERNELS=="*054C:0DF2*", MODE="0660", TAG+="uaccess"