ci / bun-nix (pull_request) Successful in 20s
ci / docs-site (pull_request) Successful in 1m5s
ci / web (pull_request) Successful in 1m10s
ci / rust-arm64 (pull_request) Successful in 1m49s
ci / rust (pull_request) Successful in 6m23s
nix / flake (pull_request) Failing after 12m37s
Two separate things had to be wrong for this, and both were. The frame's own policy locked it out. Plugin UIs moved to their own origin so a plugin cannot act as the logged-in operator, and the plugin origin names the console as the only page allowed to frame it. It built that name from the incoming request — but Nitro hands the app a synthetic request with no TLS socket, so an HTTPS console reads back as `http:`. The header said `frame-ancestors http://host:47992` while the operator was on `https://host:47992`, and the browser refused the frame outright (ERR_BLOCKED_BY_RESPONSE). Nothing on screen said so; the reason was only in devtools. The scheme now comes from the listener's own TLS state, stamped at bind time, with x-forwarded-proto winning when something in front terminated TLS for us — the one case where the browser's scheme is not ours. And the port was shut. 47993 was added to the firewall definitions, but an already-open firewall does not pick a new port up: ufw expands an app profile into rules when you allow it and keeps those, so editing the profile on upgrade changes nothing, and firewalld needs a reload. Every upgraded Linux host kept a 47992-only rule, silently. The packages now notice on upgrade and print the one command that fixes it, without touching the running firewall. The NixOS module and the container image never learned the port at all; both now open it. Also: the console no longer mounts the frame while it is still checking whether that origin is reachable. A firewalled port drops rather than refuses, so the check hangs for the browser's whole connect timeout, and mounting meanwhile is exactly the empty panel with no explanation. The card that follows now names both causes it can be — an untrusted certificate for that port, or a closed one — because from a browser the two are indistinguishable. The rule is now a pure function with tests, since its failure mode is a well-formed header that only a browser rejects. Verified on glass against home-worker-5 (.21) and its ROM Manager plugin: the frame was refused before, renders the plugin's UI after.
117 lines
5.7 KiB
Plaintext
117 lines
5.7 KiB
Plaintext
# pacman install scriptlet — mirrors the RPM %post / deb postinst.
|
|
_ensure_update_group() {
|
|
# The (empty) opt-in group for web-console-triggered updates — nobody is auto-added.
|
|
getent group punktfunk-update >/dev/null 2>&1 || groupadd --system punktfunk-update 2>/dev/null || true
|
|
}
|
|
|
|
_ensure_punktfunk_group() {
|
|
# Owns the usbip vhci attach/detach nodes (60-punktfunk.rules). Separate from 'input' on
|
|
# purpose: writing 'attach' materialises an arbitrary emulated USB device, which is a root-only
|
|
# kernel primitive and must not ride on the group users are told to join for gamepads
|
|
# (security-review 2026-08-05 M-4).
|
|
getent group punktfunk >/dev/null 2>&1 || groupadd --system punktfunk 2>/dev/null || true
|
|
}
|
|
|
|
post_install() {
|
|
_ensure_update_group
|
|
_ensure_punktfunk_group
|
|
udevadm control --reload-rules 2>/dev/null || true
|
|
udevadm trigger --subsystem-match=misc 2>/dev/null || true
|
|
# Apply the UDP socket-buffer tuning now (also auto-applied at boot by systemd-sysctl).
|
|
sysctl -p /usr/lib/sysctl.d/99-punktfunk-net.conf >/dev/null 2>&1 || true
|
|
cat <<'MSG'
|
|
punktfunk-host installed.
|
|
1. Add yourself to the 'input' group for virtual gamepads:
|
|
sudo usermod -aG input "$USER" # then re-login
|
|
Only if you want the virtual Steam Deck pad (usbip), ALSO join 'punktfunk':
|
|
sudo usermod -aG punktfunk "$USER"
|
|
That group can emulate arbitrary USB devices — join it only on a machine you trust.
|
|
2. Pick a backend config (gamescope is the no-desktop default on SteamOS/Deck):
|
|
mkdir -p ~/.config/punktfunk
|
|
cp /usr/share/punktfunk/host.env.bazzite ~/.config/punktfunk/host.env
|
|
3. Enable the host:
|
|
systemctl --user enable --now punktfunk-host
|
|
|
|
NOTE: encode is NVENC-only. Install 'nvidia-utils' on an NVIDIA host. An AMD Steam Deck is NOT
|
|
yet supported — it needs a VAAPI (hevc_vaapi) encoder backend (see packaging/arch/README.md).
|
|
MSG
|
|
# Firewall: stock Arch ships none (ports already open); CachyOS ships ufw; some spins (EndeavourOS)
|
|
# enable firewalld. We install a ufw app profile AND firewalld service definitions but never touch
|
|
# the running firewall — just point the way for whichever is active.
|
|
if command -v ufw >/dev/null 2>&1; then
|
|
cat <<'MSG'
|
|
|
|
4. ufw is installed — open the streaming ports once (native-only host shown; add
|
|
'punktfunk-gamestream' as well for Moonlight compat):
|
|
sudo ufw allow punktfunk-native
|
|
MSG
|
|
fi
|
|
if command -v firewall-cmd >/dev/null 2>&1; then
|
|
cat <<'MSG'
|
|
|
|
4. firewalld is active — open the streaming ports once (native-only host shown; add
|
|
'punktfunk-gamestream' as well for Moonlight compat):
|
|
sudo firewall-cmd --reload # load the new service def
|
|
sudo firewall-cmd --permanent --add-service=punktfunk-native
|
|
sudo firewall-cmd --reload
|
|
MSG
|
|
fi
|
|
# Conflicting Moonlight-compatible host (Sunshine/Apollo/...): reuse the host's own detector so
|
|
# the warning lives in one place. Exit 1 = found; never fail the install on it.
|
|
if command -v punktfunk-host >/dev/null 2>&1; then
|
|
if ! conflict="$(punktfunk-host detect-conflicts 2>/dev/null)"; then
|
|
printf '\n%s\n' "$conflict"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
post_upgrade() {
|
|
_ensure_update_group
|
|
udevadm control --reload-rules 2>/dev/null || true
|
|
sysctl -p /usr/lib/sysctl.d/99-punktfunk-net.conf >/dev/null 2>&1 || true
|
|
_warn_stale_firewall_ports
|
|
}
|
|
|
|
# An already-open firewall does NOT pick up a port we added to a profile.
|
|
#
|
|
# ufw expands an app profile into concrete rules when you run `ufw allow`, and stores THOSE. Editing
|
|
# /etc/ufw/applications.d later — which is all a package upgrade does — changes nothing about the
|
|
# rules already installed. firewalld is friendlier (its permanent config names the service, so a
|
|
# reload re-reads the XML) but still needs that reload. Either way the operator has an old rule and
|
|
# no reason to suspect it.
|
|
#
|
|
# That is not hypothetical: 47993 (plugin UIs, a separate origin from the console) arrived exactly
|
|
# this way, and on an upgraded ufw box every plugin interface silently became an empty panel in the
|
|
# console. So on upgrade, look at what is actually open and say so — still without touching the
|
|
# running firewall, which stays the operator's call.
|
|
_warn_stale_firewall_ports() {
|
|
# `ufw status verbose` prints each rule with its EXPANDED ports — "47992/tcp (punktfunk-web)"
|
|
# before the refresh, "47992,47993/tcp (punktfunk-web)" after — so one listing answers both "is
|
|
# the profile allowed at all" and "does that rule know the new port". (Plain `ufw status` prints
|
|
# the profile NAME instead, which cannot tell the two apart.)
|
|
if command -v ufw >/dev/null 2>&1 &&
|
|
ufw status verbose 2>/dev/null | grep -q 'punktfunk-web' &&
|
|
! ufw status verbose 2>/dev/null | grep -q '47993'; then
|
|
cat <<'MSG'
|
|
|
|
punktfunk: your ufw rule for 'punktfunk-web' predates TCP 47993, the separate origin plugin UIs
|
|
are served from. Until it is refreshed, plugin interfaces will not load in the web console:
|
|
sudo ufw app update punktfunk-web && sudo ufw reload
|
|
MSG
|
|
fi
|
|
# `--info-service` asks the DAEMON, which answers from the definition it loaded at its last
|
|
# (re)start — precisely the stale copy we are warning about. The file on disk already says 47993.
|
|
if command -v firewall-cmd >/dev/null 2>&1 &&
|
|
firewall-cmd --state >/dev/null 2>&1 &&
|
|
firewall-cmd --query-service=punktfunk-web >/dev/null 2>&1 &&
|
|
! firewall-cmd --info-service=punktfunk-web 2>/dev/null | grep -q '47993'; then
|
|
cat <<'MSG'
|
|
|
|
punktfunk: the punktfunk-web firewalld service now also covers TCP 47993, the separate origin
|
|
plugin UIs are served from. Reload so the running firewall picks it up, or plugin interfaces will
|
|
not load in the web console:
|
|
sudo firewall-cmd --reload
|
|
MSG
|
|
fi
|
|
}
|