ci / bun-nix (pull_request) Successful in 29s
ci / docs-site (pull_request) Successful in 1m37s
ci / web (pull_request) Successful in 2m39s
ci / rust-arm64 (pull_request) Successful in 4m10s
ci / rust (pull_request) Successful in 6m50s
nix / flake (pull_request) Failing after 23m28s
60-punktfunk.rules chgrp's the usbip vhci attach/detach nodes to a dedicated
`punktfunk` group (security-review 2026-08-05 M-4: writing `attach` materialises
an arbitrary emulated USB device, so it must not ride on `input`). Four of the
six install paths shipped that rule in 0.25.0 without ever creating the group.
chgrp then failed, the nodes stayed root:root 0644, and the virtual Steam Deck
pad silently never attached — while `usermod -aG punktfunk` failed outright with
"group 'punktfunk' does not exist".
Affected and fixed:
* arch — post_upgrade() called only _ensure_update_group, so every box that
reached 0.25.0 by `pacman -Syu` missed it; post_install was correct.
* nix — no users.groups.punktfunk at all, though host.users' own description
already promised the usbip/vhci pad. Declares it now and adds
host.users to both groups.
* bazzite sysext — a group is host state and cannot ride an image, and the
deb/rpm scriptlets that would create it never run there.
* steamdeck install.sh/update.sh — handled `input` only. Both now create the
group and join it: running that script IS the statement "make my
Deck a host with native pad passthrough".
deb and rpm were correct throughout (one postinst/%post for install + upgrade).
Also on the Deck path: web.env secret hygiene. install.sh's `chmod 600` sat
inside the create-only branch despite a comment calling it "the idempotent belt
for a pre-existing file", and update.sh never touched the config dir at all — so
an install set up once and only updated since kept web.env world-readable
(0644) with the console password and session secret in it. Both scripts now
harden ~/.config/punktfunk to 0700 and web.env to 0600 on every run, and say so
loudly, because a chmod does not un-leak an already-readable secret: the
password still needs rotating.
Both group blocks are `if ensure_group ...` rather than `ensure_group || true`:
a failed groupadd must not fall through to a usermod against a nonexistent
group, which under `set -e` aborted install.sh after the long build and
update.sh before the service restart (verified: exit 6, no restart).
Docs: the group is now documented where people actually look — the per-distro
guides, install.md, steamos-host.md, a new troubleshooting entry for "pad
arrives as an Xbox 360 controller", and the uninstall pages. The 0.25.0 notes
gain the "group does not exist" caveat and turn the password bullet from
"consider rotating" into a real instruction, and CHANGELOG records the known
issue against the breaking change that introduced it.
Verified: bash -n on all four scripts; the arch scriptlet's post_upgrade driven
in a container (creates the group, idempotent on re-run); the ensure_group
helper and both membership branches, including a control that reproduces the
original bug (chgrp to a missing group leaves the node root:root 0644); the
find -perm /0077 probe across 0644/0640/0604/0600/0400 on GNU findutils;
`nix flake check --no-build` (the exact CI gate) and a NixOS eval showing
alice.extraGroups == ["input","punktfunk"]; docs-site build + typecheck.
123 lines
6.2 KiB
Plaintext
123 lines
6.2 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
|
|
# Also on UPGRADE, not just post_install: 'punktfunk' was introduced in 0.25.0, so every box that
|
|
# reached it by `pacman -Syu` from 0.24.x ran only this function and never got the group at all —
|
|
# leaving 60-punktfunk.rules to chgrp to a nonexistent group, the vhci attach/detach nodes
|
|
# root-only, and the virtual Steam Deck pad silently unable to attach. groupadd is idempotent, so
|
|
# this is a no-op on boxes that installed fresh.
|
|
_ensure_punktfunk_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
|
|
}
|