systemd user services and Pulseaudio on Lubuntu 16.04
Introduction
These are my notes as I made Pulseaudio work on an ARM v7-based Embedded Lubuntu 16.04, which doesn’t support Pulseaudio otherwise.
The goal: On a mini-distribution based upon Lubuntu, for use of others, make Pulseaudio work even though the Lubuntu desktop won’t start it. In fact, it’s supposed to run even without any X server running.
Being an embedded distribution, basically everything (except for certain daemons that drop their privileges) runs as root. Nothing one should try on a desktop computer, but it’s a very common practice on embedded mini-distros. Raspbian excluded.
The problem
There are basically two ways to run pulseaudio: Per-user (actually, per-session) and in system mode, which is discouraged, mostly for security reasons. Given that pulseaudio runs as root on a system where the user is root, I’m not so sure it would have made such a difference. This way or another, I went for user mode.
Which bring me to the actual problem: On a systemd-based OS, pulseaudio expects the XDG_RUNTIME_DIR environment variable to be set to /run/user/UID on its invocation, where UID is the number of the user which shall have access to Pulseaudio. Not only that, this directory must exist when pulseaudio is launched.
Some systemd background
The /run/user/ tmpfs-mounted directory is maintained by pam_systemd (see man pam_systemd, and possibly this too), which adds an entry with a UID when the respective user starts its first session (typically by logging in somehow), and removes this directory (and its content) when the last session for this user is finished.
Among other things pam_systemd does when a user creates its first session, is starting a new instance of the system service user@.service, which runs the systemd user manager instance (typically the template service /lib/systemd/system/user@.service). Which in turn calls “/lib/systemd/systemd –user” with the user ID set to the relevant uid by virtue of a “User=” directive in the said service unit file. So this is how we end up with something like:
# systemctl status [ ... ] └─user.slice └─user-0.slice ├─user@0.service │ └─init.scope │ ├─2227 /lib/systemd/systemd --user │ └─2232 (sd-pam) ├─session-c1.scope │ ├─2160 /bin/login -f │ ├─2238 -bash [ ... ]
An important aspect of the systemd –user call is that User Services are executed as required: The new user-systemd reads unit files (i.e. the *.wants directories) from ~/.config/systemd/user/, /etc/systemd/user/ and /usr/lib/systemd/user/.
This feature allows certain services to run as long as a specific user has at least one session open, running with this user’s UID. Quite helpful for the Pulseaudio service.
From a practical point of view, the difference from regular systemd services is that the service files are put in the systemd/user directory rather than systemd/system. The calls to systemctl also need to indicate that we’re dealing with user services. The –user flag makes systemctl relate to user services that are enabled or disabled for each user separately, while the –global flag relates to user services that are enabled or disabled for all users. This is reflected in the following:
# systemctl --user enable tryuser
Created symlink from /root/.config/systemd/user/default.target.wants/tryuser.service to /etc/systemd/user/tryuser.service.
which is just for the user calling systemctl, versus
# systemctl --global enable tryuser
Created symlink /etc/systemd/user/default.target.wants/tryuser.service, pointing to /etc/systemd/user/tryuser.service.
which enables a user service for each user that has a session in the system. Note that in both cases, the original service unit file was in the same directory, /etc/systemd/user/.
Another little remark: Since the launch of a user service depends on a very specific event (at least one user having a session), the WantedBy directive is typically set ot default.target. No need to fuss.
And almost needless to say, there might be several user services running in parallel, one for each user having an active session. Note however that neither “sudo” or “su” generate a new session, as they merely start a process (with a shell) with another user (root).
A pulseaudio service
This solution isn’t perfect, but will probably work well for most people. That is, those who simply log in as themselves (or use auto-login).
Add this as/etc/systemd/user/my_pulseaudio.service:
[Unit] Description=User Pulseaudio service After=dbus.service Requires=dbus.service [Service] Environment="XDG_RUNTIME_DIR=/run/user/%U" ExecStart=/usr/bin/pulseaudio Type=simple [Install] WantedBy=default.target
Note that the service depends and runs after dbus.service, since Pulseaudio attempts to connect to DBus, among others. Probably not very important, as DBus is likely to already run when a user session starts. Besides, in my specific case, the connection with DBus failed, and yet Pulseaudio worked fine. From /var/log/syslog:
Nov 30 16:28:18 localhost pulseaudio[2093]: W: [pulseaudio] main.c: Unable to contact D-Bus: org.freedesktop.DBus.Error.NotSupported: Unable to autolaunch a dbu s-daemon without a $DISPLAY for X11
Also pay attention to the %U substitution into the numeric UID, while setting the environment.
To enable this service for all users,
# systemctl --global enable my_pulseaudio Created symlink /etc/systemd/user/default.target.wants/my_pulseaudio.service, pointing to /etc/systemd/user/my_pulseaudio.service.
The multi-user problem
In a way, it’s quite interesting that the standard method for using Pulseaudio gives access to a single user. In particular, as Pulseaudio was originally developed to allow simultaneous access of the sound hardware by several pieces of software. Even so, it ended up tuned to the single user scenario: One single user sitting in front the computer. If there are processes belonging to other users, they are bogus users, such as “apache” or “dovecot”, which are intended for controlling privileges.
In recent distributions, it seems like pulseaudio is started by the X server, with the user owning it (i.e. the one who has logged in, typically through the GUI login interface). Once again, access to the sound card is given to one single user, who is supposedly sitting in front of a computer. This is the model Microsoft grew its operating systems with.
There’s always the system mode alternative or allowing TCP connections, but these are not mainstream. In theory, there should have been some privilege access mechanism for Pulseaudio, allowing an arbitrary group of clients to connect as required. But since the by far most common usage scenario of sound is part of a GUI experience for the user in front of the machine, Pulseaudio was shaped accordingly.
The main problem of the solution above is that it doesn’t work in a multi-user scenario: The first user creating a session will have audio access. If another user creates a session, the attempt to launch pulseaudio will fail, leaving this user without sound.
To demonstrate how absurd it can get, suppose that user X connects to the system through ssh (and hence generates a session). This user will have pulseaudio running on its behalf, even though it can’t play any sound on the system. Then user Y logs in on the GUI console, but its pulseaudio service fails to launch, because there’s already one running. To make things even more absurd, if user X logs out, its pulseaudio service is killed, and a service for user Y doesn’t start, because what would kick it off?
It doesn’t help checking if the session that launched my_pulseaudio has a console (man loginctl for example), because user X might log in with ssh first (my_pulseaudio launched, but doesn’t activate a pulseaudio process) and then through the console (my_pulseaudio won’t be launched).
In reality, none of these problems are expected to happen often. We all end up logging into a computer with one specific user.
A less preferred alternative: Waiting for /run/user/0
As I tried to find a proper solution, I came up with the idea to launch the service when /run/user/o is created. It’s a working solution in my specific case, where only root is expected to log in.
The idea is simple. Make a path-based launch of the service when /run/user/0 is created.
/etc/systemd/system/my_pulseaudio.path is then:
[Unit] Description=Detection of session for user 0 before launching Pulseaudio [Path] PathExists=/run/user/0 [Install] WantedBy=paths.target
And the following is /etc/systemd/system/my_pulseaudio.service:
[Unit] Description=Pulseaudio for user 0 After=dbus.service Requires=dbus.service [Service] Environment="XDG_RUNTIME_DIR=/run/user/0" ExecStart=/usr/bin/pulseaudio Type=simple
and then enable the service with
# systemctl enable my_pulseaudio.path Created symlink from /etc/systemd/system/paths.target.wants/my_pulseaudio.path to /etc/systemd/system/my_pulseaudio.path.
This works effectively like the user service for pulseaudio suggested above, but only for user 0. It might be modified to watch for changes in /run/user, and launch a script which evaluates which actions to take (possibly killing one pulseaudio daemon in favor of another?). But it will still not solve the case where a user logs in with ssh first, and then through the console. So the perfect solution should probably hook on session starts and terminations. However that is done.