diff --git a/CHANGELOG.md b/CHANGELOG.md index 74a5268f..61f5a6cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,20 @@ capability rode on `input`, which every gamepad guide tells users to join — bu arbitrary USB hardware. Operators must `usermod -aG punktfunk "$USER"` and re-login or the pad stops attaching. Ordinary virtual gamepads are unaffected. +> **Known issue in 0.25.0, fixed after it.** Four of the six install paths shipped +> `60-punktfunk.rules` — whose `RUN+=` does `chgrp punktfunk` on the vhci `attach`/`detach` nodes — +> without ever creating the group, so the `chgrp` failed, the nodes stayed root-only, and the pad +> silently never attached. The `usermod` above also fails outright on those boxes with *group +> 'punktfunk' does not exist*. Affected: **Arch/CachyOS upgraded** rather than freshly installed +> (`post_upgrade` called only `_ensure_update_group`), the **NixOS module** (no +> `users.groups.punktfunk`), the **Bazzite sysext** (a group is host state and cannot ride an +> image), and **Steam Deck source installs** (`scripts/steamdeck/install.sh`/`update.sh` handled +> only `input`). The deb and rpm scriptlets were correct throughout — they run one `%post`/`postinst` +> on install and upgrade alike. All four now create the group, and the two that know which user +> runs the host (the Deck scripts and the NixOS module's `host.users`) add that user to it as well. +> Workaround on an unpatched box: +> `sudo groupadd --system punktfunk`, then the `usermod`, then re-login. + **3. Plugins may no longer set `launch.command` or the pre-launch command.** Both run through a shell and are now operator-token only; a plugin that sets them is refused. Third-party plugins that populated them need updating — use the `launcher_ui` / `xbox` launch kinds instead. diff --git a/docs-site/content/docs/arch.md b/docs-site/content/docs/arch.md index 5f19f3d1..6cef020c 100644 --- a/docs-site/content/docs/arch.md +++ b/docs-site/content/docs/arch.md @@ -57,6 +57,18 @@ sudo pacman -Syu punktfunk-scripting # optional: the plugin/script runner (see b sudo usermod -aG input "$USER" # /dev/uinput access for virtual gamepads (re-login to apply) ``` +Only if you want the **virtual Steam Deck controller** (paddles, trackpads, gyro — it reaches games +as a real USB pad, which is why Steam Input adopts it), also join `punktfunk`: + +```sh +sudo usermod -aG punktfunk "$USER" # usbip/vhci access (re-login to apply) +``` + +That is a second group on purpose. It grants write access to the usbip `attach` file, which +materialises an arbitrary emulated USB device — so it stays off the `input` group everyone is +routinely told to join. Join it only on a machine you trust. Without it, everything else still +works and the pad simply arrives as an ordinary Xbox 360 controller. + Each install is a **full** `-Syu`, on purpose: our packages are built against current Arch sonames, and `pacman -Sy ` would drop one onto a system whose other packages are still old — the classic partial upgrade that breaks Arch boxes. To take several in one go, name them on a diff --git a/docs-site/content/docs/bazzite.md b/docs-site/content/docs/bazzite.md index 944ce19f..5d89cf8a 100644 --- a/docs-site/content/docs/bazzite.md +++ b/docs-site/content/docs/bazzite.md @@ -126,6 +126,18 @@ ujust add-user-to-input-group Then **log out and back in**. (A controller that's "detected but does nothing" is almost always this permission, not a client problem.) +Only if you want the **virtual Steam Deck controller** (paddles, trackpads, gyro), also join +`punktfunk` — `usermod` is fine here, because unlike `input` this group is ours and the sysext +creates it on merge: + +```sh +sudo usermod -aG punktfunk "$USER" # then log out and back in +``` + +It is a separate group on purpose: it gates the usbip `attach` file, which can materialise +arbitrary emulated USB hardware, so it is not folded into the group everyone is told to join for +gamepads. Skip it and the pad arrives as an ordinary Xbox 360 controller instead. + ## Configure The RPM ships a Bazzite-tuned config you can copy as your starting point: diff --git a/docs-site/content/docs/fedora.md b/docs-site/content/docs/fedora.md index ad101c2e..b80d3c9c 100644 --- a/docs-site/content/docs/fedora.md +++ b/docs-site/content/docs/fedora.md @@ -93,6 +93,18 @@ sudo dnf install punktfunk sudo usermod -aG input "$USER" # /dev/uinput access for virtual gamepads (re-login to apply) ``` +Only if you want the **virtual Steam Deck controller** (paddles, trackpads, gyro — it reaches games +as a real USB pad, which is why Steam Input adopts it), also join `punktfunk`: + +```sh +sudo usermod -aG punktfunk "$USER" # usbip/vhci access (re-login to apply) +``` + +That is a second group on purpose: it grants write access to the usbip `attach` file, which +materialises an arbitrary emulated USB device, so it stays off the `input` group everyone is +routinely told to join. Join it only on a machine you trust. Skip it and the pad simply arrives as +an ordinary Xbox 360 controller. + Updates later are just `sudo dnf upgrade punktfunk`, followed by `systemctl --user restart punktfunk-host` so the running host picks up the new binary. The package ships the systemd user units, the udev rule, the UDP socket-buffer sysctl tuning, and example diff --git a/docs-site/content/docs/install.md b/docs-site/content/docs/install.md index 25c315b8..11c4bad8 100644 --- a/docs-site/content/docs/install.md +++ b/docs-site/content/docs/install.md @@ -155,6 +155,12 @@ you; on NixOS the module does steps 1 and 2, and [NixOS](#nixos) above has the u input](/docs/input#pen-and-stylus) both need `/dev/uinput` — then re-login. The exact command differs per distro — see your guide (`usermod -aG input "$USER"`, or `ujust add-user-to-input-group` on Bazzite). + + Only if you want the **virtual Steam Deck controller** (paddles, trackpads, gyro), also join + `punktfunk`: `sudo usermod -aG punktfunk "$USER"`. Your package created that group at install + time; it gates the usbip nodes that pad attaches through, and it is separate from `input` on + purpose, because writing them can present arbitrary emulated USB hardware. Join it only on a + machine you trust — skipping it costs you nothing but that one pad type. 2. Put your `host.env` in place, then start the host. Every Linux package ships a systemd **user** unit, so you don't run the host by hand — but that unit reads `~/.config/punktfunk/host.env` and won't start until the file exists. Each package ships a template to copy; your distro and desktop diff --git a/docs-site/content/docs/steamos-host.md b/docs-site/content/docs/steamos-host.md index 14383d06..340ca853 100644 --- a/docs-site/content/docs/steamos-host.md +++ b/docs-site/content/docs/steamos-host.md @@ -74,8 +74,9 @@ It is idempotent — safe to re-run. In one pass it: [plugin store](/docs/plugins) works out of the box — the runner service itself stays opt-in), 3. writes config to `~/.config/punktfunk/` (a generated web-console login password), 4. raises the UDP socket buffers to 32 MB, installs the gamepad udev rule + the `vhci-hcd` autoload - and adds you to the `input` group (virtual gamepads / **native Steam Deck controller passthrough**), - seeds the KDE RemoteDesktop grant for Desktop-mode input, and **registers all of it on SteamOS's + and adds you to the `input` group (virtual gamepads) **and the `punktfunk` group** (the usbip + nodes **native Steam Deck controller passthrough** attaches through — creating that group if it + does not exist yet), seeds the KDE RemoteDesktop grant for Desktop-mode input, and **registers all of it on SteamOS's atomic-update keep list** so OS updates carry it over — the installer asks for your `sudo` password **first, before the long build**, so you can authorise once and walk away, 5. installs + starts the `punktfunk-host` and `punktfunk-web` **systemd user services** (with linger, @@ -102,7 +103,8 @@ When it finishes it prints the web-console URL and how to pair. > surface at all. > **First install — reboot once before streaming.** KWin only authorizes Desktop-mode screen capture -> on a fresh session, and the new `input` group (native Steam Deck controller passthrough) only takes +> on a fresh session, and the new `input` and `punktfunk` groups (native Steam Deck controller +> passthrough) only take > effect on a new login — so after the **first** install, **reboot the Deck** (a re-run that changes > nothing doesn't need it). Streaming **Game Mode** with a generic Xbox pad works right away; **Desktop > capture and the native Steam Deck controller need the reboot.** If a client connects and every @@ -127,6 +129,13 @@ The installer generates a random console login password (printed at the end of s to `~/.config/punktfunk/web.env`. To read it back or set your own, see [The Web Console](/docs/web-console#login-password). +> **Installed before 0.25.0? Rotate that password once.** Older versions of the script created +> `web.env` at the account's default umask, so the console password and session secret sat on disk +> world-readable — any local account could read them. Re-running `install.sh` or `update.sh` now +> tightens the file to `0600` and tells you it did, but a chmod cannot un-leak a secret that was +> already readable. Change `PUNKTFUNK_UI_PASSWORD` in `~/.config/punktfunk/web.env`, then +> `systemctl --user restart punktfunk-web`. + ## 4. Verify ```sh @@ -177,8 +186,11 @@ bash ~/punktfunk/scripts/steamdeck/update.sh --pull ``` Drop `--pull` if you rsync source in yourself. `update.sh` also retrofits anything a newer installer -adds — the plugin runner, the HDR gamescope, the atomic-update keep list, the rebuild check — onto an -older install. +adds — the plugin runner, the HDR gamescope, the atomic-update keep list, the rebuild check, the +`punktfunk` group, and the `0600`/`0700` permissions on `~/.config/punktfunk` — onto an older +install. You do **not** need to run the group or firewall commands from the release notes by hand on +a Deck: the group is the script's job, and stock SteamOS runs no firewall for the port note to apply +to. Rotating the console password after a pre-0.25.0 install is the one thing still on you (above). > **This install follows the canary channel.** An on-device source build tracks `main`, not stable > `vX.Y.Z` releases, so the console offers you the newest `main` build. See @@ -247,7 +259,9 @@ rm -rf ~/.config/punktfunk And the installer may have seeded a KDE RemoteDesktop portal grant at `~/.local/share/flatpak/db/kde-authorized` (only if you had none); remove that file if nothing else on the device relies on it. Your `input` group membership is harmless to keep — drop it with -`sudo gpasswd -d "$USER" input` if you'd rather not. +`sudo gpasswd -d "$USER" input` if you'd rather not. The `punktfunk` group is worth actually +dropping once the host is gone, because it can present emulated USB hardware and nothing else uses +it: `sudo gpasswd -d "$USER" punktfunk`. See [Uninstalling](/docs/uninstall) for the other install methods and what each one leaves behind. @@ -258,9 +272,14 @@ See [Uninstalling](/docs/uninstall) for the other install methods and what each - **Keep the device awake.** On handhelds, Game Mode auto-suspends on idle, which drops the host off the network mid stream — disable auto-suspend (Settings → Power) for a headless host. - **Native Steam Deck controller passthrough** presents the client's pad as a real Steam Deck - controller (paddles, trackpads, gyro) via a virtual USB device — that needs the `input` group and the + controller (paddles, trackpads, gyro) via a virtual USB device — that needs the `input` **and + `punktfunk`** groups and the `vhci-hcd` module live, so it only works **after the first-install reboot** above; until then the pad - degrades to a generic Xbox 360 controller (still fully playable). If you're streaming *to* another + degrades to a generic Xbox 360 controller (still fully playable). The second group is separate on + purpose: it can present arbitrary emulated USB hardware, which is why it is not folded into the + `input` group every gamepad guide tells you to join. Check both with `id -nG`, and check the nodes + themselves with `ls -l /sys/devices/platform/vhci_hcd.0/attach` — group `punktfunk`, mode `0660`. + If you're streaming *to* another Steam Deck, also set Steam Input to **Off** for Punktfunk on that Deck — see [Stream to a Steam Deck](/docs/steam-deck). - **It survives OS updates — automatically.** SteamOS A/B updates rebuild `/etc` and can move diff --git a/docs-site/content/docs/troubleshooting.md b/docs-site/content/docs/troubleshooting.md index a59de68c..f79bd9ee 100644 --- a/docs-site/content/docs/troubleshooting.md +++ b/docs-site/content/docs/troubleshooting.md @@ -250,6 +250,31 @@ switch mouse mode, disconnect, fullscreen — are in button (see [Updating](/docs/updating)). Swapping `punktfunk-host.exe` by hand does not fix it, because the stale controller device keeps the driver it was already bound to. +## The pad works, but arrives as an Xbox 360 controller instead of a Steam Deck + +Only the **virtual Steam Deck controller** (paddles, trackpads, gyro) is missing here — ordinary +gamepad input is fine. That pad reaches games as a real USB device over usbip, and the sysfs files +it attaches through are owned by a group called `punktfunk`, separate from `input`. Four things +have to line up on the Linux host, and none of them announces itself when it doesn't: + +```sh +getent group punktfunk # the group exists at all +id -nG | tr ' ' '\n' | grep -x punktfunk # ...and you are in it +ls -l /sys/devices/platform/vhci_hcd.0/attach # owned by punktfunk, mode 0660 +lsmod | grep vhci_hcd # the transport module is loaded +``` + +If the group is missing entirely, the udev rule tried to `chgrp` to a group nobody created, so the +nodes stayed root-only. That was the case on installs that reached 0.25.0 by **upgrade** on Arch, +on NixOS, on the Bazzite sysext, and on Steam Deck source installs. Re-running your package +manager's upgrade (or `update.sh` on a Deck) creates it now; otherwise `sudo groupadd --system +punktfunk` by hand. Then `sudo usermod -aG punktfunk "$USER"` and **log out and back in** — group +changes only reach the host's `systemd --user` service on a fresh login, and on a Deck a reboot is +the reliable way to get one. + +Joining the group is optional, and there is a real reason it is not automatic: writing that +`attach` file materialises an arbitrary emulated USB device. Skip it on a machine you share. + ## Copy and paste between host and client does nothing The shared clipboard needs **two** separate switches on, and turning on only one looks exactly like diff --git a/docs-site/content/docs/ubuntu.md b/docs-site/content/docs/ubuntu.md index 882ec553..38860187 100644 --- a/docs-site/content/docs/ubuntu.md +++ b/docs-site/content/docs/ubuntu.md @@ -111,6 +111,18 @@ re-login so the new group membership takes effect: sudo usermod -aG input "$USER" # re-login to apply ``` +Only if you want the **virtual Steam Deck controller** (paddles, trackpads, gyro), also join +`punktfunk`. That pad reaches games as a real USB device over usbip — which is what makes Steam +Input adopt it — and the group gating those nodes is deliberately separate from `input`, because +writing the usbip `attach` file can materialise arbitrary emulated USB hardware: + +```sh +sudo usermod -aG punktfunk "$USER" # re-login to apply +``` + +Join it only on a machine you trust. Skip it and everything else still works; the pad just arrives +as an ordinary Xbox 360 controller. + ## 4. Check it installed Before moving on, confirm the binary is there and nothing else is competing for the same job: diff --git a/docs-site/content/docs/uninstall.md b/docs-site/content/docs/uninstall.md index 8a1972de..4413c09f 100644 --- a/docs-site/content/docs/uninstall.md +++ b/docs-site/content/docs/uninstall.md @@ -58,16 +58,20 @@ sudo rm -f /etc/apt/sources.list.d/punktfunk.list /etc/apt/keyrings/punktfunk.as sudo apt update ``` -**Left behind:** `~/.config/punktfunk`, and the empty `punktfunk-update` system group the package -created for [one-click updates](/docs/updating). Clear them with: +**Left behind:** `~/.config/punktfunk`, and the two system groups the package created — the empty +`punktfunk-update` for [one-click updates](/docs/updating), and `punktfunk` for the virtual Steam +Deck pad's usbip nodes. Clear them with: ```sh rm -rf ~/.config/punktfunk sudo groupdel punktfunk-update +sudo gpasswd -d "$USER" punktfunk; sudo groupdel punktfunk ``` Your `input` group membership is harmless to keep (it is a stock Ubuntu group). Drop it with -`sudo gpasswd -d "$USER" input` if you'd rather not have it. If you opened the firewall, close it +`sudo gpasswd -d "$USER" input` if you'd rather not have it. The `punktfunk` group above is worth +dropping rather than keeping: it can present arbitrary emulated USB hardware, and with the host +gone nothing uses it. If you opened the firewall, close it again: `sudo ufw delete allow punktfunk-native` (and `punktfunk-gamestream` / `punktfunk-web` if you allowed those too). @@ -80,9 +84,12 @@ sudo dnf remove punktfunk punktfunk-web punktfunk-client punktfunk-scripting sudo rm -f /etc/yum.repos.d/punktfunk.repo ``` -**Left behind:** `~/.config/punktfunk`, the `punktfunk-update` group, and the signing key dnf +**Left behind:** `~/.config/punktfunk`, the `punktfunk-update` and `punktfunk` groups, and the +signing key dnf imported into the rpm keyring when it first installed a Punktfunk package. Clear the first two with -`rm -rf ~/.config/punktfunk` and `sudo groupdel punktfunk-update`. The key is harmless to leave — on +`rm -rf ~/.config/punktfunk` and `sudo groupdel punktfunk-update`; drop `punktfunk` too +(`sudo gpasswd -d "$USER" punktfunk; sudo groupdel punktfunk`) — it can present arbitrary +emulated USB hardware and nothing uses it once the host is gone. The key is harmless to leave — on its own it only marks packages from our registry as trusted, and nothing fetches them once the repo file is gone. @@ -127,6 +134,7 @@ Three things it created outside `/usr` stay behind: ```sh sudo rm -f /etc/modules-load.d/punktfunk.conf /etc/udev/rules.d/60-punktfunk.rules sudo groupdel punktfunk-update +sudo gpasswd -d "$USER" punktfunk; sudo groupdel punktfunk ``` And your config, if you want it gone: `rm -rf ~/.config/punktfunk`. See @@ -150,8 +158,10 @@ repo's signing key from pacman's keyring: sudo pacman-key --delete E0CA04465C99C936E0B0C6510A317015A34DDD69 ``` -**Left behind:** `~/.config/punktfunk` and the `punktfunk-update` group — -`rm -rf ~/.config/punktfunk` and `sudo groupdel punktfunk-update` clear them. On CachyOS, close the +**Left behind:** `~/.config/punktfunk` and the `punktfunk-update` and `punktfunk` groups — +`rm -rf ~/.config/punktfunk`, `sudo groupdel punktfunk-update`, and +`sudo gpasswd -d "$USER" punktfunk; sudo groupdel punktfunk` clear them. Drop that last one +rather than keeping it: it can present arbitrary emulated USB hardware. On CachyOS, close the ufw rules you opened: `sudo ufw delete allow punktfunk-native`. ### SteamOS / Steam Deck host (on-device build) @@ -172,8 +182,10 @@ atomic-update keep list is what carries those files through every SteamOS update stay on the device indefinitely. **Left behind:** `~/.config/punktfunk` (`rm -rf ~/.config/punktfunk` for a clean slate), your -`input` group membership, and — if the installer seeded it because you had none — the KDE -RemoteDesktop portal grant at `~/.local/share/flatpak/db/kde-authorized`. +`input` and `punktfunk` group memberships, and — if the installer seeded it because you had none — +the KDE RemoteDesktop portal grant at `~/.local/share/flatpak/db/kde-authorized`. Drop the second +group once the host is gone — it can present arbitrary emulated USB hardware and nothing else on a +Deck uses it: `sudo gpasswd -d "$USER" punktfunk; sudo groupdel punktfunk`. ### NixOS @@ -184,9 +196,9 @@ There is nothing to uninstall imperatively — remove what you declared: input. 3. Rebuild: `sudo nixos-rebuild switch`. -The unit, udev rules, sysctl tuning, firewall ports and `input` group membership all disappear with -the generation. The store paths stay until you garbage-collect, and `~/.config/punktfunk` — which -the module never managed — stays regardless. +The unit, udev rules, sysctl tuning, firewall ports and the `input` / `punktfunk` group memberships +all disappear with the generation. The store paths stay until you garbage-collect, and +`~/.config/punktfunk` — which the module never managed — stays regardless. ## Windows host diff --git a/docs/releases/v0.25.0.md b/docs/releases/v0.25.0.md index 4a58fec7..2f3b56bb 100644 --- a/docs/releases/v0.25.0.md +++ b/docs/releases/v0.25.0.md @@ -16,12 +16,12 @@ This is the largest release so far — close to four hundred changes. The short Most people need to do nothing. Check this list if any of it applies to you. -- **Linux, if you use the virtual Steam Deck controller: join a new group.** That permission used to ride on `input`, which every gamepad guide tells you to join — but it can emulate arbitrary USB hardware, so it now has its own. Run `sudo usermod -aG punktfunk "$USER"` and log back in, or the virtual Deck pad stops attaching. Ordinary virtual gamepads are unaffected, and you should only join this group on a machine you trust. +- **Linux hosts, if you use the virtual Steam Deck controller: join a new group.** That permission used to ride on `input`, which every gamepad guide tells you to join — but it can emulate arbitrary USB hardware, so it now has its own. Run `sudo usermod -aG punktfunk "$USER"` and log back in, or the virtual Deck pad stops attaching. Ordinary virtual gamepads are unaffected, and you should only join this group on a machine you trust. This is a host-side step: a Linux machine you only *stream from* needs nothing. **If that command reports `group 'punktfunk' does not exist`**, you are on one of the install paths that shipped 0.25.0 without creating it — an Arch box upgraded rather than freshly installed, NixOS, the Bazzite sysext, or a Steam Deck source install. `sudo groupadd --system punktfunk` first, then the `usermod`; a later update creates it for you. - **Add-on interfaces moved to their own port (47993).** An existing firewall rule will not pick it up when you upgrade, and the symptom is a blank panel where the add-on's interface should be. On Linux the package prints the exact command — `sudo ufw app update punktfunk-web && sudo ufw reload`, or a firewalld reload. With Docker, publish `47993` as well. If you reach your console over a self-signed certificate, your browser needs to trust the new port once; the console shows a card with a link that does it. - **Windows hosts now need Steam installed** for streamed audio — it never has to run. Without it the host streams video only, and picks the drivers up on its own if you install Steam later. Two new devices, "Punktfunk Speakers" and "Punktfunk Microphone", will appear in your sound settings; that is this feature working. If you already have VB-CABLE, leave it — it still works as a fallback and is not removed. - **Saving a game with a custom launch command asks for your console password again**, and add-ons may no longer set launch commands at all. A third-party add-on that did will need updating by its author. - **A fresh install now runs the add-on runner by default.** Upgrades are untouched — if you switched it off, it stays off. -- **If you set up a Steam Deck with the install script, consider rotating your console password.** It was written to a world-readable file; that is fixed. +- **If you set up a Steam Deck with the install script, rotate your console password.** It was written to a world-readable file, so any local account could read it — and fixing the permissions does not un-share a password that was already readable, which is why this one is worth actually doing rather than considering. New installs are written correctly. On an existing one, re-run `install.sh` or `update.sh` to tighten the file, then change `PUNKTFUNK_UI_PASSWORD` in `~/.config/punktfunk/web.env` and `systemctl --user restart punktfunk-web`. - **If you play with motion controls, your aim sensitivity will change.** The gyro pipeline was wrong at every stage and is now measured against a real controller, so the numbers moved: a controller presented to games as a DualShock 4 was reporting motion **forty times too fast**, and a PlayStation pad plugged into an Android phone was reporting about **30% short**. If you turned a game's sensitivity down or up to cope, set it back. The Android case is the one people plausibly tuned around — that aim now needs a *higher* in-game sensitivity than you are used to. ## New diff --git a/packaging/arch/punktfunk-host.install b/packaging/arch/punktfunk-host.install index 07af7c24..894da933 100644 --- a/packaging/arch/punktfunk-host.install +++ b/packaging/arch/punktfunk-host.install @@ -67,6 +67,12 @@ MSG 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 diff --git a/packaging/bazzite/punktfunk-sysext.sh b/packaging/bazzite/punktfunk-sysext.sh index 61468e1d..5c9e4857 100644 --- a/packaging/bazzite/punktfunk-sysext.sh +++ b/packaging/bazzite/punktfunk-sysext.sh @@ -167,6 +167,13 @@ post_merge() { # The (empty) opt-in group for web-console-triggered updates (the sysext ships the pf-update # helper + unit + polkit rule in its /usr; the group can't ride an image) — nobody is auto-added. getent group punktfunk-update >/dev/null 2>&1 || groupadd --system punktfunk-update 2>/dev/null || : + # 'punktfunk' owns the vhci attach/detach nodes the rule we just mirrored into /etc chgrp's to. + # A group cannot ride an image either (/etc/group is host state), and the deb/rpm scriptlets that + # would normally create it never run on an image-based install — so without this the chgrp fails, + # attach/detach stay root-only and the virtual Steam Deck pad never attaches. Deliberately NOT + # 'input': writing 'attach' materialises an arbitrary emulated USB device (review 2026-08-05 M-4), + # so it stays a group users join on purpose — see `ujust add-user-to-input-group` for the other one. + getent group punktfunk >/dev/null 2>&1 || groupadd --system punktfunk 2>/dev/null || : modprobe vhci-hcd 2>/dev/null || : # Re-fire the vhci rule against the (possibly already-present) controller so attach/detach pick up # the input-group ownership even when the module's original add event predated the reloaded rule. diff --git a/packaging/nix/nixos-module.nix b/packaging/nix/nixos-module.nix index fe684878..d5dcf8a3 100644 --- a/packaging/nix/nixos-module.nix +++ b/packaging/nix/nixos-module.nix @@ -1,9 +1,10 @@ # NixOS integration for punktfunk — the declarative equivalent of everything the RPM/deb do in # their %install + %post (packaging/rpm/punktfunk.spec, packaging/debian/build-deb.sh): # the systemd *user* service, the uinput/uhid/vhci udev rules, the vhci-hcd autoload, the 32 MB -# UDP socket-buffer sysctls, the firewall openers, the `input`-group membership for virtual -# gamepads, the management web console (`services.punktfunk.web`, on by default with the host — the -# RPM/deb Recommends), and the opt-in plugin/script runner (`services.punktfunk.scripting`). +# UDP socket-buffer sysctls, the firewall openers, the `input`- and `punktfunk`-group membership +# for virtual gamepads, the management web console (`services.punktfunk.web`, on by default with +# the host — the RPM/deb Recommends), and the opt-in plugin/script runner +# (`services.punktfunk.scripting`). # # Usage (flake): # { inputs.punktfunk.url = "git+https://git.unom.io/unom/punktfunk"; @@ -111,9 +112,11 @@ in default = [ ]; example = [ "alice" ]; description = '' - Users to add to the `input` group — required for the virtual gamepads the host creates - (`/dev/uinput`, `/dev/uhid`, and the usbip/vhci virtual Steam Deck). The host runs as - these users' `systemd --user` service. + Users to add to the `input` and `punktfunk` groups — required for the virtual gamepads + the host creates: `input` covers `/dev/uinput` and `/dev/uhid`, `punktfunk` covers the + usbip/vhci nodes the virtual Steam Deck pad attaches through. The second is separate on + purpose — it can emulate arbitrary USB hardware, so only list users you would trust with + that. The host runs as these users' `systemd --user` service. ''; }; @@ -326,9 +329,22 @@ in ]; # `input` group membership for the virtual-gamepad nodes (mirrors the RPM's usermod hint). + # + # `punktfunk` is the SECOND group 60-punktfunk.rules needs: it owns the usbip vhci + # attach/detach nodes, and is deliberately not `input` because writing `attach` materialises + # an arbitrary emulated USB device — a root-only kernel primitive that must not ride on the + # group every gamepad guide tells you to join (security-review 2026-08-05 M-4). Declaring the + # group is not optional: the rule shells out to `chgrp punktfunk`, which fails outright if + # nothing ever created it, leaving the nodes root-only and the virtual Steam Deck pad unable + # to attach. Membership follows `host.users`, which is already the explicit "these users run + # the host" list this option's description scopes to the usbip/vhci pad. users.groups.input = { }; + users.groups.punktfunk = { }; users.users = genAttrs cfg.host.users (_: { - extraGroups = [ "input" ]; + extraGroups = [ + "input" + "punktfunk" + ]; }); # Status-tray autostart entry (self-gating: `--autostart` exits unless this user runs a host). diff --git a/scripts/steamdeck/README.md b/scripts/steamdeck/README.md index c606d869..118affd0 100644 --- a/scripts/steamdeck/README.md +++ b/scripts/steamdeck/README.md @@ -85,7 +85,13 @@ default `pf2`), `PUNKTFUNK_MGMT_PORT` (47990), `PUNKTFUNK_WEB_PORT` (47992). - **System tuning (sudo):** `/etc/sysctl.d/99-punktfunk-net.conf` (32 MB UDP buffers — the #1 high-bitrate lever), `/etc/udev/rules.d/60-punktfunk.rules` (`uinput`/`uhid` access), `/etc/modules-load.d/punktfunk.conf` (`vhci-hcd` for the native Deck pad), `$USER` in the `input` - group — and `/etc/atomic-update.conf.d/punktfunk.conf`, which registers the three files on + group **and in `punktfunk`** — the latter created here if missing, because the udev rule + `chgrp`s the vhci `attach`/`detach` nodes to it and a rule that names a nonexistent group fails + silently, leaving the native Deck pad unable to attach (the deb/rpm/arch scriptlets `groupadd` it; + nothing on this path did until now). It is separate from `input` on purpose: writing `attach` + materialises an arbitrary emulated USB device (security-review 2026-08-05 M-4). Drop it with + `sudo gpasswd -d "$USER" punktfunk` if you would rather stream without that pad. + Plus `/etc/atomic-update.conf.d/punktfunk.conf`, which registers the three files on SteamOS's atomic-update keep list so A/B OS updates carry them over (verified: without it an update silently strips them — pads degrade to Xbox 360, buffers drop to 208 KB). @@ -104,8 +110,13 @@ host advertises over mDNS as `_punktfunk._udp`, so clients discover it automatic - **distrobox required.** If missing: `curl -sfL https://raw.githubusercontent.com/89luca89/distrobox/main/install | sh -s -- --prefix ~/.local` (then ensure `~/.local/bin` is on PATH). - **First build is slow** (~10–15 min + ~1 GB toolchain/image). Incremental afterwards. -- **No passwordless sudo** → the installer skips the sysctl/udev/input steps with a warning; high - bitrates will drop packets until you apply `99-punktfunk-net.conf` and join `input` yourself. +- **No passwordless sudo** → the installer skips the sysctl/udev/group steps with a warning; high + bitrates will drop packets until you apply `99-punktfunk-net.conf` and join `input` (and + `punktfunk`, for the native Deck pad) yourself. The script prints the exact commands. +- **Installed before 0.25.0?** `web.env` was written at the ambient umask, i.e. world-readable, so + the console password and session secret leaked to every local account. `install.sh`/`update.sh` + now tighten `~/.config/punktfunk` to `0700` and `web.env` to `0600` on every run and say so — + but rotate `PUNKTFUNK_UI_PASSWORD` afterwards, because a chmod does not un-leak a read secret. - **Game Mode auto-suspend** drops the host off the network on idle — disable it (Settings → Power) for a headless host. - **WiFi tx ceiling** ≈ 250 Mbps goodput (a Deck hardware/driver packet-rate limit, band-independent); diff --git a/scripts/steamdeck/install.sh b/scripts/steamdeck/install.sh index 622d972c..2dfdd13e 100755 --- a/scripts/steamdeck/install.sh +++ b/scripts/steamdeck/install.sh @@ -23,6 +23,13 @@ ok() { printf '\033[1;32m ok\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m !!\033[0m %s\n' "$*" >&2; } die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } have() { command -v "$1" >/dev/null 2>&1; } +# Create a system group if it is missing (needs sudo). Idempotent, and mirrors what the +# deb/rpm/arch scriptlets do — a udev rule that chgrp's to a group nobody created fails silently. +ensure_group() { + getent group "$1" >/dev/null 2>&1 && return 0 + sudo groupadd --system "$1" 2>/dev/null || return 1 + ok "created the '$1' system group" +} # --- options --------------------------------------------------------------- SRC="${PUNKTFUNK_SRC:-$HOME/punktfunk}" @@ -252,8 +259,21 @@ EOF ) chmod 600 "$CONFIG/web.env" ok "wrote web.env (generated login password)" -else - [ "$WITH_WEB" = 1 ] && ok "web.env exists (login password unchanged)" +elif [ "$WITH_WEB" = 1 ] && [ -f "$CONFIG/web.env" ]; then + # THE belt the comment above promises. It used to live inside the create-only branch, so it + # only ever ran on files that had just been written 0600 anyway — every install that predates + # the L-19 fix still has its console password and session secret on disk at the Deck's ambient + # umask (0644, world-readable). Tighten it here, and say so out loud: a chmod does not un-leak + # a secret that was already readable by every local account, so the password needs rotating. + if find "$CONFIG/web.env" -maxdepth 0 -perm /0077 2>/dev/null | grep -q .; then + chmod 600 "$CONFIG/web.env" + warn "web.env was group/world-readable — an older install wrote it at the default umask." + warn "Tightened to 0600, but that does NOT un-expose the password it already leaked to every" + warn "local account. Rotate it: edit PUNKTFUNK_UI_PASSWORD in $CONFIG/web.env, then" + warn " systemctl --user restart punktfunk-web" + else + ok "web.env exists (login password unchanged, mode already 0600)" + fi fi # --- 3b. HDR gamescope (punktfunk-gamescope, best-effort) ------------------ @@ -264,8 +284,8 @@ fi # host.env only while the binary provably runs on SteamOS. PUNKTFUNK_SRC="$SRC" PUNKTFUNK_BOX="$BOX" bash "$SRC/scripts/steamdeck/build-gamescope.sh" -# --- 4. system tuning (needs sudo: UDP buffers + gamepad udev rule + vhci-hcd + input group) -------- -log "System tuning (UDP buffers + gamepad rules + vhci-hcd + input group)" +# --- 4. system tuning (needs sudo: UDP buffers + udev rule + vhci-hcd + input/punktfunk groups) ----- +log "System tuning (UDP buffers + gamepad rules + vhci-hcd + input/punktfunk groups)" # sudo was acquired up front in preflight (SUDO_OK) so this never stalls behind the long build; a # skip here (no password / no TTY) was already reported loudly there. if [ "$SUDO_OK" = 1 ]; then @@ -293,6 +313,34 @@ if [ "$SUDO_OK" = 1 ]; then NEED_RELOGIN=1 warn "added $USER to the 'input' group (applies on next login)" fi + # The 'punktfunk' group owns the usbip vhci attach/detach nodes (see 60-punktfunk.rules). + # Deliberately NOT 'input': writing 'attach' hands the kernel a caller-supplied socket fd and + # materialises an arbitrary emulated USB device — a root-only primitive that must not ride on + # the group every gamepad guide tells you to join (security-review 2026-08-05 M-4). + # + # The deb/rpm/arch scriptlets groupadd this; NOTHING on the Deck path did. So the rule we just + # installed ran `chgrp punktfunk` against a group that did not exist, the chgrp failed, the + # attach/detach files stayed root-only, and the native Steam Deck pad never attached — with no + # error anywhere the user would look. Create it and join it here: unlike a general-purpose + # host, running THIS script IS the statement "make my Deck a host with native pad passthrough". + # `if ensure_group` (not `ensure_group || true`): a failed groupadd must not fall through to a + # usermod against a group that does not exist, which under `set -e` would kill the installer + # here — after the long build and before the services are installed. + if ensure_group punktfunk; then + if id -nG "$USER" | grep -qw punktfunk; then + ok "already in the 'punktfunk' group (usbip vhci access)" + else + sudo usermod -aG punktfunk "$USER" + NEED_RELOGIN=1 + warn "added $USER to the 'punktfunk' group — the native Steam Deck pad needs it. That group" + warn "can emulate arbitrary USB devices; drop it with 'sudo gpasswd -d $USER punktfunk' if" + warn "you would rather stream without the native pad." + fi + else + warn "could not create the 'punktfunk' group — the native Steam Deck pad will not attach" + warn "(everything else works; the pad arrives as a generic Xbox 360 controller). By hand:" + warn " sudo groupadd --system punktfunk; sudo usermod -aG punktfunk $USER" + fi # SteamOS A/B updates rebuild /etc and DROP everything not on Valve's keep list — verified # live: an OS update stripped the udev rule + vhci autoload + UDP sysctl (gamepads silently # degrade to Xbox 360, buffers back to 208 KB). The sanctioned fix is a preserve drop-in in @@ -303,15 +351,18 @@ if [ "$SUDO_OK" = 1 ]; then fi else warn "no usable sudo — SKIPPED system tuning. Gamepad passthrough + clean streaming need root (udev" - warn "rule, 'input' group, vhci-hcd, UDP buffers) — there is no user-space way to do these." + warn "rule, 'input' + 'punktfunk' groups, vhci-hcd, UDP buffers) — there is no user-space way to do these." warn "A stock SteamOS 'deck' account has NO password, so sudo can't work until you set one:" warn " passwd # set a sudo password once, then re-run this script" warn "Or apply it by hand (then reboot):" warn " sudo install -m644 $SRC/scripts/60-punktfunk.rules /etc/udev/rules.d/ &&" warn " sudo install -m644 $SRC/scripts/punktfunk-modules.conf /etc/modules-load.d/punktfunk.conf &&" - warn " sudo usermod -aG input $USER &&" + warn " sudo groupadd --system punktfunk;" + warn " sudo usermod -aG input,punktfunk $USER &&" warn " printf 'net.core.wmem_max=33554432\\nnet.core.rmem_max=33554432\\n' | sudo tee /etc/sysctl.d/99-punktfunk-net.conf &&" warn " sudo sysctl --system && sudo udevadm control --reload-rules && sudo udevadm trigger" + warn "('punktfunk' owns the usbip vhci nodes the native Steam Deck pad attaches through — without" + warn " it the pad silently never appears. Omit it if you do not want that pad.)" fi # --- 5. systemd user services --------------------------------------------- diff --git a/scripts/steamdeck/update.sh b/scripts/steamdeck/update.sh index ac163598..a3953c76 100755 --- a/scripts/steamdeck/update.sh +++ b/scripts/steamdeck/update.sh @@ -12,6 +12,13 @@ ok() { printf '\033[1;32m ok\033[0m %s\n' "$*"; } # found") aborted the whole update before the service restarts. warn() { printf '\033[1;33m !!\033[0m %s\n' "$*" >&2; } die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } +# Create a system group if it is missing (needs sudo). Idempotent, and mirrors what the +# deb/rpm/arch scriptlets do — a udev rule that chgrp's to a group nobody created fails silently. +ensure_group() { + getent group "$1" >/dev/null 2>&1 && return 0 + sudo groupadd --system "$1" 2>/dev/null || return 1 + ok "created the '$1' system group" +} SRC="${PUNKTFUNK_SRC:-$HOME/punktfunk}" BOX="${PUNKTFUNK_BOX:-pf2}" @@ -81,11 +88,27 @@ EOF ok "punktfunk-rebuild-check.service installed (auto-rebuild after SteamOS updates)" fi +CONFIG="$HOME/.config/punktfunk" + +# Secret hygiene, retrofitted. install.sh §3 does this for fresh installs — but only install.sh +# ever did, so a Deck that was set up once and only ever *updated* since kept the old modes +# forever. This directory holds web.env (console login password + session secret), the mgmt token +# and the host key; a plain `mkdir -p` left it 0755 at the Deck's ambient umask and web.env itself +# 0644, i.e. readable by every local account (2026-08-05 review L-19). Both chmods are idempotent. +[ -d "$CONFIG" ] && chmod 700 "$CONFIG" 2>/dev/null || true +if [ -f "$CONFIG/web.env" ] && find "$CONFIG/web.env" -maxdepth 0 -perm /0077 2>/dev/null | grep -q .; then + chmod 600 "$CONFIG/web.env" + warn "web.env was group/world-readable — an older install wrote it at the default umask." + warn "Tightened to 0600, but that does NOT un-expose the password it already leaked to every" + warn "local account. Rotate it: edit PUNKTFUNK_UI_PASSWORD in $CONFIG/web.env, then" + warn " systemctl --user restart punktfunk-web" +fi + # Retrofit config that install.sh now writes but older installs predate (both idempotent): # RADV_PERFTEST — Van Gogh RADV still gates VK_KHR_video_encode_* behind it; without it the # Vulkan backend can't open and sessions silently fall back to libav VAAPI. The KWin .desktop — # KWin only grants the restricted capture/input globals to the exe a .desktop authorizes. -HOST_ENV="$HOME/.config/punktfunk/host.env" +HOST_ENV="$CONFIG/host.env" if [ -f "$HOST_ENV" ] && ! grep -q '^RADV_PERFTEST=' "$HOST_ENV"; then printf '\n# Van Gogh RADV gates VK_KHR_video_encode_* behind this (Vulkan Video encode).\nRADV_PERFTEST=video_encode\n' >> "$HOST_ENV" ok "host.env: added RADV_PERFTEST=video_encode" @@ -128,6 +151,25 @@ if [ "$SUDO_OK" = 1 ]; then sudo usermod -aG input "$USER" warn "added $USER to the 'input' group — REBOOT (or log out/in) for it to apply" fi + # 'punktfunk' owns the usbip vhci attach/detach nodes (60-punktfunk.rules), deliberately NOT + # 'input' — writing 'attach' materialises an arbitrary emulated USB device, a root-only kernel + # primitive that must not ride on the group every gamepad guide tells you to join + # (security-review 2026-08-05 M-4). No Deck install ever created it, so the rule's chgrp failed + # and the native Steam Deck pad silently never attached. Retrofit both group and membership. + # `if ensure_group` (not `ensure_group || true`): a failed groupadd must not fall through to a + # usermod against a group that does not exist — under `set -e` that would abort the update + # before the service restarts at the bottom, leaving the host down. + if ensure_group punktfunk; then + if id -nG "$USER" | grep -qw punktfunk; then :; else + sudo usermod -aG punktfunk "$USER" + warn "added $USER to the 'punktfunk' group (usbip vhci — the native Steam Deck pad needs it)" + warn " — REBOOT (or log out/in) for it to apply. That group can emulate arbitrary USB" + warn " devices; 'sudo gpasswd -d $USER punktfunk' drops it if you do not want the native pad." + fi + else + warn "could not create the 'punktfunk' group — the native Steam Deck pad will not attach." + warn "By hand: sudo groupadd --system punktfunk; sudo usermod -aG punktfunk $USER" + fi # Register the tuning on Valve's atomic-update preserve list (see install.sh §4): without # this, every SteamOS A/B update strips the three files above again (verified live — # gamepads silently degrade to Xbox 360, UDP buffers back to 208 KB).