I need help starting services that communicate via a session (not system) D-Bus on a headless Linux system. The key is that no-one will be logged in on the headless system.
So far I've been able to start a D-Bus daemon and test the D-Bus communication on behalf of a user ("otheruser") who is not logged in, in three different terminals:
In the first terminal, I start a D-Bus daemon for the "otheruser":
$ sudo -u otheruser dbus-daemon --session --print-address 1
unix:abstract=/tmp/dbus-a5cU7r4IHc,guid=6c0a9bbfd02f5f68da0fe32f5a5e0a48
In the second terminal I start the D-Bus server application using the above DBUS_SESSION_BUS_ADDRESS response:
$ sudo -u otheruser DBUS_SESSION_BUS_ADDRESS="unix:abstract=/tmp/dbus-a5cU7r4IHc,guid=6c0a9bbfd02f5f68da0fe32f5a5e0a48" /usr/bin/my-dbus-service
Then, in the third terminal, I can test the connection:
$ sudo -u otheruser DBUS_SESSION_BUS_ADDRESS="unix:abstract=/tmp/dbus-a5cU7r4IHc,guid=6c0a9bbfd02f5f68da0fe32f5a5e0a48" gdbus introspect --session --dest com.mycompany.myappname --object-path /com/mycompany/interface
But, I want to start the D-Bus server application as well as a few client D-Bus services via systemd. How do I start a D-Bus session via systemd so that its DBUS_SESSION_BUS_ADDRESS environment variable gets propagated to the D-Bus server and client services for "otheruser"?
One possible solution might be to pipe the output of dbus-daemon into a "somefile," and then set DBUS_SESSION_BUS_ADDRESS=$(cat somefile) before starting the D-Bus server and clients. This just seems a little too awkward to me; especially because I'm aware there is some magic with a "Busname" directive in the systemd service file for system D-Bus connections. How do I properly start systemd services for "otheruser" so that these systemd services can communicate with a session D-Bus interface?
Best Answer
You need several things to make this work.
my-dbus-client.service
files are ofType=dbus
or depend on thedbus.socket
unit to make sure they allocate the dbus session bus socket and start the dbus session service if it hasn't already been started.First, to make Systemd services for a given user start at boot-time without login, you need to enable systemd user lingering - this only needs to be done once as root when configuring to enable it for a user:
Next, if you're on a Debian-based system, for the next two steps, you can simply install the package dbus-user-session package:
If you're using some other distribution, want to do this manually, or just want to understand how it works continue on. Otherwise skip over the creation of
dbus.service
anddbus.socket
.Create the file
/usr/lib/systemd/user/dbus.socket
(note, on some distributions the user directory may be under/lib
instead of/usr/lib
) with the following content:Propagation of
DBUS_SESSION_BUS_ADDRESS
to all services, which was your main concern, is addressed by theExecPostStart
line below - all following services will have that set.%t
gets replaced with theXDG_RUNTIME_DIR
- a transient directory under/run
created by systemd specific to the user session that you can stuff files. If you wish to create this socket somewhere else, there's no reason you can't. Just make sure it's somewhere transient, or it gets cleaned up on reboot/session teardown.I did have some issues trying to make the dbus unix socket an abstract one - systemd didn't seem to like the form
unix:abstract=
or@
prefix for some reason.Now create the file
/usr/lib/systemd/user/dbus.service
with the following content:There is a little bit of magic that goes on here behind the scenes by systemd to pass in the already created unix socket to the dbus-daemon. Systemd uses the information from
dbus.socket
to create the socket, and it's file descriptor gets set in the environment variableLISTEN_FDS
, which is passed into thedbus-daemon
. Special options listed above make dbus-daemon use the file descriptor passed in instead of creating a new one. This allows dbus clients to start parallel to the dbus-daemon starting without worries of the socket not existing.Finally, create your own systemd user services, making sure that you either set the type to
Type=dbus
, setBusName=
to the name of one of the dbus service names that will be registered by this service, or by making sureRequires=dbus.socket
is specified in the Unit section. Here is an example:You can place them in one of several places:
$HOME/.config/systemd/user
/usr/lib/systemd/user
Enable your services with
systemctl --user enable <service name>
and reboot, and everything should work.For
systemctl --user ..
to work, you need to have a full systemd login environment for the /run/user/{uid} to exist. The lightweight environments created by su - .. --login or sudo do not set this up. You need to ssh in, log into a console or, if you run a properly set up systemd distribution, you can grab and usemachinectl shell
to create a full systemd environment in your current shell.References:
man loginctl
for lingerman pam_systemd
for XDG_RUNTIME_DIR infoman systemd.service
for Type=dbus, BusName=, and implicit dependency on dbus.socketman sd_listen_fds
for info about LISTEN_FDS environment variable