Files
punktfunk/scripts
enricobuehler 767e67caf4 feat(packaging): grant the host CAP_SYS_NICE, without which the GPU-priority lever does nothing
Wave-2 PW1, second half. The companion commit wires `PYROWAVE_QUEUE_PRIORITY` into the Linux
PyroWave device; this is what makes it work on a packaged host.

Measured on .21 (RTX 5070 Ti, NVIDIA 610.43.02), same binary in both arms:

  as packaged (no capability)     every class refused, REALTIME *and* HIGH -> default priority
  same binary, cap_sys_nice+ep    granted REALTIME on the FIRST attempt, no downgrade

RADV behaves the same way. So this is not the RADV-specific "expect one downgrade to HIGH" the
plan predicted — without the capability there is no elevated priority at all, on any vendor, and
the knob is decoration.

Worth being precise about what is being granted, because it is a network-facing daemon.
CAP_SYS_NICE permits raising scheduling priority (nice, ioprio, affinity, RT class) and nothing
else: no filesystem access, no network privilege, no user switching, and it is NOT setuid. The
repo already ships exactly this capability on its gamescope binary for the same reason. Two side
effects that will otherwise confuse someone debugging: a capability-carrying binary is AT_SECURE,
so the loader ignores LD_LIBRARY_PATH/LD_PRELOAD for it (note this box was propped up by exactly
such a shim during the ffmpeg-9 soname break — that workaround would now be silently ignored), and
core dumps are suppressed by default.

Per packaging path, because none of them are the same:

- Arch: a `_grant_sched_capability` in the scriptlet, called from post_install AND post_upgrade —
  a replaced binary is a new inode, so the capability does not survive an upgrade by itself.
- Debian: the same setcap in the postinst `configure` branch.
- RPM: `%caps(cap_sys_nice=ep)` on the binary in `%files`, which is the rpm-native form — rpm then
  applies it on install, restores it on upgrade, and verifies it. A `%post setcap` does none of
  those.
- NixOS: `security.wrappers`, because a store path is read-only and shared and cannot be setcap'd.
  The unit's ExecStart moves to `config.security.wrapperDir` — without that the wrapper exists and
  the service still runs the uncapped store path, which is the whole failure this fixes.
- Steam Deck: setcap in the installer's sudo block. That box needs it most (one small Van Gogh GPU
  shared between the game and the encode). The binary lives under $HOME, so unlike the /etc
  drop-ins it survives a SteamOS A/B update on its own and needs no atomic-keep entry — but it
  does need re-applying after each rebuild, which re-running the installer does.
- Bazzite sysext: at IMAGE BUILD time, before mksquashfs. It cannot be done in the merge hook (a
  merged sysext's /usr is read-only squashfs) and it cannot ride in from the RPM either — rpm keeps
  capabilities in its own header and `rpm2cpio | cpio` carries only the payload, so the staged file
  arrives with none. mksquashfs does record security.capability (only security.selinux is
  excluded), so a setcap on the staging tree is what lands in the image. Needs root/CAP_SETFCAP;
  a plain-user CI build warns and ships without it rather than failing a release over a
  performance lever.

Every one of them is best-effort and cannot fail an install: a box without libcap, or a filesystem
that cannot store capabilities, simply runs at default priority exactly as it does today.

Documented in the same PR — the configuration row now says the packages grant it, and
running-as-a-service gets a section explaining what it is, how to check it (`getcap`), and how to
remove it (`setcap -r`, or just `PYROWAVE_QUEUE_PRIORITY=off`), including the two debugging side
effects.

Verified: the Arch scriptlet grants the capability from a fake package root exactly as pacman
would invoke it, and the resulting binary reaches REALTIME end to end on the RTX 5070 Ti; the RPM
spec's %caps line parses under rpmspec in a Fedora 41 container; the NixOS module parses under
nix-instantiate; all five edited shell scripts pass `bash -n`. No Rust file changed in this
commit, so the CI-parity Rust gates from the companion commit still stand.
2026-08-08 15:24:54 +02:00
..