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

From ArchWiki

Tango-preferences-desktop-locale.png本文或本节需要翻译。要贡献翻译,请访问简体中文翻译团队Tango-preferences-desktop-locale.png

附注: Need translate.(在 Talk:Systemd (简体中文)/User (简体中文)# 中讨论)
翻译状态:本文是 Systemd/User翻译。上次翻译日期:2022-11-09。如果英文版本有所更改,则您可以帮助同步翻译。

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
Note:
  • 不会影响导入 PATH 之前启用的程序.
  • systemd 自己在处理非绝对路径二进制时不会使用设置的 PATH when resolving non-absolute binaries itself.


pam_env

Warning: 这个方式已经过时,很快会被删除,请不要使用。

Environment variables can be made available through use of the pam_env.so module. See Environment variables#Using pam_env for configuration details.

随系统自动启动 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
Note: 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。

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 服务。 但另一方面,这个方案存在某些倒退,这在下面会提到。

xorg-server 提供了两种整合到 systemd 的方法:

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

注意: 这并不是 logind 强加的限制,而是 xorg 需要知道它将要接管的是哪个会话,而现在它通过调用 logind's GetSessionByPID 来获取这个信息(使用 xorg 自身的 pid 作为参数)。参见这个话题xorg 源码. 看上去如果 xorg 通过其依附的 tty 来获取会话信息的话,这个问题将得到解决。
Warning: 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
Note: 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.

Troubleshooting

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