systemd/用户
systemd 会给每个用户生成一个 systemd 实例,用户可以在这个实例下管理服务,启动、停止、启用以及禁用他们自己的单元。这个能力大大方便了那些通常在特定用户下运行的守护进程和服务,比如 mpd, 还有像拉取邮件等需要自动执行的任务。
工作原理[编辑 | 编辑源代码]
从 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
when resolving non-absolute binaries itself.
随系统自动启动 systemd 用户实例[编辑 | 编辑源代码]
systemd 用户实例在用户首次登陆时启动,并在最后一个会话退出时终止。 但有时候,对于一些不依赖于会话的用户进程,在系统启动时加载用户实例,在会话全部结束时,也不停止用户实例是比较有用的。Lingering 就是用来实现这个的。 使用以下命令来启用驻留指定用户:
# loginctl enable-linger username
开发用户单元[编辑 | 编辑源代码]
通用的 unit 文件编写请参考 systemd#Writing unit files。
例子[编辑 | 编辑源代码]
下面是 mpd 服务用户版本的例子:
~/.config/systemd/user/mpd.service
[Unit] Description=Music Player Daemon [Service] ExecStart=/usr/bin/mpd --no-daemon [Install] WantedBy=default.target
使用变量的例子[编辑 | 编辑源代码]
下面是 sickbeard.service
用户版本的例子, 在配置中,使用了主目录变量(%h):
~/.config/systemd/user/sickbeard.service
[Unit] Description=SickBeard Daemon [Service] ExecStart=/usr/bin/env python2 /opt/sickbeard/SickBeard.py --config %h/.sickbeard/config.ini --datadir %h/.sickbeard [Install] WantedBy=default.target
在systemd.unit(5)的SPECIFIERS章节中,详细介绍了各种变量, %h
指示符将使用运行该服务的用户的主目录替代。更多的变量参考 systemd 的 manpages。
Reading the journal[编辑 | 编辑源代码]
The journal for the user can be read using the analogous command:
$ journalctl --user
To specify a unit, one can use
$ journalctl --user-unit myunit.service
Or, equivalently:
$ journalctl --user -u myunit.service
Temporary files[编辑 | 编辑源代码]
systemd-tmpfiles allows users to manage custom volatile and temporary files and directories just like in the system-wide way (see systemd#systemd-tmpfiles - temporary files). User-specific configuration files are read from ~/.config/user-tmpfiles.d/
and ~/.local/share/user-tmpfiles.d/
, in that order. For this functionality to be used, it is needed to enable the necessary systemd user units for your user:
$ systemctl --user enable systemd-tmpfiles-setup.service systemd-tmpfiles-clean.timer
The syntax of the configuration files is the same than those used system-wide. See the systemd-tmpfiles(8) and tmpfiles.d(5) man pages for details.
Xorg 和 systemd[编辑 | 编辑源代码]
使用 systemd 单元来运行 Xorg 有好几种方法,下面介绍其中两种,一种是启动一个新的用户会话,在里面运行 Xorg 服务,另外一种是用 systemd 用户服务启动 Xorg。
不用显示管理器自动登录[编辑 | 编辑源代码]
这种方法通过一个系统单元将用户会话带起来,并在用户会话里面启动一个 xorg 服务,并运行 ~/.xinitrc
将窗口管理器等启动起来。
你需要配置好 #D-Bus 并安装 xlogin-gitAUR。
配置你的 xinitrc 文件, 让它 source /etc/X11/xinit/xinitrc.d/
目录下的所有文件。~/.xinitrc
在运行的时候不要返回(返回意味着会话结束)。你可以通过在 xinitrc 的最后加上 wait
命令,或使用 exec
来运行最后一条命令,最后一条命令应该在整个用户会话都不会退出(如你的窗口管理器)。
会话会使用它自己的 dbus 守护,而需要用到 dbus.service
的 systemd 工具会自动连接到会话的 dbus 实例上。
最后,在 (root) 用户下,启用xlogin服务,使其开机自启动:
# systemctl enable xlogin@username
整个用户会话都在 systemd 的作用域下运行,会话内的一切都能正常工作。
Xorg as a systemd user service[编辑 | 编辑源代码]
另外一种选择是将 xorg 作为一个 systemd 用户服务。这是一种不错的方案,因为其他的 X-related units 可以依赖于 xorg 服务。 但另一方面,这个方案存在某些倒退,这在下面会提到。
xorg-server包 提供了两种整合到 systemd 的方法:
- 可以在无特权模式下运行,设备管理由 logind 代为管理(参考 Hans de Goede 的这个提交).
- 可以实现通过 socket 激活服务 (参考这个提交). 这点使得我们不再需要依赖于 systemd-xorg-launch-helper-gitAUR[损坏的链接:package not found].
但非常不幸,xorg 的无特权模式需要在用户会话里面运行。所以,xorg 的用户服务只能在 root 权限下运行(和 1.16 版本之前一样),而不能使用 1.16 版本提供的无特权模式。
GetSessionByPID
来获取这个信息(使用 xorg 自身的 pid 作为参数)。参见这个话题 和 xorg 源码. 看上去如果 xorg 通过其依附的 tty 来获取会话信息的话,这个问题将得到解决。下面是从用户服务运行 xorg 的步骤:
1. 通过编辑/etc/X11/Xwrapper.config
文件,允许所有用户使用root权限运行xorg. This builds on Xorg#Xorg as Root by adding the stipulation that this need not be done from a physical console. That is, allowed_user
's default of console
is being overwritten with anybody
; see 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 # So that xorg@.service knows which vt use $ systemctl --user start xorg@0.socket # Start listening on the socket for display 0
现在,在 tty 2上运行任意的X应用,xorg 都会自动启动。
可以在 .bash_profile
里面把环境变量 XDG_VTNR
设置到 systemd 环境里面。在这之后,你可以使用 systemd 单元启动任意的X应用,包括窗口管理器。当然,这些 systemd 单元必须依赖于 xorg@0.socket
。
X 应用程序须知[编辑 | 编辑源代码]
大多数X 应用运行都需要 DISPLAY
变量。如何让所有systemd用户实例看到这个环境变量,参考 #DISPLAY 和 XAUTHORITY。
Some use cases[编辑 | 编辑源代码]
Window manager[编辑 | 编辑源代码]
To run a window manager as a systemd service, you first need to run #Xorg as a systemd user service. In the following we will use awesome as an example:
~/.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]
section includes a WantedBy
part. When using systemctl --user enable
it will link this as ~/.config/systemd/user/wm.target.wants/window_manager.service
, allowing it to be started at login. Is recommended to enable this service, not to link it manually.Persistent terminal multiplexer[编辑 | 编辑源代码]
Rather than logging you into a window manager session for your user session by default, you may want to automatically run a terminal multiplexer (such as screen or tmux) in the background.
Create the following:
~/.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
Separating login from X login is most likely only useful for those who boot to a TTY instead of to a display manager (in which case you can simply bundle everything you start in mystuff.target
).
The dependency cruft.target
, like the mystuff.target
above, allows starting anything which should run before the multiplexer starts (or which you want started at boot regardless of timing), such as a GnuPG daemon session.
You then need to create a service for your multiplexer session. Here is a sample service, using tmux as an example and sourcing a gpg-agent session which wrote its information to /tmp/gpg-agent-info
. This sample session, when you start X, will also be able to run X programs, since $DISPLAY
is set:
~/.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
and any services you created to be run by cruft.target
, start user@.service
as usual and you should be done.
Kill user processes on logout[编辑 | 编辑源代码]
Arch Linux builds the systemd包 package with --without-kill-user-processes
, setting KillUserProcesses
to no
by default. This setting causes user processes not to be killed when the user logs out. To change this behavior in order to have all user processes killed on the user's logout, set KillUserProcesses=yes
in /etc/systemd/logind.conf
.
Note that changing this setting breaks terminal multiplexers such as tmux and GNU Screen. If you change this setting, you can still use a terminal multiplexer by using systemd-run
as follows:
$ systemd-run --scope --user command args
For example, to run screen
you would do:
$ systemd-run --scope --user screen -S foo
Using systemd-run
will keep the process running after logout only while the user is logged in at least once somewhere else in the system and user@.service
is still running.
After the user logs out of all sessions, user@.service
will be terminated too, by default, unless the user has "lingering" enabled [6]. To effectively allow users to run long-term tasks even if they are completely logged out, lingering must be enabled for them. See #Automatic start-up of systemd user instances and loginctl(1) for details.
问题解决[编辑 | 编辑源代码]
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
If you see errors such as this and your login session is broken, it is possible that another system (non-user) service on your system is creating this directory. This can happen for example if you use a docker container that has a bind mount to /run/user/1000
. To fix this, you can either fix the container by removing the mount, or disable/delay the docker service.