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) § 用户服务管理器管理的单元.
基礎設置
所有的用戶單元都位於 ~/.config/systemd/user
路徑下。 如果你想在首次用戶登陸時運行單元,對想要自動啟動的單元執行 systemctl --user enable unit
即可。
systemctl --global enable service
命令。環境變量
systemd 用戶實例不會繼承類似 .bashrc
中定義的 環境變量。systemd 用戶實例有三種設置環境變量的方式:
- 對於有
$HOME
目錄的用戶,可以在~/.config/environment.d/
目錄中新建一個".conf"文件,然後在其中寫入格式為NAME=VAL
這樣的行。這些設置只對指定用戶的用戶單元有效 。更多信息可以參考 environment.d(5) 。 - 使用
/etc/systemd/user.conf
文件中的DefaultEnvironment
選項。這個配置在所有的用戶單元中可見。 - 在
/etc/systemd/system/user@.service.d/
下增加配置文件設置。 這個配置在所有的用戶單元中可見。 - 在任何時候, 使用
systemctl --user set-environment
或systemctl --user import-environment
. 對設置環境變量之後啟動的所有用戶單元有效,但已經啟動的用戶單元不會生效。 - 使用由 dbus提供的
dbus-update-activation-environment --systemd --all
命令。和systemctl --user import-environment
有同樣的效果,但是會影響D-Bus會話。你可以把這個添加到shell初始化文件的末尾。 - 對於用戶環境的「全局」環境變量,可以使用會被某些生成器解析的
environment.d
目錄。 更多信息可以參考environment.d(5)和 systemd.generator(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
開發用戶單元
通用的 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
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 的方法:
- 可以在無特權模式下運行,設備管理由 logind 代為管理(參考 Hans de Goede 的這個提交).
- 可以實現通過 socket 激活服務 (參考這個提交). 這點使得我們不再需要依賴於 systemd-xorg-launch-helper-gitAUR[損壞的連結:package not found].
但非常不幸,xorg 的無特權模式需要在用戶會話裡面運行。所以,xorg 的用戶服務只能在 root 權限下運行(和 1.16 版本之前一樣),而不能使用 1.16 版本提供的無特權模式。
GetSessionByPID
來獲取這個信息(使用 xorg 自身的 pid 作為參數)。參見這個話題 和 xorg 源碼. 看上去如果 xorg 通過其依附的 tty 來獲取會話信息的話,這個問題將得到解決。下面是從用戶服務運行 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
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
。
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
[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.