diff --git a/.gitea/workflows/rpm.yml b/.gitea/workflows/rpm.yml index 02bd239..5481372 100644 --- a/.gitea/workflows/rpm.yml +++ b/.gitea/workflows/rpm.yml @@ -50,6 +50,14 @@ jobs: run: | git config --global --add safe.directory "$PWD" dnf -y install gtk4-devel libadwaita-devel SDL3-devel + # bun builds the punktfunk-web console (--with web). Baked into the image; install it + # here too so the job stays green against the PREVIOUS image (docker.yml bootstrap note). + command -v bun >/dev/null || { + dnf -y install unzip + curl -fsSL https://bun.sh/install | bash + install -m0755 "$HOME/.bun/bin/bun" /usr/local/bin/bun + } + bun --version - uses: actions/cache@v4 with: path: /usr/local/cargo/registry @@ -71,7 +79,9 @@ jobs: echo "rpm $V-$R" - name: Build RPM - run: PF_VERSION="$PF_VERSION" PF_RELEASE="$PF_RELEASE" bash packaging/rpm/build-rpm.sh + # PF_WITH_WEB=1 → also build the noarch punktfunk-web subpackage (the publish loop below + # globs it in; the host RPM Recommends it). Needs bun (ensured in Prep). + run: PF_VERSION="$PF_VERSION" PF_RELEASE="$PF_RELEASE" PF_WITH_WEB=1 bash packaging/rpm/build-rpm.sh - name: Publish to the Gitea RPM registry env: diff --git a/ci/fedora-rpm.Dockerfile b/ci/fedora-rpm.Dockerfile index faaf45d..3b27617 100644 --- a/ci/fedora-rpm.Dockerfile +++ b/ci/fedora-rpm.Dockerfile @@ -17,7 +17,8 @@ RUN dnf -y install \ "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 \ + # AND the punktfunk-web .output at runtime; unzip is for the bun installer below. + rpm-build rpmdevtools systemd-rpm-macros git tar gzip nodejs unzip \ # build toolchain + bindgen gcc gcc-c++ clang clang-devel cmake nasm pkgconf-pkg-config curl ca-certificates \ # ffmpeg (NVENC), capture/audio/display link deps @@ -27,6 +28,13 @@ RUN dnf -y install \ gtk4-devel libadwaita-devel SDL3-devel \ && dnf clean all +# bun — the build tool for the punktfunk-web console (`bun run build` -> the node-server .output +# the punktfunk-web RPM ships and runs with plain node). Not in Fedora repos; install the official +# standalone binary to a system PATH dir so the rpmbuild `%build` (run as any uid) finds it. +RUN curl -fsSL https://bun.sh/install | bash \ + && install -m0755 /root/.bun/bin/bun /usr/local/bin/bun \ + && bun --version + # 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 diff --git a/packaging/README.md b/packaging/README.md index 55dceb1..a1e6a0a 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -91,9 +91,20 @@ ujust add-user-to-input-group # virtual gamepads need /dev/uinput (the mkdir -p ~/.config/punktfunk cp /usr/share/punktfunk/host.env.bazzite ~/.config/punktfunk/host.env # edit (gamescope app, etc.) systemctl --user enable --now punktfunk-host + +# Management web console (pairing + status) — pulled in by default (the host RPM Recommends it; +# `--no-install-recommends` / headless-only boxes can skip it). Enable it and read the login password: +systemctl --user enable --now punktfunk-web +journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p' # then open http://:3000 ``` -Pair a stock Moonlight client (mDNS-discovered), or connect the native punktfunk/1 client. +Pair a stock Moonlight client (mDNS-discovered), or connect the native punktfunk/1 client — via the +web console at `http://:3000` or directly. + +> ⚠️ **COPR caveat:** COPR's mock chroot has no `bun`, so a COPR build produces only +> `punktfunk` + `punktfunk-client` — **not** `punktfunk-web`. For the console on a COPR/bootc host, +> install from the **Gitea RPM registry** (Option A — it carries `punktfunk-web`), which is also why +> `bootc/Containerfile` installs from there rather than COPR. ## Why not Flatpak (for the HOST)? diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index 7346e28..c110031 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -14,6 +14,10 @@ # NVIDIA hosts; an AMD Deck-as-HOST needs a VAAPI backend first. The CLIENT decodes via VAAPI # (AMD/Intel, incl. the Deck) with a software fallback, so it works everywhere. See README.md. pkgbase=punktfunk +# punktfunk-web (the browser console) is OPT-IN: building it needs `bun` (AUR-only as bun-bin on +# stock Arch/SteamOS), so a default makepkg builds only host+client with no JS tooling — mirroring +# the RPM spec's `%bcond_with web` (off by default). Set PF_WITH_WEB=1 to also build punktfunk-web +# (appended to pkgname + bun to makedepends below). pkgname=('punktfunk-host' 'punktfunk-client') pkgver=0.0.1 pkgrel=1 @@ -26,6 +30,12 @@ license=('MIT OR Apache-2.0') makedepends=('rust' 'cargo' 'clang' 'cmake' 'nasm' 'pkgconf' 'git' 'gtk4' 'libadwaita' 'sdl3' 'ffmpeg' 'pipewire' 'wayland' 'libxkbcommon' 'opus' 'libei') +# Opt-in punktfunk-web: only then is bun (build tool; the console runs on plain nodejs) required. +if [ "${PF_WITH_WEB:-0}" = 1 ]; then + pkgname+=('punktfunk-web') + makedepends+=('bun') # `bun-bin` from the AUR if bun isn't in your configured repos +fi + # AUR source (a tagged release). For an in-tree CI build, set PF_SRCDIR to the repo root and # build() uses it instead; see the README. source=("git+https://git.unom.io/unom/punktfunk.git#tag=v${pkgver}") @@ -40,6 +50,10 @@ build() { # NVIDIA builder. On a GPU-less builder symlink the CUDA stub into the link path first (same # caveat the RPM documents): ln -s "$(find / -name libcuda.so -path '*stubs*'|head -1)" /usr/lib/ cargo build --release --locked -p punktfunk-host -p punktfunk-client-linux + # Management web console (opt-in): the node-server .output bundle (built with bun, run with node). + if [ "${PF_WITH_WEB:-0}" = 1 ]; then + ( cd web && bun install --frozen-lockfile && bun run build ) + fi } package_punktfunk-host() { @@ -54,7 +68,8 @@ package_punktfunk-host() { 'mutter: stream a GNOME desktop (Mutter RecordVirtual backend)' 'sway: stream a wlroots desktop (Sway VirtualDisplay backend)' 'xdg-desktop-portal-kde: portal for the headless KDE session helper' - 'xdg-desktop-portal-wlr: portal for the headless Sway session helper') + 'xdg-desktop-portal-wlr: portal for the headless Sway session helper' + 'punktfunk-web: browser management console (device pairing + status)') install=punktfunk-host.install local R; R="$(_repo)"; local T="$srcdir/target/release" @@ -107,3 +122,28 @@ package_punktfunk-client() { install -Dm0644 "$R/LICENSE-MIT" "$pkgdir/usr/share/licenses/punktfunk-client/LICENSE-MIT" install -Dm0644 "$R/LICENSE-APACHE" "$pkgdir/usr/share/licenses/punktfunk-client/LICENSE-APACHE" } + +package_punktfunk-web() { + pkgdesc="punktfunk management web console (Nitro/Node SSR) — pairing + status in the browser" + arch=('any') + # Runtime is plain node (the .output is portable JS — bun was only the build tool). Auto-wired to + # the host's mgmt token via the systemd --user units; enable with `systemctl --user enable --now punktfunk-web`. + depends=('nodejs') + local R; R="$(_repo)" + + # Pre-built node-server bundle (from build()) + a PATH-stable launcher (matches the .deb/.rpm). + install -d "$pkgdir/usr/share/punktfunk-web/.output" + cp -r "$R/web/.output/server" "$pkgdir/usr/share/punktfunk-web/.output/server" + cp -r "$R/web/.output/public" "$pkgdir/usr/share/punktfunk-web/.output/public" + install -d "$pkgdir/usr/bin" + printf '%s\n' '#!/bin/sh' 'exec /usr/bin/node /usr/share/punktfunk-web/.output/server/index.mjs "$@"' \ + > "$pkgdir/usr/bin/punktfunk-web-server" + chmod 0755 "$pkgdir/usr/bin/punktfunk-web-server" + # systemd USER units: the console runs per-user; web-init generates the login password on first start. + install -Dm0644 "$R/scripts/punktfunk-web.service" "$pkgdir/usr/lib/systemd/user/punktfunk-web.service" + install -Dm0644 "$R/scripts/punktfunk-web-init.service" "$pkgdir/usr/lib/systemd/user/punktfunk-web-init.service" + install -Dm0755 "$R/scripts/web-init.sh" "$pkgdir/usr/share/punktfunk-web/web-init.sh" + install -Dm0644 "$R/web/web.env.example" "$pkgdir/usr/share/punktfunk-web/web.env.example" + install -Dm0644 "$R/LICENSE-MIT" "$pkgdir/usr/share/licenses/punktfunk-web/LICENSE-MIT" + install -Dm0644 "$R/LICENSE-APACHE" "$pkgdir/usr/share/licenses/punktfunk-web/LICENSE-APACHE" +} diff --git a/packaging/arch/README.md b/packaging/arch/README.md index ca38000..53c0e8d 100644 --- a/packaging/arch/README.md +++ b/packaging/arch/README.md @@ -1,12 +1,17 @@ # punktfunk on Arch Linux / SteamOS Packaging for punktfunk on Arch and Arch-derived immutable distros (SteamOS 3, etc.). The -`PKGBUILD` is a **split package** producing both **`punktfunk-host`** (the gaming-rig host) and +`PKGBUILD` is a **split package** producing **`punktfunk-host`** (the gaming-rig host) and **`punktfunk-client`** (the GTK4 couch/Deck client) — mirrors the rpm subpackages -(`packaging/rpm/punktfunk.spec`) and the two deb build scripts. On a **Steam Deck you want +(`packaging/rpm/punktfunk.spec`) and the deb build scripts. On a **Steam Deck you want `punktfunk-client`** (it's what the [Decky plugin](../../clients/decky/) launches); on a gaming rig, `punktfunk-host`. +A third member, **`punktfunk-web`** (the browser management console — pairing + status), is +**opt-in**: build it by setting `PF_WITH_WEB=1`, which requires **`bun`** at build time (`bun-bin` +from the AUR if it isn't in your repos; the console then runs on plain `nodejs`). A default +`makepkg` builds only host+client with no JS tooling — mirroring the RPM spec's `%bcond_with web`. + > ⚠️ **Host encode is NVENC-only today.** `crates/punktfunk-host/src/encode/linux.rs` implements > `hevc_nvenc`/`av1_nvenc`/`h264_nvenc` + a CUDA zero-copy path — there is **no VAAPI encoder**. So > `punktfunk-host` works on **Arch + NVIDIA** (incl. `bazzite-deck-nvidia`); an **AMD Deck-as-host** @@ -22,6 +27,8 @@ cd packaging/arch PF_SRCDIR="$(git rev-parse --show-toplevel)" makepkg -f --holdver # …or build the tagged release the AUR way: makepkg -si +# …add the web console too (needs bun / bun-bin): +PF_WITH_WEB=1 PF_SRCDIR="$(git rev-parse --show-toplevel)" makepkg -f --holdver ``` Then the standard first-run (printed by the install scriptlet): ```sh @@ -29,6 +36,9 @@ sudo usermod -aG input "$USER" # virtual gamepads; re-login after mkdir -p ~/.config/punktfunk cp /usr/share/punktfunk/host.env.bazzite ~/.config/punktfunk/host.env # gamescope backend systemctl --user enable --now punktfunk-host +# Web console (if you installed the punktfunk-web package): enable it + read the login password. +systemctl --user enable --now punktfunk-web +journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p' # open http://:3000 ``` NVENC/EGL come from the NVIDIA driver: `sudo pacman -S --needed nvidia-utils`. Arch's stock `ffmpeg` already has NVENC built in — no RPM-Fusion-style swap needed (unlike Fedora). diff --git a/packaging/bazzite/README.md b/packaging/bazzite/README.md index fe1ebf4..5ed00a0 100644 --- a/packaging/bazzite/README.md +++ b/packaging/bazzite/README.md @@ -32,10 +32,12 @@ There are two supported paths on Bazzite, driven by different files in `packagin | **B — bootc / OCI image** | `packaging/bootc/Containerfile` | Bakes punktfunk into a `FROM bazzite-nvidia` image once; you `bootc switch` any number of hosts onto it | Fleets, reproducible appliances, no per-host drift | **Trade-off:** Path A is a per-host package layer — simple, but each host accumulates its own -layered-package state. Path B builds one image (RPM Fusion + COPR + the package + udev rule -pre-installed) that you push to a registry and rebase hosts onto atomically — no per-host -`rpm-ostree install` drift, at the cost of running a `podman build`/`push` pipeline. Both -ultimately install the **same RPM** and require the **same first-run setup** (sections 3–6). +layered-package state. Path B builds one image (RPM Fusion + the Gitea RPM repo + the host and +**web console** + udev rule pre-installed) that you push to a registry and rebase hosts onto +atomically — no per-host `rpm-ostree install` drift, at the cost of running a `podman build`/`push` +pipeline. Both require the **same first-run setup** (sections 3–6); note Path B installs from the +**Gitea RPM registry** (which carries `punktfunk-web`), whereas Path A's COPR builds host+client +only — for the web console on Path A, layer from the Gitea registry instead (`../rpm/README.md`). ### Path A — rpm-ostree layering from the COPR @@ -64,8 +66,10 @@ systemctl reboot The image is built **off-host** (on any machine with `podman`) from `packaging/bootc/Containerfile`, which bases on `ghcr.io/ublue-os/bazzite-nvidia:stable` -(override with `--build-arg BASE_IMAGE=…`), enables RPM Fusion free + nonfree, enables the COPR -(`--build-arg PUNKTFUNK_COPR=…`, default `enricobuehler/punktfunk`), and installs the package. +(override with `--build-arg BASE_IMAGE=…`), enables RPM Fusion free + nonfree, adds the Gitea RPM +repo (`--build-arg PUNKTFUNK_RPM_GROUP=…`, default `bazzite`), and installs the host **and the web +console** (`punktfunk punktfunk-web`). It uses the Gitea registry rather than the COPR specifically +because the registry carries `punktfunk-web` (COPR's mock chroot can't build it — no `bun`). ```sh # Build + push (run from the repo root, on your builder machine): @@ -76,9 +80,10 @@ podman push ghcr.io//bazzite-punktfunk sudo bootc switch ghcr.io//bazzite-punktfunk && systemctl reboot ``` -> ⚠️ The image build runs `dnf5 copr enable enricobuehler/punktfunk` — so **Path B also depends on -> the COPR being published** (or on you pointing `PUNKTFUNK_COPR` at a COPR you've built yourself). -> If the COPR doesn't exist, the `podman build` fails at the install step. +> ⚠️ The image installs from the **Gitea RPM registry** (group `bazzite`), so **Path B depends on +> that registry being populated** — CI (`.gitea/workflows/rpm.yml`) publishes `punktfunk` + +> `punktfunk-web` on every push to `main`. Packages are unsigned with GPG-signed metadata +> (`repo_gpgcheck=1`), matching `packaging/rpm/README.md`. --- @@ -215,6 +220,10 @@ into the user unit directory. ```sh systemctl --user daemon-reload systemctl --user enable --now punktfunk-host +# Management web console (pairing + status), if you installed punktfunk-web (it ships in the Gitea +# RPM registry / bootc image — COPR can't build it; see ../rpm/README.md). Read the login password: +systemctl --user enable --now punktfunk-web +journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p' # then open http://:3000 ``` Check health and logs: diff --git a/packaging/bootc/Containerfile b/packaging/bootc/Containerfile index 4109c96..bde97da 100644 --- a/packaging/bootc/Containerfile +++ b/packaging/bootc/Containerfile @@ -15,8 +15,12 @@ ARG BASE_IMAGE=ghcr.io/ublue-os/bazzite-nvidia:stable FROM ${BASE_IMAGE} -# COPR project that hosts the punktfunk RPM (see packaging/copr/README). Override at build. -ARG PUNKTFUNK_COPR=enricobuehler/punktfunk +# punktfunk's RPMs come from unom's Gitea RPM registry (the recommended path — see +# packaging/rpm/README). Use it rather than COPR specifically because it carries the +# punktfunk-web management console subpackage, which COPR's mock chroot can't build (no `bun`). +# Group "bazzite" == the Fedora 43 base; override for a different base. Gitea signs the repo +# metadata (repo_gpgcheck=1); the packages themselves are unsigned (gpgcheck=0). +ARG PUNKTFUNK_RPM_GROUP=bazzite # RPM Fusion nonfree provides the NVENC-capable ffmpeg-libs punktfunk records/encodes with. # (Bazzite usually has RPM Fusion enabled already; this is belt-and-suspenders.) @@ -25,15 +29,20 @@ RUN dnf5 -y install \ https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ || true -# Enable our COPR and install punktfunk. -RUN dnf5 -y copr enable ${PUNKTFUNK_COPR} && \ - dnf5 -y install punktfunk && \ - dnf5 -y copr disable ${PUNKTFUNK_COPR} && \ - dnf5 clean all +# Add the Gitea RPM repo and install the host + the web console (punktfunk-web pulls nodejs). +RUN printf '%s\n' \ + '[gitea-unom-punktfunk]' \ + 'name=punktfunk (unom)' \ + "baseurl=https://git.unom.io/api/packages/unom/rpm/${PUNKTFUNK_RPM_GROUP}" \ + 'enabled=1' 'gpgcheck=0' 'repo_gpgcheck=1' \ + 'gpgkey=https://git.unom.io/api/packages/unom/rpm/repository.key' \ + > /etc/yum.repos.d/punktfunk.repo \ + && dnf5 -y install punktfunk punktfunk-web \ + && dnf5 clean all -# The udev rule + systemd *user* unit ship in the RPM; nothing else to enable at image -# build time (the host runs per-user in the graphical session, enabled with -# `systemctl --user enable --now punktfunk-host` after first boot). +# The udev rule + systemd *user* units ship in the RPMs; nothing else to enable at image build +# time (host + console run per-user in the graphical session, enabled after first boot with +# `systemctl --user enable --now punktfunk-host punktfunk-web`). # bootc image hygiene: the container build must leave a clean ostree commit. RUN ostree container commit diff --git a/packaging/copr/README.md b/packaging/copr/README.md index c8e97b0..26b5515 100644 --- a/packaging/copr/README.md +++ b/packaging/copr/README.md @@ -33,3 +33,16 @@ copr-cli buildscm punktfunk \ Note: COPR caps build time/RAM; a full `cargo build --release` of the host (FFmpeg/PipeWire sys-crates + aws-lc-rs) is heavy but within the default COPR limits. If a chroot OOMs, lower parallelism with `CARGO_BUILD_JOBS` in the spec's `%build`. + +## The web console subpackage (`punktfunk-web`) + +The spec can also build the management web console as a noarch `punktfunk-web` subpackage, but it's +gated behind `%bcond_with web` and **OFF by default** — building the Nitro/Node SSR bundle needs +`bun`, which COPR's mock chroot does not provide. So a stock COPR build produces only `punktfunk` ++ `punktfunk-client`. + +Two ways to get the console: +- **Recommended:** install it from the Gitea RPM registry (`packaging/rpm/README.md`, Option A), + whose CI builder image has `bun` and builds `--with web`. (This is what `bootc/Containerfile` does.) +- **In COPR:** add `bun` to the chroot (a custom mock config / external repo) and set the build + option `--with web` on the project, then `dnf install punktfunk-web`. diff --git a/packaging/rpm/README.md b/packaging/rpm/README.md index 8826ad8..7a4e45c 100644 --- a/packaging/rpm/README.md +++ b/packaging/rpm/README.md @@ -29,8 +29,10 @@ repo_gpgcheck=1 gpgkey=https://git.unom.io/api/packages/unom/rpm/repository.key REPO -# Layer the package, then reboot into the new deployment. -rpm-ostree install punktfunk +# 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 ``` @@ -46,6 +48,9 @@ ujust add-user-to-input-group # virtual gamepads need /dev/uinput (re- 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://:3000): +systemctl --user enable --now punktfunk-web +journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p' ``` (See [`../bazzite/README.md`](../bazzite/README.md) for the full appliance walkthrough — @@ -65,7 +70,9 @@ 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 +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: diff --git a/packaging/rpm/build-rpm.sh b/packaging/rpm/build-rpm.sh index b3deb43..78ae0b4 100755 --- a/packaging/rpm/build-rpm.sh +++ b/packaging/rpm/build-rpm.sh @@ -11,6 +11,10 @@ set -euo pipefail PF_VERSION="${PF_VERSION:-0.0.1}" PF_RELEASE="${PF_RELEASE:-1}" +# PF_WITH_WEB=1 builds the punktfunk-web subpackage too (needs `bun` on PATH — present in the CI +# builder image, not in a plain mock chroot). Default off so a bare `rpmbuild`/COPR still works. +WEB_OPT=() +[ "${PF_WITH_WEB:-0}" = "1" ] && WEB_OPT=(--with web) ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)" cd "$ROOTDIR" @@ -28,7 +32,7 @@ git archive --format=tar.gz --prefix="punktfunk-${PF_VERSION}/" \ # 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 \ +rpmbuild -bb --nodeps "${WEB_OPT[@]}" \ --define "_topdir $TOP" \ --define "pf_version ${PF_VERSION}" \ --define "pf_release ${PF_RELEASE}" \ diff --git a/packaging/rpm/punktfunk.spec b/packaging/rpm/punktfunk.spec index dce2377..6139701 100644 --- a/packaging/rpm/punktfunk.spec +++ b/packaging/rpm/punktfunk.spec @@ -39,6 +39,13 @@ ExclusiveArch: x86_64 aarch64 # Recommends). Drop it from the auto-Requires, mirroring the Debian package's NVIDIA filter. %global __requires_exclude ^libcuda\\.so.*$ +# Management web console subpackage (punktfunk-web). OFF by default: building the Nitro/Node SSR +# bundle needs `bun`, which a plain rpmbuild / COPR mock chroot does NOT have. CI's builder image +# (ci/fedora-rpm.Dockerfile) DOES have bun and builds with `--with web`, so the Gitea RPM registry +# carries punktfunk-web. COPR (no bun) builds host+client only — use the Gitea registry for the +# console, or enable bun + `--with web` in the COPR project. Mirrors the Debian punktfunk-web .deb. +%bcond_with web + # --- Build toolchain --------------------------------------------------------- BuildRequires: cargo BuildRequires: rust @@ -90,6 +97,10 @@ Suggests: kwin Suggests: mutter # NVENC + GPU EGL come from the NVIDIA driver; on Bazzite the -nvidia image has it. Recommends: (xorg-x11-drv-nvidia-cuda if xorg-x11-drv-nvidia) +# The management web console (pairing + status) every user needs — a separate noarch subpackage. +# Weak-dep so `dnf install punktfunk` pulls it where it exists (the Gitea registry); harmless where +# it doesn't (a COPR build without `--with web` simply has no punktfunk-web to satisfy). +Recommends: punktfunk-web %description punktfunk is a Linux-first, low-latency desktop and game streaming host. It speaks @@ -114,6 +125,23 @@ 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. +%if %{with web} +%package web +Summary: punktfunk management web console (Nitro/Node SSR + React) +BuildArch: noarch +# Runtime is plain node (the .output is portable JS — bun is only the build tool). Fedora 41+ +# ships nodejs >= 20, which the node-server build needs. +Requires: nodejs + +%description web +The browser console for a punktfunk streaming host: status, paired devices, and the SPAKE2 +PIN pairing flow every client needs. Runs as a systemd --user service on port 3000, login-gated +(a password generated on first start), proxying the host's loopback HTTPS management API with a +bearer token injected server-side (never sent to the browser). Auto-wired to the host on a +packaged install — it sources the host's mgmt token and a generated login password, no env +editing. Enable with `systemctl --user enable --now punktfunk-web`. +%endif + %prep %autosetup -n %{name}-%{version} @@ -123,6 +151,16 @@ virtual output at exactly this client's resolution and refresh rate — no scali export RUSTFLAGS="%{?build_rustflags}" cargo build --release -p punktfunk-host -p punktfunk-client-linux +%if %{with web} +# Management web console: build the Nitro/Node SSR bundle (node-server preset) with bun. The +# .output is portable JS run at runtime by plain node; bun is only the build tool (CI image). +(cd web && bun install --frozen-lockfile && bun run build) +if grep -q 'Bun\.serve' web/.output/server/index.mjs; then + echo "ERROR: web build is a bun bundle (Bun.serve) — need the node-server preset" >&2 + exit 1 +fi +%endif + %install # Binary install -Dm0755 target/release/punktfunk-host %{buildroot}%{_bindir}/punktfunk-host @@ -177,6 +215,24 @@ install -d %{buildroot}%{_datadir}/%{name}/bazzite install -Dm0755 packaging/bazzite/kde-desktop-setup.sh %{buildroot}%{_datadir}/%{name}/bazzite/kde-desktop-setup.sh install -Dm0644 docs/api/openapi.json %{buildroot}%{_datadir}/%{name}/openapi.json +%if %{with web} +# --- web console subpackage (punktfunk-web) --- +install -d %{buildroot}%{_datadir}/punktfunk-web/.output +cp -r web/.output/server %{buildroot}%{_datadir}/punktfunk-web/.output/server +cp -r web/.output/public %{buildroot}%{_datadir}/punktfunk-web/.output/public +# PATH-stable launcher (matches the .deb's /usr/bin/punktfunk-web-server). +cat > %{buildroot}%{_bindir}/punktfunk-web-server <<'WRAP' +#!/bin/sh +exec /usr/bin/node /usr/share/punktfunk-web/.output/server/index.mjs "$@" +WRAP +chmod 0755 %{buildroot}%{_bindir}/punktfunk-web-server +# systemd --user units: the console runs per-user; web-init generates the login password. +install -Dm0644 scripts/punktfunk-web.service %{buildroot}%{_userunitdir}/punktfunk-web.service +install -Dm0644 scripts/punktfunk-web-init.service %{buildroot}%{_userunitdir}/punktfunk-web-init.service +install -Dm0755 scripts/web-init.sh %{buildroot}%{_datadir}/punktfunk-web/web-init.sh +install -Dm0644 web/web.env.example %{buildroot}%{_datadir}/punktfunk-web/web.env.example +%endif + %files %license LICENSE-MIT LICENSE-APACHE %doc README.md docs/implementation-plan.md packaging/README.md @@ -195,6 +251,18 @@ install -Dm0644 docs/api/openapi.json %{buildroot}%{_datadir}/% %{_udevrulesdir}/70-punktfunk-client.rules %{_prefix}/lib/sysctl.d/99-punktfunk-client-net.conf +%if %{with web} +%files web +%license LICENSE-MIT LICENSE-APACHE +%{_bindir}/punktfunk-web-server +%dir %{_datadir}/punktfunk-web +%{_datadir}/punktfunk-web/.output +%{_datadir}/punktfunk-web/web-init.sh +%{_datadir}/punktfunk-web/web.env.example +%{_userunitdir}/punktfunk-web.service +%{_userunitdir}/punktfunk-web-init.service +%endif + %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). @@ -215,6 +283,17 @@ echo "punktfunk installed. Add yourself to the 'input' group (sudo usermod -aG i echo "then enable the host: systemctl --user enable --now punktfunk-host" echo "Config: cp %{_datadir}/%{name}/host.env.bazzite ~/.config/punktfunk/host.env" +%if %{with web} +%post web +echo "punktfunk-web installed. Enable the console for your user:" +echo " systemctl --user enable --now punktfunk-web" +echo "A login password is generated on first start — read it with:" +echo " journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'" +echo "Then open http://:3000" +%endif + %changelog +* Sun Jun 15 2026 punktfunk - 0.0.1-2 +- Add punktfunk-web subpackage (management console, --with web; auto-wired to the host token). * Wed Jun 10 2026 punktfunk - 0.0.1-1 - Initial RPM: punktfunk-host + udev rule + systemd user unit + headless helpers.