systemd/User

来自 ArchWiki
(重定向自 Systemctl --user

systemd 提供了在用户控制下通过每个用户的 systemd 实例管理服务的能力,使用户能够启动、停止、启用和禁用他们自己的 用户单元。这对于通常为单个用户运行的守护进程和其他服务(例如 mpd)或执行自动化任务(如获取邮件)非常方便。

工作原理

根据 /etc/pam.d/system-login 中的默认配置,当用户首次登录时,pam_systemd 模块会自动启动一个 systemd --user 实例。只要该用户存在会话,此进程就会继续存在,并在该用户的最后一个会话关闭后立即被杀死。当启用#systemd 用户实例的自动启动时,该实例将在启动时启动,并且不会被杀死。systemd 用户实例负责管理用户服务,用户服务可用于运行守护进程或自动化任务,并具有 systemd 的所有优点,例如套接字激活、定时器、依赖关系系统以及通过 cgroup 进行的严格进程控制。

与系统单元类似,用户单元位于以下目录中(按升序优先级排序)

  • /usr/lib/systemd/user/:已安装软件包提供的单元位于此处。
  • ~/.local/share/systemd/user/:已安装在主目录中的软件包的单元位于此处。
  • /etc/systemd/user/:系统管理员放置的系统级用户单元位于此处。
  • ~/.config/systemd/user/:用户放置自己的单元的位置。

当 systemd 用户实例启动时,它会启动每个用户的目标 default.target。可以使用 systemctl --user 手动控制其他单元。请参阅 systemd.special(7) § UNITS MANAGED BY THE USER SERVICE MANAGER

注意
  • 请注意,systemd --user 实例是每个用户的进程,而不是每个会话的进程。其基本原理是,用户服务处理的大多数资源(如套接字或状态文件)将是每个用户的(位于用户的主目录中),而不是每个会话的。这意味着所有用户服务都在会话之外运行。因此,需要在会话内运行的程序可能会在用户服务中中断。systemd 处理用户会话的方式正在不断变化。有关未来发展方向的一些提示,请参阅 [1][2]
  • systemd --user 作为与 systemd --system 进程分离的进程运行。用户单元不能引用或依赖于系统单元或其他用户的单元。

基本设置

所有用户单元都将放置在 ~/.config/systemd/user/ 中。如果要在首次登录时启动单元,请为您要自动启动的任何单元执行 systemctl --user enable unit

提示: 如果要为所有用户而不是执行 systemctl 命令的用户启用单元,请以 root 身份运行 systemctl --global enable unitdisable 也类似。

环境变量

由 systemd 用户实例启动的单元不继承在 .bashrc 等位置设置的任何环境变量。有几种方法可以为它们设置环境变量

  • 对于具有 $HOME 目录的用户,在 ~/.config/environment.d/ 目录中创建一个 .conf 文件,其中包含 NAME=VAL 形式的行。仅影响该用户的用户单元。有关更多信息,请参阅 environment.d(5)
  • 使用 /etc/systemd/user.conf 文件中的 DefaultEnvironment 选项。影响所有用户单元。
  • /etc/systemd/system/user@UID.service.d/ 中添加一个 drop-in 配置文件,请参阅#服务示例
  • /etc/systemd/system/user@.service.d/ 中添加一个 drop-in 配置文件(影响所有用户),请参阅#服务示例
  • 在任何时候,使用 systemctl --user set-environmentsystemctl --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_DIRDBUS_SESSION_BUS_ADDRESS 等就是这种情况)。

您可能要设置的一个变量是 PATH

配置后,可以使用命令 systemctl --user show-environment 来验证值是否正确。您可能需要运行 systemctl --user daemon-reload 以使更改立即生效。

Systemd 用户实例

以上仅解决了用户单元的默认环境变量。但是,systemd 用户实例本身也受到某些环境变量的影响。特别是,某些说明符(请参阅 systemd.unit(5) § SPECIFIERS)受到 XDG 变量的影响。

但是,systemd 用户实例使用在其启动时设置的环境变量。特别是,它不会尝试解析文件,请参阅 上游错误 #29414(已关闭 WONTFIX)。因此,如果需要此类环境变量,则应在 drop-in 配置文件中设置它们,请参阅#服务示例

Systemd 不提供内省工具来检查这些值,但是,可以使用如下服务来帮助检查说明符是否按预期扩展

$XDG_CONFIG_HOME/systemd/user/test-specifiers.service
[Service]
Type=oneshot
ExecStart=printf '(systemd)=(envvar)\n'
ExecStart=printf '%%s=%%s\n' %C "${XDG_CACHE_HOME}"
ExecStart=printf '%%s=%%s\n' %E "${XDG_CONFIG_HOME}"
ExecStart=printf '%%s=%%s\n' %L "${XDG_STATE_HOME}"/log
ExecStart=printf '%%s=%%s\n' %S "${XDG_STATE_HOME}"
ExecStart=printf '%%s=%%s\n' %t "${XDG_RUNTIME_DIR}"

服务示例

创建 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/sbin:/usr/local/bin:/usr/bin"
Environment="EDITOR=nano -c"
Environment="BROWSER=firefox"
Environment="NO_AT_BRIDGE=1"
Environment="XDG_STATE_HOME=%h/.local/var/state"

DISPLAY 和 XAUTHORITY

DISPLAY 供任何 X 应用程序使用,以了解要使用的显示器,而 XAUTHORITY 提供用户 .Xauthority 文件的路径,从而提供访问 X 服务器所需的 cookie。如果您计划从 systemd 单元启动 X 应用程序,则需要设置这些变量。Systemd 在 /etc/X11/xinit/xinitrc.d/50-systemd-user.sh 中提供了一个脚本,用于在 X 启动时将这些变量导入到 systemd 用户会话中。[3] 因此,除非您以非标准方式启动 X,否则用户服务应知道 DISPLAYXAUTHORITY

PATH

如果您自定义了 PATH 并计划从 systemd 单元启动使用它的应用程序,则应确保在 systemd 环境中设置了修改后的 PATH。假设您在 .bash_profile 中设置了 PATH,则使 systemd 了解您修改后的 PATH 的最佳方法是在设置 PATH 变量后将以下内容添加到 .bash_profile

~/.bash_profile
systemctl --user import-environment PATH
注意
  • 这不会影响在导入 PATH 之前启动的 systemd 服务。
  • systemd 在解析非绝对二进制文件本身时不会查看设置的 PATH

pam_env

注意: 这种按用户设置环境变量的方式已弃用,将被删除。

可以通过使用 pam_env.so 模块来提供环境变量。有关配置详细信息,请参阅环境变量#使用 pam_env

systemd 用户实例的自动启动

systemd 用户实例在用户首次登录后启动,并在用户的最后一个会话关闭后被杀死。有时,在启动后立即启动它,并在最后一个会话关闭后保持 systemd 用户实例运行可能很有用,例如,在没有任何打开的会话的情况下运行某些用户进程。持久化用于此目的。如果安装了 polkit,请使用以下命令为您自己的用户启用持久化

$ loginctl enable-linger

如果没有 polkit 或要为其他用户启用持久化

# loginctl enable-linger username
警告: systemd 服务不是会话,它们在 logind 之外运行。请勿使用持久化来启用自动登录,因为它会破坏会话

要列出所有具有持久化许可的用户,请查看 “LINGER” 列,其中 yes 表示已启用

$ loginctl list-users 

或检查 /var/lib/systemd/linger。要撤销持久化

# loginctl disable-linger username

编写用户单元

有关编写 systemd 单元文件的常规信息,请参阅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
注意: journald 不会为 UID 低于 1000 的用户编写用户日志,而是 将所有内容定向到系统日志。

临时文件

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

本文或章节需要扩充。

原因: 涵盖 graphical-session.targetsystemd.special(7) § Special Passive User Units, [4]。(在 Talk:Systemd/User 中讨论)

有几种在 systemd 单元中运行 xorg 的方法。下面是两个选项,要么使用 xorg 进程启动新的用户会话,要么从 systemd 用户服务启动 xorg。

无需显示管理器自动登录 Xorg

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

原因: 此设置最终会产生两个用户 D-Bus 总线,一个用于桌面,另一个用于 systemd。为什么我们不能单独使用 systemd 的总线?(在 Talk:Systemd/User 中讨论)

此选项将启动一个系统单元,该单元将启动一个包含 xorg 服务器的用户会话,然后运行常用的 ~/.xinitrc 以启动窗口管理器等。您需要安装 xlogin-gitAUR。按照 Xinit#xinitrc 部分中的指定设置您的 xinitrc。

会话将使用其自己的 dbus 守护进程,但是各种 systemd 实用程序将自动连接到 dbus.service 实例。最后,启用 xlogin@username 服务以在启动时自动登录。用户会话完全在 systemd 作用域内运行,并且用户会话中的所有内容都应正常工作。

作为 systemd 用户服务的 Xorg

或者,可以从 systemd 用户服务中运行 xorg。这很好,因为其他与 X 相关的单元可以依赖于 xorg 等,但另一方面,它也有一些缺点,如下所述。

xorg-server 通过两种方式与 systemd 集成

  • 可以以非特权模式运行,将设备管理委托给 logind(请参阅 Hans de Goede 在 此提交 附近的提交)。
  • 可以制作为套接字激活服务(请参阅 此提交)。

不幸的是,为了能够以非特权模式运行 xorg,它需要在会话内运行。因此,目前将 xorg 作为用户服务运行的障碍是,它必须以 root 权限运行(如 1.16 之前那样),并且无法利用 1.16 中引入的非特权模式。

注意: 这不是 logind 施加的基本限制,但原因似乎是 xorg 需要知道要接管哪个会话,而现在它通过调用 logindGetSessionByPID 并使用其自己的 pid 作为参数来获取此信息。请参阅 此线程xorg 源代码。xorg 很可能可以修改为从其连接的 tty 获取会话,然后它可以从会话之外的用户服务以非特权模式运行。
警告: 在 xorg 1.18 上,套接字激活似乎已损坏。触发激活的客户端死锁。请参阅上游错误报告 [5]。作为临时解决方法,您可以启动不带套接字激活的 xorg 服务器,确保客户端在延迟后连接,以便服务器完全启动。似乎没有很好的机制来获取 X 服务器的启动通知。

这是如何从用户服务启动 xorg 的方法

1. 通过编辑 /etc/X11/Xwrapper.config,使 xorg 以 root 权限为任何用户运行。这建立在 Xorg#Xorg as Root 的基础上,并添加了不必从物理控制台执行此操作的规定。也就是说,allowed_user 的默认值 consoleanybody 覆盖;请参阅 Xorg.wrap(1)

/etc/X11/Xwrapper.config
allowed_users=anybody
needs_root_rights=yes

2. 将以下单元添加到 ~/.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 将启动的虚拟终端,可以硬编码在服务单元中,也可以使用以下命令在 systemd 环境中设置

$ systemctl --user set-environment XDG_VTNR=1
注意: xorg 应在用户登录的同一虚拟终端启动。否则,logind 将认为会话处于非活动状态。

3. 确保按照上面的说明配置 DISPLAY 环境变量。

4. 然后,要在显示器 0 和 tty 2 上为 xorg 启用套接字激活,可以执行

$ 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

现在,运行任何 X 应用程序都会自动在虚拟终端 2 上启动 xorg。

环境变量 XDG_VTNR 可以在 .bash_profile 中的 systemd 环境中设置,然后可以启动任何 X 应用程序,包括窗口管理器,作为依赖于 xorg@0.socket 的 systemd 单元。

警告: 当前将窗口管理器作为用户服务运行意味着它在会话之外运行,这可能会带来问题:破坏会话。但是,systemd 开发人员似乎打算使这种做法成为可能。请参阅 [6][7]

一些用例

窗口管理器

要将窗口管理器作为 systemd 服务运行,您首先需要运行#作为 systemd 用户服务的 Xorg。在下面,我们将使用 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 会话,该会话将其信息写入 /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

启用 tmux.servicemultiplexer.target 以及您创建的要由 cruft.target 运行的任何服务,启动 user@.service 和往常一样,您应该就完成了。

注销时杀死用户进程

Arch Linux 构建 systemd 软件包时使用了 --without-kill-user-processes,默认情况下将 KillUserProcesses 设置为 no。此设置导致用户进程在用户注销时不会被杀死。要更改此行为以便在用户注销时杀死所有用户进程,请在 /etc/systemd/logind.conf 中设置 KillUserProcesses=yes

请注意,更改此设置会破坏终端复用器,例如 tmuxGNU Screen。如果您更改此设置,您仍然可以使用终端复用器,方法是按如下方式使用 systemd-run

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

例如,要运行 screen,您可以执行

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

使用 systemd-run 将使进程在注销后继续运行,前提是用户至少在系统中的其他位置登录一次,并且 user@.service 仍在运行。

在用户注销所有会话后,user@.service 也将默认终止,除非用户已启用“持久化”[8]。要有效地允许用户运行长期任务,即使他们完全注销,也必须为他们启用持久化。请参阅#systemd 用户实例的自动启动loginctl(1) 以了解详细信息。

故障排除

运行时目录 '/run/user/1000' 不应由 UID 1000 拥有,但事实并非如此

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 服务。

参见