出自 Arch Linux 中文维基

本文或本節需要翻譯。要貢獻翻譯,請訪問簡體中文翻譯團隊

附註: Need translate.(在 Talk:Systemd/用戶# 中討論)

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

工作原理

從 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 when resolving non-absolute binaries itself.

隨系統自動啟動 systemd 用戶實例

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

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

開發用戶單元

通用的 unit 文件編寫請參考 systemd#Writing unit files

例子

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

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

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

[Install]
WantedBy=default.target

使用變量的例子

下面是 sickbeard.service 用戶版本的例子, 在配置中,使用了主目錄變量(%h):

~/.config/systemd/user/sickbeard.service
[Unit]
Description=SickBeard Daemon

[Service]
ExecStart=/usr/bin/env python2 /opt/sickbeard/SickBeard.py --config %h/.sickbeard/config.ini --datadir %h/.sickbeard

[Install]
WantedBy=default.target

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

Reading the journal

The journal for the user can be read using the analogous command:

$ journalctl --user

To specify a unit, one can use

$ journalctl --user-unit myunit.service

Or, equivalently:

$ journalctl --user -u myunit.service
注意: journald will not write user journals for users with UIDs below 1000, instead directing everything to the system journal.

Temporary files

systemd-tmpfiles allows users to manage custom volatile and temporary files and directories just like in the system-wide way (see systemd#systemd-tmpfiles - temporary files). User-specific configuration files are read from ~/.config/user-tmpfiles.d/ and ~/.local/share/user-tmpfiles.d/, in that order. For this functionality to be used, it is needed to enable the necessary systemd user units for your user:

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

The syntax of the configuration files is the same than those used system-wide. See the systemd-tmpfiles(8) and tmpfiles.d(5) man pages for details.

Xorg 和 systemd

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

不用顯示管理器自動登錄

這種方法通過一個系統單元將用戶會話帶起來,並在用戶會話裡面啟動一個 xorg 服務,並運行 ~/.xinitrc 將窗口管理器等啟動起來。

你需要配置好 #D-Bus 並安裝 xlogin-gitAUR

配置你的 xinitrc 文件, 讓它 source /etc/X11/xinit/xinitrc.d/ 目錄下的所有文件。~/.xinitrc 在運行的時候不要返回(返回意味著會話結束)。你可以通過在 xinitrc 的最後加上 wait命令,或使用 exec 來運行最後一條命令,最後一條命令應該在整個用戶會話都不會退出(如你的窗口管理器)。

會話會使用它自己的 dbus 守護,而需要用到 dbus.service 的 systemd 工具會自動連接到會話的 dbus 實例上。

最後,在 (root) 用戶下,啟用xlogin服務,使其開機自啟動:

# systemctl enable xlogin@username

整個用戶會話都在 systemd 的作用域下運行,會話內的一切都能正常工作。

Xorg as a systemd user service

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

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

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

注意: 這並不是 logind 強加的限制,而是 xorg 需要知道它將要接管的是哪個會話,而現在它通過調用 logind's GetSessionByPID 來獲取這個信息(使用 xorg 自身的 pid 作為參數)。參見這個話題xorg 源碼. 看上去如果 xorg 通過其依附的 tty 來獲取會話信息的話,這個問題將得到解決。
警告: On xorg 1.18 socket activation seems to be broken. The client triggering the activation deadlocks. See the upstream bug report [3]. As a temporary workaround you can start the xorg server without socket activation, making sure the clients connect after a delay, so the server is fully started. There seems to be no nice mechanism to get a startup notification for the X server.

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

1. 通過編輯/etc/X11/Xwrapper.config文件,允許所有用戶使用root權限運行xorg. This builds on Xorg#Xorg as Root by adding the stipulation that this need not be done from a physical console. That is, allowed_user's default of console is being overwritten with anybody; see 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     # So that xorg@.service knows which vt use
$ systemctl --user start xorg@0.socket            # Start listening on the socket for display 0

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

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

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

X 應用程式須知

大多數X 應用運行都需要 DISPLAY 變量。如何讓所有systemd用戶實例看到這個環境變量,參考 #DISPLAY 和 XAUTHORITY

Some use cases

Window manager

To run a window manager as a systemd service, you first need to run #Xorg as a systemd user service. In the following we will use awesome as an example:

~/.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
注意: The [Install] section includes a WantedBy part. When using systemctl --user enable it will link this as ~/.config/systemd/user/wm.target.wants/window_manager.service, allowing it to be started at login. Is recommended to enable this service, not to link it manually.

Persistent terminal multiplexer

Rather than logging you into a window manager session for your user session by default, you may want to automatically run a terminal multiplexer (such as screen or tmux) in the background.

Create the following:

~/.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

Separating login from X login is most likely only useful for those who boot to a TTY instead of to a display manager (in which case you can simply bundle everything you start in mystuff.target).

The dependency cruft.target, like the mystuff.target above, allows starting anything which should run before the multiplexer starts (or which you want started at boot regardless of timing), such as a GnuPG daemon session.

You then need to create a service for your multiplexer session. Here is a sample service, using tmux as an example and sourcing a gpg-agent session which wrote its information to /tmp/gpg-agent-info. This sample session, when you start X, will also be able to run X programs, since $DISPLAY is set:

~/.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.service, multiplexer.target and any services you created to be run by cruft.target, start user@.service as usual and you should be done.

Kill user processes on logout

Arch Linux builds the systemd package with --without-kill-user-processes, setting KillUserProcesses to no by default. This setting causes user processes not to be killed when the user logs out. To change this behavior in order to have all user processes killed on the user's logout, set KillUserProcesses=yes in /etc/systemd/logind.conf.

Note that changing this setting breaks terminal multiplexers such as tmux and GNU Screen. If you change this setting, you can still use a terminal multiplexer by using systemd-run as follows:

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

For example, to run screen you would do:

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

Using systemd-run will keep the process running after logout only while the user is logged in at least once somewhere else in the system and user@.service is still running.

After the user logs out of all sessions, user@.service will be terminated too, by default, unless the user has "lingering" enabled [6]. To effectively allow users to run long-term tasks even if they are completely logged out, lingering must be enabled for them. See #Automatic start-up of systemd user instances and loginctl(1) for details.

問題解決

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

If you see errors such as this and your login session is broken, it is possible that another system (non-user) service on your system is creating this directory. This can happen for example if you use a docker container that has a bind mount to /run/user/1000. To fix this, you can either fix the container by removing the mount, or disable/delay the docker service.

See also