Starting systemd services sharing a session D-Bus on headless system
You need several things to make this work.
- Enable user services to run at boot time without user login (systemd linger).
- A systemd socket file to specify the D-Bus socket for systemd to allocate.
- A systemd service to launch the D-Bus session bus that launches, then sets the DBUS_SESSION_BUS_ADDRESS env var for other systemd services.
- Ensure your systemd
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:
# loginctl enable-linger otheruser
Next, if you're on a Debian-based system, for the next two steps, you can simply install the package dbus-user-session package:
# apt-get install dbus-user-session
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
and dbus.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:
[Unit]
Description=D-Bus User Message Bus Socket
[Socket]
ListenStream=%t/bus
ExecStartPost=-/bin/systemctl --user set-environment DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus
[Install]
WantedBy=sockets.target
Also=dbus.service
Propagation of DBUS_SESSION_BUS_ADDRESS
to all services, which was your main concern, is addressed by the ExecPostStart
line below - all following services will have that set.
%t
gets replaced with the XDG_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:
[Unit]
Description=D-Bus User Message Bus
Requires=dbus.socket
[Service]
ExecStart=/usr/bin/dbus-daemon --session --address=systemd: --nofork --nopidfile --systemd-activation
ExecReload=/usr/bin/dbus-send --print-reply --session --type=method_call --dest=org.freedesktop.DBus / org.freedesktop.DBus.ReloadConfig
[Install]
Also=dbus.socket
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 variable LISTEN_FDS
, which is passed into the dbus-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
, set BusName=
to the name of one of the dbus service names that will be registered by this service, or by making sure Requires=dbus.socket
is specified in the Unit section.
Here is an example:
[Unit]
Description=Config Server Startup
[Service]
Type=dbus
BusName=com.example.app.configuree
ExecStart=/opt/example/app/configuration_server
Restart=on-failure
[Install]
WantedBy=default.target
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 use machinectl shell
to create a full systemd environment in your current shell.
References:
-
man loginctl
for linger -
man pam_systemd
for XDG_RUNTIME_DIR info -
man systemd.service
for Type=dbus, BusName=, and implicit dependency on dbus.socket -
man sd_listen_fds
for info about LISTEN_FDS environment variable - https://wiki.archlinux.org/index.php/Systemd/User - general information on systemd user sessions