systemd/用户

来自 Arch Linux 中文维基

systemd 会给每个用户生成一个 systemd 实例,用户可以在这个实例下管理服务,启动、停止、启用以及禁用他们自己的单元。这个能力大大方便了那些通常在特定用户下运行的守护进程和服务,比如 Music Player Daemon, 还有像拉取邮件等需要自动执行的任务。

工作原理[编辑 | 编辑源代码]

从 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

pam_env[编辑 | 编辑源代码]

注意: 这种为用户设置环境变量的方法已弃用且将被移除。

环境变量可以通过 pam_env.so 模块来设置。参阅环境变量#使用 pam_env 获取配置详情。

随系统自动启动 systemd 用户实例[编辑 | 编辑源代码]

systemd 用户实例在用户首次登录时启动,并在最后一个会话退出时终止。 但有时候,对于一些不依赖于会话的用户进程,在系统启动时加载用户实例,在会话全部结束时,也不停止用户实例是比较有用的。Lingering 就是用来实现这个的。 使用以下命令来启用驻留:

$ loginctl enable-linger

为另一个用户启用驻留:

# loginctl enable-linger username
警告: systemd 服务是 没有 会话的, 它们在 logind 状态之外运行, 所以不要在 lingering 中启用自动登录的功能,这会导致 会话中断

开发用户单元[编辑 | 编辑源代码]

通用的 systemd unit 文件编写请参考 systemd#编写单元文件

例子[编辑 | 编辑源代码]

下面是 mpd 服务用户版本的例子:

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

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

[Install]
WantedBy=default.target

使用变量的例子[编辑 | 编辑源代码]

下面是用于 foldingathomeAUR 的用户服务,将主目录作为变量传入使得 Folding@home 能找到指定文件:

~/.config/systemd/user/foldingathome-user.service
[Unit]
Description=Folding@home distributed computing client
After=network.target

[Service]
Type=simple
WorkingDirectory=%h/.config/fah
ExecStart=/usr/bin/FAHClient
CPUSchedulingPolicy=idle
IOSchedulingClass=3

[Install]
WantedBy=default.target

systemd.unit(5) 的 SPECIFIERS 章节中,详细介绍了各种变量, %h 指示符将使用运行该服务的用户的主目录替代。更多的变量参考 systemd 的手册页。

查看日志[编辑 | 编辑源代码]

查看用户单元的日志可以用类似命令:

$ journalctl --user

查看指定单元的日志,可以使用以下命令:

$ journalctl --user-unit myunit.service

也可以这样:

$ journalctl --user -u myunit.service
注意: UID 小于 1000 的用户的日志不会被 journald 记录,需要的话可以 重定向 所有内容至系统日志。

临时文件[编辑 | 编辑源代码]

systemd-tmpfiles 可以像管理系统临时文件那样管理用户自定义的临时文件和目录(参阅 systemd#systemd-tmpfiles - 临时文件)。用户配置文件以 ~/.config/user-tmpfiles.d/~/.local/share/user-tmpfiles.d/ 的顺序读取。为了正常使用本功能,需要为用户启用必要的 systemd 用户单元:

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

配置文件的语法格式和系统级的配置是一样的,详情请参阅 systemd-tmpfiles(8)tmpfiles.d(5) 手册页。

Xorg 和 systemd[编辑 | 编辑源代码]

这篇文章的某些内容需要扩充。

原因: Cover graphical-session.target: systemd.special(7) § Special Passive User Units, [3]. (在 Talk:Systemd/用户 中讨论)

使用 systemd 单元来运行 Xorg 有好几种方法,下面介绍其中两种,一种是启动一个新的用户会话,在里面运行 Xorg 服务,另外一种是用 systemd 用户服务启动 Xorg。

没有显示管理器的情况下自动登录到 Xorg[编辑 | 编辑源代码]

本文或本章节的事实准确性存在争议。

原因: 这样设置会导致用户有2条 D-Bus 总线,一条用于桌面,另一条用于 systemd。为什么不共用 systemd 的那一条?(在 Talk:Systemd/用户 中讨论)


这种方法通过一个系统单元将用户会话带起来,并在用户会话里面启动一个 xorg 服务,并运行 ~/.xinitrc 将窗口管理器等启动起来。你需要安装好 xlogin-gitAUR,并按照 Xinit#xinitrc 来设置好你的 xinitrc。

会话会使用它自己的 dbus 守护,而需要用到 dbus.service 的 systemd 工具会自动连接到会话的 dbus 实例上。最后启用 xlogin@username 服务来达成开机自动登录。 整个用户会话都在 systemd 的作用域下运行,会话内的一切都能正常工作。

将 Xorg 作为 systemd 用户服务[编辑 | 编辑源代码]

另外一种选择是将 xorg 作为一个 systemd 用户服务。这是一种不错的方案,因为其他的 X-related units 可以依赖于 xorg 服务。 但另一方面,这个方案存在某些倒退,这在下面会提到。

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

  • 可以在无特权模式下运行,设备管理由 logind 代为管理(参考 Hans de Goede 的这个提交)。
  • 可以实现通过 socket 激活服务 (参考这个提交)。

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

注意: 这并不是 logind 强加的限制,而是 xorg 需要知道它将要接管的是哪个会话,而现在它通过调用 logind's GetSessionByPID 来获取这个信息(使用 xorg 自身的 pid 作为参数)。参见这个话题xorg 源码. 看上去如果 xorg 通过其依附的 tty 来获取会话信息的话,这个问题将得到解决。
警告: 在 xorg 1.18 上 socket 激活存在问题,客户端会触发激活死锁。具体查阅上游报告 [4]。临时策略是启动一个不带 socket 激活的 xorg 服务器,并在服务器启动一段时间后客户端再连接,确保连接时服务器已经启动。目前来看没有一个可靠机制来判断 X 服务器是否启动完毕。

下面是从用户服务运行 xorg 的步骤:

1. 通过编辑 /etc/X11/Xwrapper.config 文件,允许所有用户使用 root 权限运行 xorg。这是在 Xorg#Xorg as Root 的基础上,无需用户在实际控制台上登录,而是将 allowed_user 的默认 console 控制台定义成了 anybody。参见 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     # 这样 xorg@.service 知道该用哪个虚拟终端
$ systemctl --user start xorg@0.socket            # 开始为 display 0 监听 socket

现在,在 tty 2上运行任意的X应用,xorg 都会自动启动。

可以在 .bash_profile 里面把环境变量 XDG_VTNR 设置到 systemd 环境里面。在这之后,你可以使用 systemd 单元启动任意的X应用,包括窗口管理器。当然,这些 systemd 单元必须依赖于 xorg@0.socket

警告: 当前,通过用户服务启动窗口管理器意味着它是在会话之外运行的,这将带来以下问题: break the session. 但是,systemd 的开发者看上去更倾向于这样(?)。参见 [5][6]

一些用例[编辑 | 编辑源代码]

窗口管理器[编辑 | 编辑源代码]

以一个 systemd 服务来运行窗口管理器需要先 #将 Xorg 作为 systemd 用户服务来运行。下面我们以 awesome 举例:

~/.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] 一节包括一个 WantedBy 参数,当使用 systemctl --user enable 时会创建一个本服务的软链接到 ~/.config/systemd/user/wm.target.wants/window_manager.service 来使它在登录时启动。推荐采用启用服务的方法来代替手动创建软链接。

终端复用器持久化[编辑 | 编辑源代码]

比起默认登入一个窗口管理器,你可能更想要自动在后台运行一个终端复用器(比如 screentmux)。

创建以下文件:

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

将用户登录与 X 登录分开,对引导到 TTY 而不是显示管理器的用户是有用的,此时你可以将所有要启动的东西绑定到 mystuff.target

依赖项 cruft.target 与上述 mystuff.target 类似,用来在终端复用器启动前启动你需要的服务(或用来启动那些需要开机启动但时序要求不高的服务),比如 GnuPG 守护程序。

现在可以为你的终端复用器创建一个服务了。以下是一个例子,以 tmux 为例且加载了一个 gpg-agent 会话,这个 gpg-agent 会话会将信息写入 /tmp/gpg-agent-info。这个示例会话在将来启动 X 以后也可以运行 X 程序,因为设置了 $DISPLAY

~/.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.servicemultiplexer.target 和由 cruft.target 运行的服务,像往常一样 start user@.service 即可。

在注销后结束用户进程[编辑 | 编辑源代码]

Arch Linux 用 --without-kill-user-processes 参数编译了 systemd 包,默认下 KillUserProcessesno。这一设置导致了在用户注销后,用户进程不会结束。要改变这一行为,在用户注销后结束所有用户进程,请在 /etc/systemd/logind.conf 中设置 KillUserProcesses=yes

注意,修改这一设置会破坏终端复用器,如 tmuxGNU Screen。修改设置后仍想使用终端复用器,需按下列方法使用 systemd-run

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

比如要运行 screen 可以这样:

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

只有在用户在某处登录过系统至少一次,且 user@.service 仍在运行的情况下,使用 systemd-run 可以保持进程在用户注销后继续运行。

用户注销所有会话后,默认 user@.service 也会终止,除非用户启用了 "lingering" [7] 。为了让用户在注销后也能运行长期任务,需要为他们启用 lingering。详情参阅 #随系统自动启动 systemd 用户实例loginctl(1)

疑难解答[编辑 | 编辑源代码]

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

如果你看到类似错误且无法登录会话,有可能是另一个系统级(非用户级)的服务正在创建该目录。例如正在使用的 docker 容器挂载了 /run/user/1000。可以修改容器取消挂载,或禁用、延迟启动 docker 服务来修复此问题。

参阅[编辑 | 编辑源代码]