systemd/User (简体中文)

翻译状态: 本文是英文页面 Systemd/User翻译,最后翻译时间:2014-11-09,点击这里可以查看翻译后英文页面的改动。

systemd (简体中文) 会给每个用户生成一个systemd实例,用户可以在这个实例下管理服务,启动、停止、启用以及禁用他们自己的单元。 这个能力大大方便了那些通常在特定用户下运行的守护进程和服务,比如 mpd, 还有像拉取邮件等需要自动执行的任务。 在某些场景下,它甚至可以在指定用户下运行xorg以及整个窗口管理系统。

工作原理

在用户首次登陆的时候, systemd 自动运行一个 systemd --user 实例, 负责管理用户的服务。 只要用户还有会话存在,这个进程就不会退出;用户所有会话退出时,进程将会被销毁。当 #随系统自动启动systemd用户实例 启用时, 这个用户实例将在系统启动时加载,并且不会被销毁。默认情况下,没有用户服务运行。 用户服务可以使用systemd提供的各种便捷机制来运行守护进程或自动化任务,如socket激活、定时器、依赖体系以及通过cgroup限制进程等。用户单元可以在以下目录找到(按优先级从低到高排序):

  • /usr/lib/systemd/user/ 这里保存的是各个软件包安装的服务。
  • /etc/systemd/user/ 这里是系统范围的用户服务。
  • ~/.config/systemd/user/ 这里是用户自身的服务。

当systemd用户实例启动时,它会将 default.target 带起来,在这之后,用户实例会将控制权交给 systemctl --user

Warning: 注意, systemd --user 实例是针对每个用户处理的,而不是针对会话。这样做的原理是用户服务处理的大部分资源,像socket或状态文件是针对每个用户的(存活于用户的主目录下)而不是会话。这意味着所有的用户服务是独立于会话之外运行的。最终,我们得出结论:基于会话运行的程序可能会导致用户服务中断。systemd处理用户会话的方式是非常生硬的(pretty much in flux)。 单会话支持的进展参考 [1][2]

基础设置

Systemd用户实例会在用户首次登陆时自动启动. 检查用户实例是否已经处于运行中状态,你可以运行命令 systemctl --user status

Note: 如果systemd用户实例没有自动启动,检查 /etc/pam.d/system-login文件。它应该存在这样一行: -session optional pam_systemd.so。 另外,检查一下是否存在 .pacnew 文件

所有的用户服务都位于 ~/.config/systemd/user 路径下。 如果你想在首次用户登陆时运行服务,对想要自动启动的服务执行 systemctl --user enable service 即可。

Note: 从systemd 206开始, systemd用户实例的机制发生了变化。 现在 pam_systemd.so 模块默认会生成一个用户实例,所以不再需要手动运行。来自user-session-unitsAURuser-session@.service 已经被废除。 这个变化使得 systemd --user 在会话之外运行成为必然。

D-Bus

有些程序会使用到 D-Bus (简体中文) 用户消息总线。 传统的方式是在生成桌面环境时,调用 dbus-launch 来启动。 但是,如果你准备运行一个依赖于D-Bus的systemd用户服务,你必须将D-Bus当成一个systemd服务启动起来,以保证依赖关系能够满足。

Note: 在即将到来的D-Bus到kdbus迁移中, systemd 仍然是系统和用户消息总线的管理者。

为了使之能正常工作,你需要:

1. 在/etc/systemd/user/中给dbus增加一个服务和socket单元

/etc/systemd/user/dbus.socket
[Unit]
Description=D-Bus Message Bus Socket
Before=sockets.target

[Socket]
ListenStream=%t/dbus/user_bus_socket

[Install]
WantedBy=default.target
/etc/systemd/user/dbus.service
[Unit]
Description=D-Bus Message Bus
Requires=dbus.socket

[Service]
ExecStart=/usr/bin/dbus-daemon --session --address=systemd: --nofork --nopidfile --systemd-activation
ExecReload=/usr/bin/dbus-send --print-reply --session --type=method_call --dest=org.freedesktop.DBus / org.freedesktop.DBus.ReloadConfig

2. 必须设置环境变量 DBUS_SESSION_BUS_ADDRESS。 可以通过 在user@.service.d/目录下新增配置文件来实现, 即创建以下文件:

/etc/systemd/system/user@.service.d/dbus.conf
[Service]
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%I/dbus/user_bus_socket
Note: 在systemd 209之前, DBUS_SESSION_BUS_ADDRESS 是由上游的 user@.service 单元来设置,但之后不会再这样做。 因为转换到kdbus后,这个变量将由 pam_systemd.so 来设置。

3. 在root用户下激活所有用户的socket:

# systemctl --global enable dbus.socket

环境变量

systemd用户实例不会继承类似.bashrc中定义的 环境变量。systemd实例有三种设置环境变量的方式:

  1. /etc/systemd/user.conf 中使用 DefaultEnvironment 选项。这个配置在所有的用户单元中可见。
  2. /etc/systemd/system/user@.service.d/ 下增加配置文件设置。 这个配置在所有的用户单元中可见。
  3. 在任何时候, 使用 systemctl --user set-environmentsystemctl --user import-environment. 对设置之后启动的所有用户单元有效,但已经启动的用户单元不会生效。

你可能需要设置的环境变量一般是 DISPLAY 或者 PATH

DISPLAY

任何一个X应用程序都需要使用Display来指示使用哪个显示器,如果你准备通过systemd单元运行X应用程序,你必须设置这个环境变量,例如:

/etc/systemd/user.conf
...
DefaultEnvironment=DISPLAY=:0 
...

PATH

通过 .bashrc 或者 .bash_profile 设置的环境变量,对systemd都是不可见的。 如果你改变了你的 PATH 变量,并且准备在systemd单元运行的应用中使用这个环境变量,你必须在systemd的环境中设置PATH。假设你在 .bash_profile 中设置了 PATH,让systemd感知到这个变化的最好方法是在修改PATH 之后,加入以下行通知systemd:

~/.bash_profile
systemctl --user import-environment PATH

随系统自动启动systemd用户实例

systemd用户实例在用户首次登陆时启动,并在最有一个会话退出时终止。 但有时候,对于一些不依赖于会话的用户进程,在系统启动时加载用户实例,在会话全部结束时,也不停止用户实例是比较有用的。Lingering 就是用来实现这个的。 使用以下命令来启用驻留指定用户:

# loginctl enable-linger username
Warning: systemd 服务是 没有 会话的, 它们在 logind 状态之外运行, 所以不要在 lingering 中启用自动登陆的功能,这会导致 会话中断

Xorg and systemd

There are several ways to run xorg within systemd units. Below there are two options, either by starting a new user session with an xorg process, or by launching xorg from a systemd user service.

Automatic login into Xorg without display manager

Tango-emblem-important.png

Tango-emblem-important.png

The factual accuracy of this article or section is disputed.

Reason: This setup ends up with two user D-Bus buses, one for the desktop, and an other for systemd. Why can't we use the systemd one alone? (Discuss in Talk:Systemd/User (简体中文)#)

This option will launch a system unit that will start a user session with an xorg server and then run the usual ~/.xinitrc to launch the window manager, etc.

You need to have #D-Bus correctly set up and xlogin-gitAUR installed.

Set up your xinitrc from the skeleton, so that it will source the files in /etc/X11/xinit/xinitrc.d/. Running your ~/.xinitrc should not return, so either have wait as the last command, or add exec to the last command that will be called and which should not return (your window manager, for instance).

The session will use its own dbus daemon, but various systemd utilities need the dbus.service instance. Possible solution to this is to create aliases for such commands:

~/.bashrc
for sd_cmd in systemctl systemd-analyze systemd-run; do
    alias $sd_cmd='DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/dbus/user_bus_socket" '$sd_cmd
done

Finally, enable (as root) the xlogin service for automatic login at boot:

# systemctl enable xlogin@username

The user session lives entirely inside a systemd scope and everything in the user session should work just fine.

Xorg as a systemd user service

Alternatively, xorg can be run from within a systemd user service. This is nice since other X-related units can be made to depend on xorg, etc, but on the other hand, it has some drawbacks explained below.

Since version 1.16 xorg-server provides better integration with systemd in two ways:

Unfortunately, to be able to run xorg in unprivileged mode, it needs to run inside a session. So, right now the handicap of running xorg as user service is that it must be run with root privileges (like before 1.16), and can't take advantage of the unprivileged mode introduced in 1.16.

Note: This is not a fundamental restriction imposed by logind, but the reason seems to be that xorg needs to know which session to take over, and right now it gets this information calling logind's GetSessionByPID using its own pid as argument. See this thread and xorg sources. It seems likely that xorg could be modified to get the session from the tty it is attaching to, and then it could run unprivileged from a user service outside a session.

This is how to launch xorg from a user service:

1. Make xorg run with root privileges and for any user, by editing /etc/X11/Xwrapper.config

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

2. Add the following units to ~/.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}"

where ${XDG_VTNR} is the virtual terminal where xorg will be launched, either hard-coded in the service unit, or set in the systemd environment with

$ systemctl --user set-environment XDG_VTNR=1
Note: xorg should be launched at the same virtual terminal where the user logged in. Otherwise logind will consider the session inactive.

3. Make sure to configure the DISPLAY environment variable as explained above.

4. Then, to enable socket activation for xorg on display 0 and tty 2 one would do:

$ 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

Now running any X application will launch xorg on virtual terminal 2 automatically.

The environment variable XDG_VTNR can be set in the systemd environment from .bash_profile, and then one could start any X application, including a window manager, as a systemd unit that depends on xorg@0.socket.

Warning: Currently running a window manager as a user service means it runs outside of a session with the problems this may bring: break the session. However, it seems that systemd developers intend to make something like this possible. See [3] and [4]

开发用户单元

例子

下面是 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

man systemd.unit的SPECIFIERS章节中,详细介绍了各种变量, %h 指示符将使用运行该服务的用户的主目录替代。更多的变量参考 systemd 的 manpages。

X应用程序须知

大多数X应用运行都需要 DISPLAY 变量 (这可能是你的服务无法启动的第一个原因). 如何让所有systemd用户实例看到这个环境变量,参考 #DISPLAY。另外一种选择是在单个的单元中设置这个变量,例子如下:

~/.config/systemd/user/parcellite.service
[Unit]
Description=Parcellite clipboard manager

[Service]
ExecStart=/usr/bin/parcellite
Environment=DISPLAY=:0

[Install]
WantedBy=mystuff.target

Some use cases

Persistent terminal multiplexer

Tango-dialog-warning.png

Tango-dialog-warning.png

This article or section is out of date.

Reason: References user-session@.service instead of user@.service; the latter does not contain Conflicts=getty@tty1.service. (Discuss in Talk:Systemd/User (简体中文)#)

You may wish your user session to default to running a terminal multiplexer, such as GNU Screen or Tmux, in the background rather than logging you into a window manager session. 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 with myStuff.target).

To create this type of user session, procede as above, but instead of creating wm.target, create 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

cruft.target, like mystuff.target above, should start anything you think should run before tmux or screen 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.

[Unit]
Description=tmux: A terminal multiplixer 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

Once this is done, systemctl --user enable tmux.service, multiplexer.target and any services you created to be run by cruft.target and you should be set to go! Activated user-session@.service as described above, but be sure to remove the Conflicts=getty@tty1.service from user-session@.service, since your user session will not be taking over a TTY. Congratulations! You have a running terminal multiplexer and some other useful programs ready to start at boot!

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
Note: The [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.

See also