systemd/用户
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) § 用户服务管理器管理的单元.
基础设置[编辑 | 编辑源代码]
所有的用户单元都位于 ~/.config/systemd/user
路径下。 如果你想在首次用户登录时运行单元,对想要自动启动的单元执行 systemctl --user enable unit
即可。
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"
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 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
临时文件[编辑 | 编辑源代码]
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[编辑 | 编辑源代码]
使用 systemd 单元来运行 Xorg 有好几种方法,下面介绍其中两种,一种是启动一个新的用户会话,在里面运行 Xorg 服务,另外一种是用 systemd 用户服务启动 Xorg。
没有显示管理器的情况下自动登录到 Xorg[编辑 | 编辑源代码]
这种方法通过一个系统单元将用户会话带起来,并在用户会话里面启动一个 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 的方法:
但非常不幸,xorg 的无特权模式需要在用户会话里面运行。所以,xorg 的用户服务只能在 root 权限下运行(和 1.16 版本之前一样),而不能使用 1.16 版本提供的无特权模式。
GetSessionByPID
来获取这个信息(使用 xorg 自身的 pid 作为参数)。参见这个话题 和 xorg 源码. 看上去如果 xorg 通过其依附的 tty 来获取会话信息的话,这个问题将得到解决。下面是从用户服务运行 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
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
。
一些用例[编辑 | 编辑源代码]
窗口管理器[编辑 | 编辑源代码]
以一个 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 服务来修复此问题。