Linux

在無頭系統上啟動共享會話 D-Bus 的 systemd 服務

  • January 16, 2022

我需要幫助在無頭 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)。這對我來說似乎有點太尷尬了。特別是因為我知道系統D-Bus 連接的 systemd 服務文件中的“Busname”指令有一些魔力。如何正確啟動“其他使用者”的 systemd 服務,以便這些 systemd 服務可以與會話 D-Bus 介面通信?

你需要幾件事來完成這項工作。

  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.serviceand的創建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 使用者會話的一般資訊

引用自:https://serverfault.com/questions/892465