systemd/用户

出自 Arch Linux 中文维基

systemd 會給每個用戶生成一個 systemd 實例,用戶可以在這個實例下管理服務,啟動、停止、啟用以及禁用他們自己的單元。這個能力大大方便了那些通常在特定用戶下運行的守護進程和服務,比如 Music Player Daemon, 還有像拉取郵件等需要自動執行的任務。

工作原理[編輯 | 編輯原始碼]

從 systemd 226 版本開始,/etc/pam.d/system-login 默認配置中的 pam_systemd 模塊會在用戶首次登錄的時候, 自動運行一個 systemd --user 實例。 只要用戶還有會話存在,這個進程就不會退出;用戶所有會話退出時,進程將會被銷毀。當#隨系統自動啟動 systemd 用戶實例啟用時, 這個用戶實例將在系統啟動時加載,並且不會被銷毀。systemd 用戶實例負責管理用戶服務,用戶服務可以使用systemd提供的各種便捷機制來運行守護進程或自動化任務,如 socket 激活、定時器、依賴體系以及通過 cgroup 限制進程等。

和系統單元類似,用戶單元可以在以下目錄找到(按優先級從低到高排序):

  • /usr/lib/systemd/user/ 這裡存放的是各個軟體包安裝的服務。
  • ~/.local/share/systemd/user/ 這裡存放的是HOME目錄中已安裝的軟體包的單元。
  • /etc/systemd/user/ 這裡存放的是由系統管理員維護的系統範圍的用戶服務。
  • ~/.config/systemd/user/ 這裡存放的是用戶自身的服務。

當 systemd 用戶實例啟動時,它會將用戶個人的 default.target 帶起來。其他用戶單元可以通過 systemctl --user 手動管理。參考 systemd.special(7) § 用户服务管理器管理的单元.

注意:
  • systemd --user 實例是針對每個用戶處理的,而不是針對會話。這樣做的原理是用戶服務處理的大部分資源,像 socket 或狀態文件是針對每個用戶的(存活於用戶的主目錄下)而不是會話。這意味著所有的用戶服務是獨立於會話之外運行的。最終,我們得出結論:基於會話運行的程序可能會導致用戶服務中斷。systemd 處理用戶會話的方式是非常生硬的(pretty much in flux)。 單會話支持的進展參考 [1][2]
  • systemd --usersystemd --system 運行於不同的進程裡面,所以用戶單元不能引用或依賴於系統單元或其他用戶的單元。

基礎設置[編輯 | 編輯原始碼]

所有的用戶單元都位於 ~/.config/systemd/user 路徑下。 如果你想在首次用戶登錄時運行單元,對想要自動啟動的單元執行 systemctl --user enable unit 即可。

提示:如果不是只讓發出"systemctl"命令的用戶啟用某個service單元,而是想要讓所有用戶都生效,請以root權限執行systemctl --global enable service 命令。

環境變量[編輯 | 編輯原始碼]

systemd 用戶實例不會繼承類似 .bashrc 中定義的 環境變量。systemd 用戶實例有三種設置環境變量的方式:

  1. 對於有 $HOME 目錄的用戶,可以在 ~/.config/environment.d/ 目錄中新建一個".conf"文件,然後在其中寫入格式為NAME=VAL這樣的行。這些設置只對指定用戶的用戶單元有效 。更多信息可以參考 environment.d(5)
  2. 使用 /etc/systemd/user.conf 文件中的 DefaultEnvironment 選項。這個配置在所有的用戶單元中可見。
  3. /etc/systemd/system/user@.service.d/ 下增加配置文件設置。 這個配置在所有的用戶單元中可見。
  4. 在任何時候, 使用 systemctl --user set-environmentsystemctl --user import-environment. 對設置環境變量之後啟動的所有用戶單元有效,但已經啟動的用戶單元不會生效。
  5. 使用由 dbus提供的 dbus-update-activation-environment --systemd --all 命令。和systemctl --user import-environment有同樣的效果,但是會影響D-Bus會話。你可以把這個添加到shell初始化文件的末尾。
  6. 對於用戶環境的「全局」環境變量,可以使用會被某些生成器解析的environment.d 目錄。 更多信息可以參考environment.d(5)systemd.generator(7)
  7. 您還可以編寫一個systemd.environment-generator(7) 腳本,該腳本可以生成因用戶而異的環境變量,如果您需要分別給每個用戶環境配置變量,這可能是最好的方法( XDG_RUNTIME_DIR, DBUS_SESSION_BUS_ADDRESS等就是這種情況 )。
提示:如果想一次設置多個環境變量,可以寫一個配置文件,文件裡面每一行定義一個環境變量,用 "key=value" 的鍵值對表示,然後在你的啟動腳本裡添加xargs systemctl --user set-environment < /path/to/file.conf

一般情況下,你需要設置 PATH 這個環境變量。 配置完成後,可以使用命令 systemctl --user show-environment 來驗證值是否正確。

Service 文件例子[編輯 | 編輯原始碼]

新建 drop-in 目錄 /etc/systemd/system/user@.service.d/ 然後在裡面新建一個 .conf文件 (例如 local.conf):

/etc/systemd/system/user@.service.d/local.conf
[Service]
Environment="PATH=/usr/lib/ccache/bin:/usr/local/bin:/usr/bin:/bin"
Environment="EDITOR=nano -c"
Environment="BROWSER=firefox"
Environment="NO_AT_BRIDGE=1"

DISPLAY 和 XAUTHORITY[編輯 | 編輯原始碼]

任何一個 X 應用程式都需要使用 DISPLAY 來指示使用哪個顯示器,而 XAUTHORITY 則是保存了用戶授權文件 .Xauthority 的路徑,X 應用需要用戶授權文件中的 cookie 信息才能訪問 X Server。如果你想通過 systemd 單元啟動一個 X 應用,必須先設置這兩個環境變量。systemd 提供了一個腳本 /etc/X11/xinit/xinitrc.d/50-systemd-user.sh,在 X 啟動的時候,將這些環境變量導入到 systemd 用戶會話中。所以除非你不是通過正常的途逕啟動X,systemd用戶服務應該已經包含了這兩個變量。

PATH[編輯 | 編輯原始碼]

通過 .bashrc 或者 .bash_profile 設置的環境變量,對 systemd 都是不可見的。 如果你改變了你的 PATH 變量,並且準備在 systemd 單元運行的應用中使用這個環境變量,你必須在 systemd 的環境中設置 PATH。假設你在 .bash_profile 中設置了 PATH,讓 systemd 感知到這個變化的最好方法是在修改 PATH 之後,加入以下行通知 systemd:

~/.bash_profile
systemctl --user import-environment PATH
注意:
  • 不會影響導入 PATH 之前啟用的程序.
  • systemd 只有在處理絕對路徑二進位時才會使用設置的 PATH

pam_env[編輯 | 編輯原始碼]

注意: 這種為用戶設置環境變量的方法已棄用且將被移除。

環境變量可以通過 pam_env.so 模塊來設置。參閱 環境變量#使用 pam_env 獲取配置詳情。

隨系統自動啟動 systemd 用戶實例[編輯 | 編輯原始碼]

systemd 用戶實例在用戶首次登錄時啟動,並在最後一個會話退出時終止。 但有時候,對於一些不依賴於會話的用戶進程,在系統啟動時加載用戶實例,在會話全部結束時,也不停止用戶實例是比較有用的。Lingering 就是用來實現這個的。 使用以下命令來啟用駐留:

$ loginctl enable-linger

為另一個用戶啟用駐留:

# loginctl enable-linger username
警告: systemd 服務是 沒有 會話的, 它們在 logind 狀態之外運行, 所以不要在 lingering 中啟用自動登錄的功能,這會導致 會話中斷

開發用戶單元[編輯 | 編輯原始碼]

通用的 systemd unit 文件編寫請參考 systemd#編寫單元文件

例子[編輯 | 編輯原始碼]

下面是 mpd 服務用戶版本的例子:

~/.config/systemd/user/mpd.service
[Unit]
Description=Music Player Daemon

[Service]
ExecStart=/usr/bin/mpd --no-daemon

[Install]
WantedBy=default.target

使用變量的例子[編輯 | 編輯原始碼]

下面是用於 foldingathomeAUR 的用戶服務,將主目錄作為變量傳入使得 Folding@home 能找到指定文件:

~/.config/systemd/user/foldingathome-user.service
[Unit]
Description=Folding@home distributed computing client
After=network.target

[Service]
Type=simple
WorkingDirectory=%h/.config/fah
ExecStart=/usr/bin/FAHClient
CPUSchedulingPolicy=idle
IOSchedulingClass=3

[Install]
WantedBy=default.target

systemd.unit(5) 的 SPECIFIERS 章節中,詳細介紹了各種變量, %h 指示符將使用運行該服務的用戶的主目錄替代。更多的變量參考 systemd 的手冊頁。

查看日誌[編輯 | 編輯原始碼]

查看用戶單元的日誌可以用類似命令:

$ journalctl --user

查看指定單元的日誌,可以使用以下命令:

$ journalctl --user-unit myunit.service

也可以這樣:

$ journalctl --user -u myunit.service
注意: UID 小於 1000 的用戶的日誌不會被 journald 記錄,需要的話可以 重定向 所有內容至系統日誌。

臨時文件[編輯 | 編輯原始碼]

systemd-tmpfiles 可以像管理系統臨時文件那樣管理用戶自定義的臨時文件和目錄(參閱 systemd#systemd-tmpfiles - 臨時文件)。用戶配置文件以 ~/.config/user-tmpfiles.d/~/.local/share/user-tmpfiles.d/ 的順序讀取。為了正常使用本功能,需要為用戶啟用必要的 systemd 用戶單元:

$ systemctl --user enable systemd-tmpfiles-setup.service systemd-tmpfiles-clean.timer

配置文件的語法格式和系統級的配置是一樣的,詳情請參閱 systemd-tmpfiles(8)tmpfiles.d(5) 手冊頁。

Xorg 和 systemd[編輯 | 編輯原始碼]

這篇文章的某些內容需要擴充。

原因: Cover graphical-session.target: systemd.special(7) § Special Passive User Units, [3]. (在 Talk:Systemd/用戶 中討論)

使用 systemd 單元來運行 Xorg 有好幾種方法,下面介紹其中兩種,一種是啟動一個新的用戶會話,在裡面運行 Xorg 服務,另外一種是用 systemd 用戶服務啟動 Xorg。

沒有顯示管理器的情況下自動登錄到 Xorg[編輯 | 編輯原始碼]

本文或本章節的事實準確性存在爭議。

原因: 這樣設置會導致用戶有2條 D-Bus 總線,一條用於桌面,另一條用於 systemd。為什麼不共用 systemd 的那一條?(在 Talk:Systemd/用戶 中討論)


這種方法通過一個系統單元將用戶會話帶起來,並在用戶會話裡面啟動一個 xorg 服務,並運行 ~/.xinitrc 將窗口管理器等啟動起來。你需要安裝好 xlogin-gitAUR,並按照 Xinit#xinitrc 來設置好你的 xinitrc。

會話會使用它自己的 dbus 守護,而需要用到 dbus.service 的 systemd 工具會自動連接到會話的 dbus 實例上。最後 啟用 xlogin@username 服務來達成開機自動登錄。 整個用戶會話都在 systemd 的作用域下運行,會話內的一切都能正常工作。

將 Xorg 作為 systemd 用戶服務[編輯 | 編輯原始碼]

另外一種選擇是將 xorg 作為一個 systemd 用戶服務。這是一種不錯的方案,因為其他的 X-related units 可以依賴於 xorg 服務。 但另一方面,這個方案存在某些倒退,這在下面會提到。

xorg-server 提供了兩種整合到 systemd 的方法:

  • 可以在無特權模式下運行,設備管理由 logind 代為管理(參考 Hans de Goede 的這個提交)。
  • 可以實現通過 socket 激活服務 (參考這個提交)。

但非常不幸,xorg 的無特權模式需要在用戶會話裡面運行。所以,xorg 的用戶服務只能在 root 權限下運行(和 1.16 版本之前一樣),而不能使用 1.16 版本提供的無特權模式。

注意: 這並不是 logind 強加的限制,而是 xorg 需要知道它將要接管的是哪個會話,而現在它通過調用 logind's GetSessionByPID 來獲取這個信息(使用 xorg 自身的 pid 作為參數)。參見這個話題xorg 源碼. 看上去如果 xorg 通過其依附的 tty 來獲取會話信息的話,這個問題將得到解決。
警告: 在 xorg 1.18 上 socket 激活存在問題,客戶端會觸發激活死鎖。具體查閱上游報告 [4]。臨時策略是啟動一個不帶 socket 激活的 xorg 伺服器,並在伺服器啟動一段時間後客戶端再連接,確保連接時伺服器已經啟動。目前來看沒有一個可靠機制來判斷 X 伺服器是否啟動完畢。

下面是從用戶服務運行 xorg 的步驟:

1. 通過編輯 /etc/X11/Xwrapper.config 文件,允許所有用戶使用 root 權限運行 xorg。這是在 Xorg#Xorg as Root 的基礎上,無需用戶在實際控制台上登錄,而是將 allowed_user 的默認 console 控制台定義成了 anybody。參見 Xorg.wrap(1)

/etc/X11/Xwrapper.config
allowed_users=anybody
needs_root_rights=yes

2. 把下面 systemd 單元加到 ~/.config/systemd/user 目錄下:

~/.config/systemd/user/xorg@.socket
[Unit]
Description=Socket for xorg at display %i

[Socket]
ListenStream=/tmp/.X11-unix/X%i
~/.config/systemd/user/xorg@.service
[Unit]
Description=Xorg server at display %i

Requires=xorg@%i.socket
After=xorg@%i.socket

[Service]
Type=simple
SuccessExitStatus=0 1

ExecStart=/usr/bin/Xorg :%i -nolisten tcp -noreset -verbose 2 "vt${XDG_VTNR}"

這裡 ${XDG_VTNR} 表示 xorg 將要運行的虛擬終端,可以在服務單元文件裡面硬編碼,也可像下面那樣在環境變量裡指定:

$ systemctl --user set-environment XDG_VTNR=1
注意: xorg應該在用戶登錄的虛擬終端上運行,否則 logind 會認為會話沒有激活。

3. 確保 DISPLAY 環境變量已經配置,參考 這裡.

4. 接下來,執行以下命令,使得 xorg 在 display 0 和 tty 2 上可以通過 socket 激活:

$ systemctl --user set-environment XDG_VTNR=2     # 这样 xorg@.service 知道该用哪个虚拟终端
$ systemctl --user start xorg@0.socket            # 开始为 display 0 监听 socket

現在,在 tty 2上運行任意的X應用,xorg 都會自動啟動。

可以在 .bash_profile 裡面把環境變量 XDG_VTNR 設置到 systemd 環境裡面。在這之後,你可以使用 systemd 單元啟動任意的X應用,包括窗口管理器。當然,這些 systemd 單元必須依賴於 xorg@0.socket

警告: 當前,通過用戶服務啟動窗口管理器意味著它是在會話之外運行的,這將帶來以下問題: break the session. 但是,systemd 的開發者看上去更傾向於這樣(?)。參見 [5][6]

一些用例[編輯 | 編輯原始碼]

窗口管理器[編輯 | 編輯原始碼]

以一個 systemd 服務來運行窗口管理器需要先 #將 Xorg 作為 systemd 用戶服務 來運行。下面我們以 awesome 舉例:

~/.config/systemd/user/awesome.service
[Unit]
Description=Awesome window manager
After=xorg.target
Requires=xorg.target

[Service]
ExecStart=/usr/bin/awesome
Restart=always
RestartSec=10
 
[Install]
WantedBy=wm.target
注意: [Install] 一節包括一個 WantedBy 參數,當使用 systemctl --user enable 時會創建一個本服務的軟連結到 ~/.config/systemd/user/wm.target.wants/window_manager.service 來使它在登錄時啟動。推薦採用啟用服務的方法來代替手動創建軟連結。

終端復用器持久化[編輯 | 編輯原始碼]

比起默認登入一個窗口管理器,你可能更想要自動在後台運行一個終端復用器(比如 screentmux)。

創建 以下文件:

~/.config/systemd/user/multiplexer.target
[Unit]
Description=Terminal multiplexer
Documentation=info:screen man:screen(1) man:tmux(1)
After=cruft.target
Wants=cruft.target

[Install]
Alias=default.target

將用戶登錄與 X 登錄分開,對引導到 TTY 而不是顯示管理器的用戶是有用的,此時你可以將所有要啟動的東西綁定到 mystuff.target

依賴項 cruft.target 與上述 mystuff.target 類似,用來在終端復用器啟動前啟動你需要的服務(或用來啟動那些需要開機啟動但時序要求不高的服務),比如 GnuPG 守護程序。

現在可以為你的終端復用器創建一個服務了。以下是一個例子,以 tmux 為例且加載了一個 gpg-agent 會話,這個 gpg-agent 會話會將信息寫入 /tmp/gpg-agent-info。這個示例會話在將來啟動 X 以後也可以運行 X 程序,因為設置了 $DISPLAY

~/.config/systemd/user/tmux.service
[Unit]
Description=tmux: A terminal multiplexer 
Documentation=man:tmux(1)
After=gpg-agent.service
Wants=gpg-agent.service

[Service]
Type=forking
ExecStart=/usr/bin/tmux start
ExecStop=/usr/bin/tmux kill-server
Environment=DISPLAY=:0
EnvironmentFile=/tmp/gpg-agent-info

[Install]
WantedBy=multiplexer.target

Enable tmux.servicemultiplexer.target 和由 cruft.target 運行的服務,像往常一樣 start user@.service 即可。

在註銷後結束用戶進程[編輯 | 編輯原始碼]

Arch Linux 用 --without-kill-user-processes 參數編譯了 systemd 包,默認下 KillUserProcessesno。這一設置導致了在用戶註銷後,用戶進程不會結束。要改變這一行為,在用戶註銷後結束所有用戶進程,請在 /etc/systemd/logind.conf 中設置 KillUserProcesses=yes

注意,修改這一設置會破壞終端復用器,如 tmuxGNU Screen。修改設置後仍想使用終端復用器,需按下列方法使用 systemd-run

$ systemd-run --scope --user command args

比如要運行 screen 可以這樣:

$ systemd-run --scope --user screen -S foo

只有在用戶在某處登錄過系統至少一次,且 user@.service 仍在運行的情況下,使用 systemd-run 可以保持進程在用戶註銷後繼續運行。

用戶註銷所有會話後,默認 user@.service 也會終止,除非用戶啟用了 "lingering" [7] 。為了讓用戶在註銷後也能運行長期任務,需要為他們啟用 lingering。詳情參閱 #隨系統自動啟動 systemd 用戶實例loginctl(1)

疑難解答[編輯 | 編輯原始碼]

Runtime directory '/run/user/1000' is not owned by UID 1000, as it should[編輯 | 編輯原始碼]

systemd[1867]: pam_systemd(systemd-user:session): Runtime directory '/run/user/1000' is not owned by UID 1000, as it should.
systemd[1867]: Trying to run as user instance, but $XDG_RUNTIME_DIR is not set

如果你看到類似錯誤且無法登錄會話,有可能是另一個系統級(非用戶級)的服務正在創建該目錄。例如正在使用的 docker 容器掛載了 /run/user/1000。可以修改容器取消掛載,或禁用、延遲啟動 docker 服務來修復此問題。

參閱[編輯 | 編輯原始碼]