systemd/用户 - Arch Linux 中文维基

跳转到内容

# systemd/用户

来自 Arch Linux 中文维基
< Systemd

翻译状态：

- 本文（或部分内容）译自 Systemd/User，最近一次同步于 2023-11-20，若英文版本有所更改，则您可以帮助同步与翻译更改的内容。
- 您可以在 ArchWiki 的对应页面找到本文翻译的原始修订历史。
- 本文可能与英文原文存在出入。

相关文章

- systemd
- Automatic login to virtual console
- Start X at login

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) § 用户服务管理器管理的单元.

注意：

- `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 用户实例有三种设置环境变量的方式：

1. 对于有 `$HOME` 目录的用户，可以在 `~/.config/environment.d/` 目录中新建一个".conf"文件，然后在其中写入格式为`NAME=VAL`这样的行。这些设置只对指定用户的用户单元有效 。更多信息可以参考 environment.d(5) 。
2. 使用 `/etc/systemd/user.conf` 文件中的 `DefaultEnvironment` 选项。这个配置在所有的用户单元中可见。
3. 在 `/etc/systemd/system/user@.service.d/` 下增加配置文件设置。 这个配置在所有的用户单元中可见。
4. 在任何时候， 使用 `systemctl --user set-environment` 或 `systemctl --user import-environment`. 对设置环境变量之后启动的所有用户单元有效，但已经启动的用户单元不会生效。
5. 使用由 dbus提供的 `dbus-update-activation-environment --systemd --all` 命令。和`systemctl --user import-environment`有同样的效果，但是会影响D-Bus会话。你可以把这个添加到shell初始化文件的末尾。
6. 对于用户环境的“全局”环境变量，可以使用会被某些生成器解析的`environment.d` 目录。 更多信息可以参考environment.d(5)和 systemd.generator(7)。
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 服务是 没有 会话的， 它们在 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

### 使用变量的例子

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

注意：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

这篇文章的某些内容需要扩充。

原因：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-gitAUR，并按照 Xinit#xinitrc 来设置好你的 xinitrc。

会话会使用它自己的 dbus 守护，而需要用到 `dbus.service` 的 systemd 工具会自动连接到会话的 dbus 实例上。最后启用 `xlogin@username` 服务来达成开机自动登录。 整个用户会话都在 systemd 的作用域下运行，会话内的一切都能正常工作。

### 将 Xorg 作为 systemd 用户服务

另外一种选择是将 xorg 作为一个 systemd 用户服务。这是一种不错的方案，因为其他的 X-related units 可以依赖于 xorg 服务。 但另一方面，这个方案存在某些倒退，这在下面会提到。

xorg-server包 提供了两种整合到 systemd 的方法:

- 可以在无特权模式下运行，设备管理由 logind 代为管理(参考 Hans de Goede 的这个提交)。
- 可以实现通过 socket 激活服务 (参考这个提交)。

但非常不幸，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 的步骤：

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

注意：xorg应该在用户登录的虚拟终端上运行，否则 logind 会认为会话没有激活。

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`。

警告：当前，通过用户服务启动窗口管理器意味着它是在会话之外运行的，这将带来以下问题: 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` 来使它在登录时启动。推荐采用启用服务的方法来代替手动创建软链接。

### 终端复用器持久化

比起默认登入一个窗口管理器，你可能更想要自动在后台运行一个终端复用器（比如 GNU 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

启用 `tmux.service`、`multiplexer.target` 和由 `cruft.target` 运行的服务，像往常一样启动 `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

分类：​

- 系统管理

隐藏分类：​

- 使用“模板:准确性”标记的页面或章节

systemd/用户
添加话题