在无头系统上启动共享会话 D-Bus 的 systemd 服务

在无头系统上启动共享会话 D-Bus 的 systemd 服务

我需要帮助在无头 Linux 系统上启动通过会话(而非系统)D-Bus 进行通信的服务。关键是没有人会登录无头系统。

到目前为止,我已经能够启动 D-Bus 守护进程并在三个不同的终端上代表未登录的用户(“其他用户”)测试 D-Bus 通信:

在第一个终端中,我为“其他用户”启动一个 D-Bus 守护进程:

$ sudo -u otheruser dbus-daemon --session --print-address 1
unix:abstract=/tmp/dbus-a5cU7r4IHc,guid=6c0a9bbfd02f5f68da0fe32f5a5e0a48

在第二个终端中,我使用上述 DBUS_SESSION_BUS_ADDRESS 响应启动 D-Bus 服务器应用程序:

$ sudo -u otheruser DBUS_SESSION_BUS_ADDRESS="unix:abstract=/tmp/dbus-a5cU7r4IHc,guid=6c0a9bbfd02f5f68da0fe32f5a5e0a48" /usr/bin/my-dbus-service

然后,在第三个终端,我可以测试连接:

$ 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

但是,我想通过 systemd 启动 D-Bus 服务器应用程序以及一些客户端 D-Bus 服务。如何通过 systemd 启动 D-Bus 会话,以便其 DBUS_SESSION_BUS_ADDRESS 环境变量传播到“其他用户”的 D-Bus 服务器和客户端服务?

一种可能的解决方案是将 dbus-daemon 的输出导入“somefile”,然后在启动 D-Bus 服务器和客户端之前设置 DBUS_SESSION_BUS_ADDRESS=$(cat somefile)。这对我来说似乎有点太尴尬了;尤其是因为我知道 systemd 服务文件中的“Busname”指令有一些神奇之处系统D-Bus 连接。如何为“其他用户”正确启动 systemd 服务,以便这些 systemd 服务可以与会话 D-Bus 接口进行通信?

答案1

你需要几样东西才能让它工作。

  1. 允许用户服务在启动时运行,无需用户登录(systemd linger)。
  2. systemd 套接字文件,用于指定 systemd 分配的 D-Bus 套接字。
  3. 用于启动 D-Bus 会话总线的 systemd 服务,然后为其他 systemd 服务设置 DBUS_SESSION_BUS_ADDRESS 环境变量。
  4. 确保您的 systemdmy-dbus-client.service文件属于Type=dbus或依赖于该dbus.socket单元,以确保它们分配 dbus 会话总线套接字并启动 dbus 会话服务(如果尚未启动)。

首先,为了使特定用户的 Systemd 服务在启动时无需登录即可启动,您需要启用 systemd 用户逗留 - 只需在配置为用户启用它时以 root 身份执行一次即可:

# loginctl enable-linger otheruser

接下来,如果您使用的是基于 Debian 的系统,对于接下来的两个步骤,您只需安装 dbus-user-session 包:

# apt-get install dbus-user-session

dbus.service如果您正在使用其他发行版,想要手动执行此操作,或者只是想了解其工作原理,请继续。否则,请跳过和的创建dbus.socket

创建文件/usr/lib/systemd/user/dbus.socket(注意,在某些发行版中,用户目录可能位于/lib而不是/usr/lib),内容如下:

[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

DBUS_SESSION_BUS_ADDRESS您主要关心的是传播到所有服务,下面这一ExecPostStart行解决了这个问题 - 所有后续服务都会有该设置。

%t被替换为XDG_RUNTIME_DIR- 由 systemd 创建的临时目录,该目录/run特定于用户会话,您可以在其中填充文件。如果您希望在其他地方创建此套接字,没有理由不能。只需确保它是临时的,否则它会在重新启动/会话关闭时被清除。

我在尝试将 dbus unix 套接字变为抽象套接字时确实遇到了一些问题 ——由于某种原因,systemd 似乎不喜欢这种形式unix:abstract=或前缀。@

/usr/lib/systemd/user/dbus.service现在创建具有以下内容的文件:

[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

systemd 在后台执行了一些小技巧,将已创建的 unix 套接字传递给 dbus-daemon。Systemd 使用来自的信息来dbus.socket创建套接字,并将其文件描述符设置在环境变量中LISTEN_FDS,该变量被传递给dbus-daemon。上面列出的特殊选项使 dbus-daemon 使用传入的文件描述符,而不是创建新的文件描述符。这允许 dbus 客户端与 dbus-daemon 并行启动,而不必担心套接字不存在。

最后,创建您自己的 systemd 用户服务,确保将类型设置为Type=dbus,设置BusName=为此服务将注册的 dbus 服务名称之一的名称,或者确保Requires=dbus.socket在 Unit 部分中指定 。以下是示例:

[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

您可以将它们放置在以下几个位置之一:

  • $HOME/.config/systemd/user
  • /usr/lib/systemd/user

启用您的服务systemctl --user enable <service name>并重新启动,一切都应该正常工作。

为了systemctl --user ..正常工作,您需要有一个完整的 systemd 登录环境,以便 /run/user/{uid} 存在。su - .. --login 或 sudo 创建的轻量级环境不会设置这一点。您需要通过 ssh 登录,登录控制台,或者,如果您运行正确设置的 systemd 发行版,您可以获取并使用它machinectl shell在您当前的 shell 中创建一个完整的 systemd 环境。


参考:

  • man loginctl逗留
  • man pam_systemd有关 XDG_RUNTIME_DIR 的信息
  • man systemd.service对于 Type=dbus、BusName= 以及对 dbus.socket 的隐式依赖
  • man sd_listen_fds有关 LISTEN_FDS 环境变量的信息
  • https://wiki.archlinux.org/index.php/Systemd/User- 有关 systemd 用户会话的一般信息

相关内容