PipeWire: Play sound effects across users
Context🔗
I am running some command-line applications whilst sudoed in as another user, on my laptop (Linux desktop).
Those command-line applications want to play sound effects, but without some workaround they won't be able to cross the user boundary.
My System🔗
I'm running NixOS (although that shouldn't matter for the principle) with PipeWire as an audio server.
The command-line application is using the pw-play command to play sound effects. This command is included with PipeWire.
Example flow🔗
desktopuser$ sudo -iu cliuser
# (within a script)
cliuser$ pw-play --volume 0.5 /home/cliuser/.sfx/abc.ogg
Note: the pw-play command is being invoked with an absolute path and desktopuser has read access to the file.
If these two caveats don't apply to your situation, you will likely need extra workarounds. I suspect you could pipe the audio into pw-play if desired.
What I did🔗
Add a pw-play-wrapped script🔗
The goal of this script is to set XDG_RUNTIME_DIR before running pw-play. If XDG_RUNTIME_DIR isn't set then pw-play won't know how to contact PipeWire and we won't be able to play any sound into the user's PipeWire session.
$UID will expand to the numeric user ID of desktopuser.
#!/usr/bin/env bash
set -euo pipefail
export XDG_RUNTIME_DIR="/run/user/$UID"
exec pw-play "$@"
On traditional distributions, you probably want to put this in /usr/local/bin. Otherwise on NixOS, see the end for a snippet.
Note: in adversarial conditions, it might be worth sanitising the passed arguments more, in case pw-play has any unsafe parameters that you may not want to expose.
Add a nopasswd sudo rule🔗
Here cliuser is the target user that wants to play sound effects, desktopuser is the user that's running your desktop and is invoking sudo.
You will need to tweak the path to wherever you put the pw-play-wrapped script.
On a traditional desktop distribution, you will want to use visudo to add this rule safely, perhaps setting EDITOR=nano (or your preferred text editor).
This rule allows cliuser to execute (as desktopuser and without specifying a password) our pw-play-wrapped command (with any arguments).
cliuser ALL=(desktopuser) NOPASSWD: /usr/local/bin/pw-play-wrapped
(See later for a NixOS snippet)
Replace pw-play on the applicable user🔗
I wrote a pw-play shell script to install into ~/.local/bin (on PATH) so that, for the user I'm sudoing in as, it replaces the command included with PipeWire.
This pw-play replacement script invokes pw-play-wrapped on the desired user through the sudo rule we just added.
#!/usr/bin/env bash
set -euo pipefail
# On a non-NixOS system, just use:
# PWPLAY="/usr/local/bin/pw-play-wrapped"
# On a NixOS system, easiest thing to do is find out which command has been given to use as a sudo-able command
PWPLAY=$(sudo -l | grep "$SUDO_USER" | grep -E --only-matching '/nix/store/.*/pw-play-wrapped' | head -n 1)
exec sudo -u "$SUDO_USER" $PWPLAY "$@"
NixOS Snippet🔗
{
pkgs,
...
}:
let
pw-play-wrapped = pkgs.writeShellScriptBin "pw-play-wrapped" ''
set -euo pipefail
export XDG_RUNTIME_DIR="/run/user/$UID"
exec ${pkgs.pipewire}/bin/pw-play "$@"
'';
in
{
security.sudo.extraRules = [
{
users = ["cliuser"];
runAs = "desktopuser";
commands = [
{ command = "${pw-play-wrapped}/bin/pw-play-wrapped"; options = [ "NOPASSWD" ]; }
];
}
];
}
(Then either install pw-play on cliuser manually, like I did, or wrap it into a package and install it 'The NixOS Way'.)
