systemd (简体中文)/User (简体中文)

From ArchWiki
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

翻译状态:本文是 Systemd/User翻译。上次翻译日期:2015-09-25。如果英文版本有所更改,则您可以帮助同步翻译。

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 --usersystemd --system 运行于不同的进程里面,所以用户单元不能引用或依赖于系统单元。

基础设置

所有的用户服务都位于 ~/.config/systemd/user 路径下。 如果你想在首次用户登陆时运行服务,对想要自动启动的服务执行 systemctl --user enable service 即可。

提示: 如果不是只让发出"systemctl"命令的用户启用某个service单元,而是想要让所有用户都生效,请以root权限执行systemctl --global enable service 命令。

D-Bus

有些程序会使用到 D-Bus 用户消息总线。 传统的方式是由桌面环境调用 dbus-launch 来启动dbus。但从systemd 226版本开始,将由 systemd 管理用户消息总线。[3] systemd会为每个用户启动一个dbus-daemon,提供dbus.socketdbus.service用户单元,供用户下的所有会话使用。

注意: 如果你之前在 /etc/systemd/user/~/.config/systemd/user/ 下创建过这两个单元,现在可以删掉了。

环境变量

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 从 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
警告: systemd 服务是 没有 会话的, 它们在 logind 状态之外运行, 所以不要在 lingering 中启用自动登陆的功能,这会导致 会话中断

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 的方法:

但非常不幸,xorg 的无特权模式需要在用户会话里面运行。所以,xorg 的用户服务只能在 root 权限下运行(和 1.16 版本之前一样),而不能使用 1.16 版本提供的无特权模式。

注意: 这并不是 logind 强加的限制,而是 xorg 需要知道它将要接管的是哪个会话,而现在它通过调用 logind's 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/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]

开发用户单元

例子

下面是 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

Tango-view-refresh-red.pngThis article or section is out of date.Tango-view-refresh-red.png

Reason: References user-session@.service instead of user@.service; the latter does not contain Conflicts=getty@tty1.service. (Discuss in Talk:Systemd (简体中文)/User (简体中文))

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 user-session@.service as described above, but be sure to remove the Conflicts=getty@tty1.service from user-session@.service, 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
注意: 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.

See also