systemd (简体中文)/User (简体中文)
systemd 会给每个用户生成一个 systemd 实例,用户可以在这个实例下管理服务,启动、停止、启用以及禁用他们自己的单元。这个能力大大方便了那些通常在特定用户下运行的守护进程和服务,比如 mpd, 还有像拉取邮件等需要自动执行的任务。在某些场景下,它甚至可以在指定用户下运行 xorg 以及整个窗口管理系统。
工作原理
从 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 206 版本开始,
systemd --user
实例是针对每个用户处理的,而不是针对会话。这样做的原理是用户服务处理的大部分资源,像 socket 或状态文件是针对每个用户的(存活于用户的主目录下)而不是会话。这意味着所有的用户服务是独立于会话之外运行的。最终,我们得出结论:基于会话运行的程序可能会导致用户服务中断。systemd 处理用户会话的方式是非常生硬的(pretty much in flux)。 单会话支持的进展参考 [1] 和 [2] 。 -
systemd --user
和systemd --system
运行于不同的进程里面,所以用户单元不能引用或依赖于系统单元。
基础设置
所有的用户服务都位于 ~/.config/systemd/user
路径下。 如果你想在首次用户登陆时运行服务,对想要自动启动的服务执行 systemctl --user enable service
即可。
systemctl --global enable service
命令。D-Bus
有些程序会使用到 D-Bus 用户消息总线。 传统的方式是由桌面环境调用 dbus-launch
来启动dbus。但从systemd 226版本开始,将由 systemd 管理用户消息总线。[3] systemd会为每个用户启动一个dbus-daemon,提供dbus.socket
和 dbus.service
用户单元,供用户下的所有会话使用。
/etc/systemd/user/
或 ~/.config/systemd/user/
下创建过这两个单元,现在可以删掉了。环境变量
systemd 用户实例不会继承类似 .bashrc
中定义的 环境变量。systemd 用户实例有三种设置环境变量的方式:
- 对于有
$HOME
目录的用户,可以在~/.config/environment.d/
目录中新建一个".conf"文件,然后在其中写入格式为NAME=VAL
这样的行。这些设置只对指定用户的用户单元有效 。更多信息可以参考 environment.d(5) 。 - 在
/etc/systemd/user.conf
文件中使用DefaultEnvironment
选项。这个配置在所有的用户单元中可见。 - 在
/etc/systemd/system/[email protected]/
下增加配置文件设置。 这个配置在所有的用户单元中可见。 - 在任何时候, 使用
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/[email protected]/
然后在里面新建一个 .conf
文件 (例如 local.conf
):
/etc/systemd/system/[email protected]/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 从 219 版本开始提供了一个脚本 /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
随系统自动启动 systemd 用户实例
systemd 用户实例在用户首次登陆时启动,并在最后一个会话退出时终止。 但有时候,对于一些不依赖于会话的用户进程,在系统启动时加载用户实例,在会话全部结束时,也不停止用户实例是比较有用的。Lingering 就是用来实现这个的。 使用以下命令来启用驻留指定用户:
# loginctl enable-linger username
Xorg 和 systemd
使用 systemd 单元来运行 Xorg 有好几种方法,下面介绍其中两种,一种是启动一个新的用户会话,在里面运行 Xorg 服务,另外一种是用 systemd 用户服务启动 Xorg。
Automatic login into Xorg without display manager
这种方法通过一个系统单元将用户会话带起来,并在用户会话里面启动一个 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 服务。 但另一方面,这个方案存在某些倒退,这在下面会提到。
从 1.16 版本开始,xorg-server 提供了两种更好的整合到 systemd 的方法:
- 可以在无特权模式下运行,设备管理由 logind 代为管理(参考 Hans de Goede 的这个提交).
- 可以实现通过 socket 激活服务 (参考这个提交). 这点使得我们不再需要依赖于 systemd-xorg-launch-helper-gitAUR.
但非常不幸,xorg 的无特权模式需要在用户会话里面运行。所以,xorg 的用户服务只能在 root 权限下运行(和 1.16 版本之前一样),而不能使用 1.16 版本提供的无特权模式。
GetSessionByPID
来获取这个信息(使用 xorg 自身的 pid 作为参数)。参见这个话题 和 xorg 源码. 看上去如果 xorg 通过其依附的 tty 来获取会话信息的话,这个问题将得到解决。下面是从用户服务运行 xorg 的步骤:
1. 通过编辑/etc/X11/Xwrapper.config
文件,允许所有用户使用root权限运行xorg:
/etc/X11/Xwrapper.config
allowed_users=anybody needs_root_rights=yes
2. 把下面 systemd 单元加到 ~/.config/systemd/user
目录下:
~/.config/systemd/user/[email protected]
[Unit] Description=Socket for xorg at display %i [Socket] ListenStream=/tmp/.X11-unix/X%i
~/.config/systemd/user/[email protected]
[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 [email protected] knows which vt use $ systemctl --user start [email protected] # Start listening on the socket for display 0
现在,在 tty 2上运行任意的X应用,xorg 都会自动启动。
可以在 .bash_profile
里面把环境变量 XDG_VTNR
设置到 systemd 环境里面。在这之后,你可以使用 systemd 单元启动任意的X应用,包括窗口管理器。当然,这些 systemd 单元必须依赖于 [email protected]
。
开发用户单元
例子
下面是 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。
X 应用程序须知
大多数X 应用运行都需要 DISPLAY
变量。如何让所有systemd用户实例看到这个环境变量,参考 #DISPLAY 和 XAUTHORITY。
Some use cases
Persistent terminal multiplexer
You may wish your user session to default to running a terminal multiplexer, such as GNU Screen or Tmux, in the background rather than logging you into a window manager session. 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 with myStuff.target).
To create this type of user session, procede as above, but instead of creating wm.target, create 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
cruft.target
, like mystuff.target
above, should start anything you think should run before tmux or screen 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.
[Unit] Description=tmux: A terminal multiplixer 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
Once this is done, systemctl --user enable
tmux.service
, multiplexer.target
and any services you created to be run by cruft.target
and you should be set to go! Activated [email protected]
as described above, but be sure to remove the [email protected]
from [email protected]
, since your user session will not be taking over a TTY. Congratulations! You have a running terminal multiplexer and some other useful programs ready to start at boot!
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.