feat(packaging): bundle the web console into the RPM / Arch / bootc host packages
ci / rust (push) Successful in 1m13s
android / android (push) Failing after 1m42s
ci / web (push) Successful in 27s
ci / bench (push) Successful in 1m50s
decky / build-publish (push) Successful in 11s
deb / build-publish (push) Failing after 2m38s
apple / swift (push) Successful in 54s
ci / docs-site (push) Successful in 32s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m57s
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
flatpak / build-publish (push) Failing after 2s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m33s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 5m20s
docker / deploy-docs (push) Successful in 17s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 5m11s

The punktfunk-web management console (pairing + status) shipped only via apt. Extend it
to the other HOST packaging methods, mirroring the Debian punktfunk-web .deb (flatpak is
the client, correctly excluded):

- rpm/punktfunk.spec: new noarch `punktfunk-web` subpackage (the .output bundle + a
  /usr/bin/punktfunk-web-server node launcher + both systemd --user units + web-init.sh +
  web.env.example), gated behind `%bcond_with web`. OFF by default because building the
  Nitro/Node SSR bundle needs `bun`, which a plain rpmbuild / COPR mock chroot lacks. Host
  package weak-Recommends punktfunk-web.
- ci/fedora-rpm.Dockerfile: install bun (+ unzip) so the CI builder can build the console.
- rpm.yml: build `PF_WITH_WEB=1` (Prep bootstraps bun to stay green pre-image-rebuild); the
  publish loop already globs the new noarch rpm into the registry. build-rpm.sh: `--with web`
  when PF_WITH_WEB=1.
- bootc/Containerfile: install from the Gitea RPM registry (which carries punktfunk-web)
  instead of COPR — `dnf5 install punktfunk punktfunk-web`.
- arch/PKGBUILD: opt-in `punktfunk-web` split member (PF_WITH_WEB=1 appends it + bun) so a
  default makepkg still builds host+client with no JS tooling — matching the spec's bcond.
- docs: packaging/README, rpm/README, copr/README (the no-bun caveat), bazzite/README
  (Path B rewritten COPR→Gitea registry), arch/README — enable + journal-password steps.

Reviewed across methods by an adversarial multi-agent pass (rpm/ci/arch/bootc/consistency
lenses, each blocking finding 3x-verified); fixed the two it confirmed real — the Arch
bun-mandatory regression (now opt-in) and the stale COPR wording in bazzite Path B.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 09:56:58 +00:00
parent 3167c936c0
commit 802e98d3a3
11 changed files with 229 additions and 29 deletions
+10 -3
View File
@@ -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://<host-ip>: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:
+5 -1
View File
@@ -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}" \
+79
View File
@@ -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://<host-ip>:3000"
%endif
%changelog
* Sun Jun 15 2026 punktfunk <noreply@anthropic.com> - 0.0.1-2
- Add punktfunk-web subpackage (management console, --with web; auto-wired to the host token).
* Wed Jun 10 2026 punktfunk <noreply@anthropic.com> - 0.0.1-1
- Initial RPM: punktfunk-host + udev rule + systemd user unit + headless helpers.