Published on

How To Start Tmux as Systemd User Service

Authors
  • avatar
    Name
    ttyS3
    Twitter

tmux 启动一下能有多慢?答案是,挺快的。 那为什么要有这种需求? 原因是布局恢复。 老灯目前使用的是一个tpm插件,名叫 tmux-resurrect, prefix + ctrl + r 即可自动恢复所有panel.

但是有个小问题,panel比较多,比如有10多个的情况下,启动tmux还是会小闪一下的,整个过程肉眼可见,

会造成操作上的停顿。比如你打开 Gnome terminal 马上就能执行命令了,但是你刚开机启动tmux,你得等几秒才能操作,就因为这个panel恢复。

怎么样加快这个过程?答案当然是,用户登录的时候就马上运行tmux呗。

这个比较简单,直接看 ArchLinux 的 wiki 整个 systemd unit 文件就好了。

不过 Arch 的 wiki 有个小错误。

/etc/systemd/system/[email protected]

[Unit]
Description=Start tmux in detached session

[Service]
Type=forking
User=%I
# 这里 Arch wiki 原来写的是 %u, 应该修正为 %i
ExecStart=/usr/bin/tmux new-session -s %i -d
ExecStop=/usr/bin/tmux kill-session -t %i

[Install]
WantedBy=multi-user.target

要使用 %i(表示 instance, 即 tmux@xxx.service 里的 xxx) 而不是%u

的原因是: %u 的值并不会被 User= 配置所影响,它表示的是 the name of the user running the service manager instance , 由于 Arch wiki 里用的是system级的unit文件,因此实际上用户名是 root

SpecifierMeaningDetails
"%u"User nameThis is the name of the user running the service manager instance. In case of the system manager this resolves to "root". Note that this setting is not influenced by the User= setting configurable in the [Service] section of the service unit.
"%i"Instance nameFor instantiated units this is the string between the first "@" character and the type suffix. Empty for non-instantiated units.
"%I"Unescaped instance nameSame as "%i", but with escaping undone.

老灯还是觉得 user service 在这种场景下更为合适,因此没有采用 system 级别的 service方案。

我的 mytmux.service 内容如下:

# put this file to ~/.config/systemd/user/mytmux.service
# enable the service: systemctl --user enable mytmux
# ref https://wiki.archlinux.org/index.php/Tmux#Autostart_with_systemd
[Unit]
Description=Start tmux in detached session

[Service]
Type=forking

# comment here, use zsh preexec instead
# below must changed per host/user
# Environment=DISPLAY=:1
# Environment=XAUTHORITY=/run/user/1000/gdm/Xauthority

ExecStart=/usr/bin/tmux new-session -s %u -d
ExecStop=/usr/bin/tmux kill-session -t %u

Restart=on-failure
RestartSec=3s

[Install]
WantedBy=default.target

为方便安装,整了个 Makefile:

install:
    -systemctl --user stop mytmux 2>/dev/null
    install -vD -m 755 ./mytmux.service ~/.config/systemd/user/mytmux.service
    systemctl --user daemon-reload
    systemctl --user enable --now mytmux
    systemctl --user status mytmux
    journalctl --user -a -f -u mytmux
.PHONY: install

troubleshooting

1. DISPLAY 环境变量没有被设置

这里指的是从 tmux-resurrect 自动恢复的panel里的zsh进程没有获取到 DISPLAY 环境变量,但是如果你在那个panel使用tmux showenv查看,却发现tmux本身是获取到了这个变量的,只是 zsh 没有获取到。

猜测应该是 user service 启动的时候,x11 其实并没有完成启动。而 tmux 自身之所以能获取到 DISPLAY 的值,是因为它会自动刷新。(如果不刷新,可以通过设置 set -g update-environment 'DISPLAY SSH_ASKPASS SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY TERM' 修复 )

几经折腾后,在这里找到了答案。在.zshrc 中加上 preexec hook 即可:

if [ -n "$TMUX" ]; then
  function refresh_env_var_from_systemd_started_tmux {
    export $(tmux show-environment | grep "^DISPLAY")
    export $(tmux show-environment | grep "^XAUTHORITY")
  }
else
  function refresh_env_var_from_systemd_started_tmux { }
fi

function preexec {
  refresh_env_var_from_systemd_started_tmux
}

注意这里一定要用 preexec hook,直接执行 refresh_env_var_from_systemd_started_tmux 是没有效果的,

因为执行的时候,DISPLAY 还是空的呢。

另外有一点不要误解的就是,set-option -g update-environment 这里的update是针对 tmux 进程自身的,

而不是通过 tmux 启动的 shell (比如 zsh).

还有一种比较笨的方法就是直接在 systemd unit 文件中指定这些环境变量的值,比如:

Environment=DISPLAY=:1
Environment=XAUTHORITY=/run/user/1000/gdm/Xauthority

为什么需要这个?有些程序(一般是x11 client)的运行是要依赖DISPLAY这个环境变量的,比如 https://github.com/suxpert/vimcaps 这个vim插件, 里面有个c lib, 初始化的时候就会调用 XOpenDisplay

2. tmux.service user service 重启后被奇怪地禁用了

这个其实是老灯装了一个叫 tmux-continuum 的tpm插件的锅。这个问题确实坑了我很久。

我怎么想到这一点的?我刚开始并没想到,并且重启了n次,折腾了好久,被这货害惨了。

后面我把 tmux.service 改成了 mytmux.service 发现问题神奇的消失了。

症状就是: systemctl --user enable tmux 后,明明服务成功运行了,检查 ~/.config/systemd/user/default.target.wants/tmux.service 也是存在的,说明确实成功enable了。

一重启,发现~/.config/systemd/user/default.target.wants/tmux.service 已经消失了。服务自动是被禁用状态了。

tmux-continuum/scripts/variables.sh 中有定义: systemd_service_name="tmux.service"

然后这货在tmux启动时会执行 systemctl --user disable ${systemd_service_name}

tmux-continuum/scripts/handle_tmux_automatic_start.sh

is_tmux_automatic_start_enabled() {
        local auto_start_value="$(get_tmux_option "$auto_start_option" "$auto_start_default")"
        [ "$auto_start_value" == "on" ]
}
is_osx() {
        [ $(uname) == "Darwin" ]
}

is_systemd() {
        [ $(ps -o comm= -p1) == 'systemd' ]
}

main() {
        if is_tmux_automatic_start_enabled; then
                if is_osx; then
                        "$CURRENT_DIR/handle_tmux_automatic_start/osx_enable.sh"
                elif is_systemd; then
                        "$CURRENT_DIR/handle_tmux_automatic_start/systemd_enable.sh"
                fi
        else
                if is_osx; then
                        "$CURRENT_DIR/handle_tmux_automatic_start/osx_disable.sh"
                elif is_systemd; then
                        "$CURRENT_DIR/handle_tmux_automatic_start/systemd_disable.sh"
                fi
        fi
}
main

默认配置是这样的:

auto_start_option="@continuum-boot"
auto_start_default="off"

所以每次重启后就执行了"$CURRENT_DIR/handle_tmux_automatic_start/systemd_disable.sh"

systemctl --user disable tmux.service

Refs

https://github.com/tmux-plugins/tmux-resurrect

https://github.com/tmux-plugins/tmux-continuum

https://wiki.archlinux.org/index.php/tmux#Autostart_with_systemd

https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Specifiers

https://babushk.in/posts/renew-environment-tmux.html

https://goosebearingbashshell.github.io/2017/12/07/reset-display-variable-in-tmux.html