跳转至内容

systemd/User (用户)

来自 ArchWiki

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

工作原理

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

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

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

当 systemd 用户实例启动时,它会启动每个用户的默认目标 default.target。其他单元可以通过 systemctl --user 手动控制。有关 systemd.special(7) § 用户服务管理器管理的单元,请参阅 systemd.special(7) § 用户服务管理器管理的单元

  • 请注意,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) § 说明符)受 XDG 变量 的影响。

但是,systemd 用户实例 **只会** 使用在启动时设置的环境变量。特别是,它不会尝试解析文件,请参阅 上游 bug #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"

重用 shell 登录环境

如果您通常通过 shell 登录机制(即在 ~/.profile~/.bash_profile~/.zprofile 或类似文件中)设置环境,则可以使用 systemd.environment-generator(7) 逻辑(如上)将 shell 登录环境读入 systemd 用户实例。创建以下脚本:

/etc/systemd/user-environment-generators/10-profile
#!/bin/sh
env -i -- $SHELL --login -c env | grep -vE '^(_|SHLVL|PWD|OLDPWD)='

该脚本将您的 $SHELL 调用为一个登录 shell,并转储生成的环境,同时删除临时的 shell 变量。这只会在管理器启动时执行一次,并可以通过 systemctl --user daemon-reload 按需重新加载。

它提供了一个非交互式登录 shell 所能获得的相同环境变量块 — 登录 GettySSH 后看到的相同环境,但不包括在 ~/.bashrc~/.zshrc 等文件中设置的任何内容 — 包括来自 /etc/profile/etc/profile.d 的系统范围环境。这类似于 gnome-shell 等的做法,它会 启动 一个登录 shell,并 用生成的环境更新 systemd。

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 单元启动利用它的应用程序,则应确保已将修改后的 PATH 设置在 systemd 环境中。假设您在 .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 用户实例运行可能很有用,例如,为了让一些用户进程在没有任何打开会话的情况下运行。长时运行 (lingering) 用于此目的。如果您已安装 polkit,请使用以下命令为您自己的用户启用长时运行:

$ loginctl enable-linger

没有 polkit 或为其他用户启用长时运行:

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

要列出所有允许长时运行的用户,请查看 yes 列的 "LINGER" 列,使用 loginctl

$ 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

带变量的示例

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

原因: 用户单元目前还不支持排序在系统单元之后。 (讨论请参阅 Talk:Systemd/User#该文章是否有因用户单元引用系统目标而导致的坏例子?)

以下是 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) § 说明符 中所述,%h 变量将被替换为运行服务的用户的家目录。在 systemd 手册页中还可以考虑其他变量。

读取 journal

可以使用类似的命令读取用户的 journal:

$ journalctl --user

要指定一个单元,可以使用:

$ journalctl --user-unit myunit.service

或者,等效地:

$ journalctl --user -u myunit.service
注意 journald 不会为 UID 小于 1000 的用户写入用户 journal,而是 所有内容重定向到系统 journal。

临时文件

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) § 特殊被动用户单元[4]。 (讨论请参阅 Talk:Systemd/User)

有几种方法可以在 systemd 单元内运行 xorg。下面有 3 种选择:通过启动带 xorg 进程的新用户会话,从 systemd 用户服务启动 xorg,或者将 xinit 和应用程序作为服务启动。

Xorg 作为 systemd 用户服务

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

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

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

不幸的是,为了能够以非特权模式运行 xorg,它需要在会话中运行。因此,目前以用户服务形式运行 xorg 的主要缺点是它必须以 root 权限运行(与 1.16 版本之前相同),并且无法利用 1.16 版本中引入的非特权模式。

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

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

1. 通过编辑 /etc/X11/Xwrapper.config,使 xorg 以任何用户的 root 权限运行。这建立在 Xorg#以 root 身份运行 Xorg 的基础上,并增加了不需要从物理控制台执行此操作的规定。也就是说,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]

xinit 和应用程序作为 systemd 服务

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

原因: 这应该是一个 *用户* 服务,而不是一个 *系统* 服务。特别是,此设置会破坏 dbus 会话。 (讨论请参阅 Talk:Systemd/User)

以下服务是一个以用户权限运行 xinit 和 mate-session 的示例。

/etc/systemd/system/xinit.service
[Unit]
After=graphical.target systemd-user-sessions.service modprobe@drm.service
Conflicts=getty@tty1.service mdoprobe@drm.service

[Service]
Type=simple
User=username
WorkingDirectory=~

PAMName=login
Environment=XDG_SESSION_TYPE=x11
TTYPath=/dev/tty1
StandardInput=tty
UnsetEnvironment=TERM

StandardOutput=journal
ExecStart=/bin/xinit /bin/mate-session -- -quiet -logfile /dev/null -nolisten tcp vt01
[Install]
WantedBy=graphical.target

另请参阅 [8]

一些用例

窗口管理器

要将窗口管理器作为 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 作为示例,并源自一个将信息写入 /tmp/gpg-agent-info 的 gpg-agent 会话。此示例会话在您启动 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 以 --without-kill-user-processes 选项构建 systemd 软件包,默认将 KillUserProcesses 设置为 no。此设置导致用户注销时不会终止用户进程。要更改此行为,以便在用户注销时终止所有用户进程,请在 /etc/systemd/logind.conf 中将 KillUserProcesses=yes 设置为 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 将默认终止,除非用户启用了“长时运行” [9]。为了有效地允许用户在完全注销后仍运行长期任务,必须为其启用长时运行。有关详细信息,请参阅 #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 服务。

"正在停止用户管理器 (UID 1000) 的一个停止作业"

如果在关机期间看到此消息,通常带有 2 分钟的超时,这意味着其中一个用户服务未能及时停止。这可能是由一个行为不端的应用程序引起的,该应用程序先前已生成了瞬态服务。您可以简单地等待超时到期,但如果这让您烦恼,您可以为行为不端的服务创建一个覆盖,或者减少所有用户服务的全局超时。

查找并覆盖行为不端的服务

要对该问题进行故障排除,请启动 systemd 调试 shell

# systemctl start debug-shell

然后,重新启动或关闭系统。当出现问题时,使用 Ctrl+Alt+F9 切换到调试 shell。要找出是哪个服务阻止了关机,请运行:

# systemctl --user list-jobs

对于大多数开源应用程序,应将此问题报告给相应的维护者,这样就不需要覆盖。然而,对于闭源应用程序,可以像这样创建一个覆盖:

$ systemctl --user edit --force name@.service 
[Service]
TimeoutStopSec=1s

这将将该特定服务的超时时间缩短到 1 秒。--force 参数仅适用于没有在磁盘上创建 .service 文件的瞬态服务。覆盖仍然有效。可以使用 KillSignal=SIGKILL 替换超时。这将导致服务在用户管理器停止时立即被终止。只有在您知道服务可以处理时才使用此选项。

更改超时值

如果您不关心是哪个服务阻止了关机,您可以以类似的方式更改所有用户服务的全局超时:

# systemctl edit user@.service 
[Service]
TimeoutStopSec=10s

在此超时后,任何未能优雅停止的用户服务都将被终止,这相当于突然断电。请根据您的具体用例调整此值。将超时值设置得过低可能会导致数据损坏,具体取决于应用程序。

参见