systemd 用户session
十二月 20, 2024 更新: 二月 25, 2025 [桌面与效率] #Linux #Shell 与命令行 #obsidian #发行版 #桌面应用systemd 用户session
工作原理
从 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 --user和systemd --system运行于不同的进程里面,所以用户单元不能引用或依赖于系统单元或其他用户的单元。
基础设置
所有的用户单元都位于 ~/.config/systemd/user 路径下。 如果你想在首次用户登录时运行单元,对想要自动启动的单元执行 systemctl --user enable unit 即可。
提示: 如果不是只让发出"systemctl"命令的用户启用某个service单元,而是想要让所有用户都生效,请以root权限执行systemctl --global enable service 命令。
环境变量
systemd 用户实例不会继承类似 .bashrc 中定义的环境变量。systemd 用户实例有三种设置环境变量的方式:
- 对于有
$HOME目录的用户,可以在~/.config/environment.d/目录中新建一个".conf"文件,然后在其中写入格式为NAME=VAL这样的行。这些设置只对指定用户的用户单元有效 。更多信息可以参考 environment.d(5) 。 - 使用
/etc/systemd/user.conf文件中的DefaultEnvironment选项。这个配置在所有的用户单元中可见。 - 在
/etc/systemd/system/user@.service.d/下增加配置文件设置。 这个配置在所有的用户单元中可见。 - 在任何时候, 使用
systemctl --user set-environment或systemctl --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_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"
任何一个 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
使用变量的例子
下面是用于 foldingathome^AUR^ 的用户服务,将主目录作为变量传入使得 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
https://wiki.archlinuxcn.org/wiki/File:Tango-view-fullscreen.png这篇文章的某些内容需要扩充。 https://wiki.archlinuxcn.org/wiki/File:Tango-view-fullscreen.png
原因: 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-git^AUR^,并按照 Xinit#xinitrc 来设置好你的 xinitrc。
会话会使用它自己的 dbus 守护,而需要用到 dbus.service 的 systemd 工具会自动连接到会话的 dbus 实例上。最后启用 xlogin@username 服务来达成开机自动登录。 整个用户会话都在 systemd 的作用域下运行,会话内的一切都能正常工作。
将 Xorg 作为 systemd 用户服务
另外一种选择是将 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 来获取会话信息的话,这个问题将得到解决。
警告: 在 xorg 1.18 上 socket 激活存在问题,客户端会触发激活死锁。具体查阅上游报告 [4]。临时策略是启动一个不带 socket 激活的 xorg 服务器,并在服务器启动一段时间后客户端再连接,确保连接时服务器已经启动。目前来看没有一个可靠机制来判断 X 服务器是否启动完毕。
下面是从用户服务运行 xorg 的步骤:
- 通过编辑
/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
- 把下面 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 会认为会话没有激活。
- 确保
DISPLAY环境变量已经配置,参考这里. - 接下来,执行以下命令,使得 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 来使它在登录时启动。推荐采用启用服务的方法来代替手动创建软链接。
终端复用器持久化
比起默认登入一个窗口管理器,你可能更想要自动在后台运行一个终端复用器(比如 screen 或 tmux)。
创建以下文件:
~/.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.service、multiplexer.target 和由 cruft.target 运行的服务,像往常一样 start user@.service 即可。
在注销后结束用户进程
Arch Linux 用 --without-kill-user-processes 参数编译了 systemd^包^ 包,默认下 KillUserProcesses 是 no。这一设置导致了在用户注销后,用户进程不会结束。要改变这一行为,在用户注销后结束所有用户进程,请在 /etc/systemd/logind.conf 中设置 KillUserProcesses=yes。
注意,修改这一设置会破坏终端复用器,如 tmux 和 GNU 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 服务来修复此问题。
参阅
- KaiSforza's Bitbucket wiki
- Zoqaeski's units on GitHub
- Arch forum thread about changes in systemd 206 user instances
分类: