0%

使用Systemd实现以用户身份开机自动执行脚本

需要开机自动执行的脚本

#!/bin/bash
# rustdocs.sh
# 别忘记给它可执行权限
/home/marly/.cargo/bin/mdbook serve -n 0.0.0.0 -p 3000 /home/marly/Documents/rust/trpl-zh-cn&
/home/marly/.cargo/bin/mdbook serve -n 0.0.0.0 -p 4000 /home/marly/Documents/rust/rust-by-example-cn&

第一行如果末尾不加”&”,第二句命令就不会执行。

另外脚本启动发生在marly登录bash之前,所以类似.bash_profile中自定义的$PATH无效,需要使用绝对路径。

systemd服务

[Unit]
Description=Start rust doc service on boot
Requires=network-online.target
Documentation=man:rustc

[Service]
User=marly
Type=forking
ExecStart=/home/marly/.local/bin/rustdocs.sh

[Install]
WantedBy=multi-user.target

把它命名为 rustdoc-start.service 即可,必须以.service结尾。

启动服务

将上述service文件放置在/etc/systemd/system/usr/lib/systemd/system/中,然后执行sudo systemctl enable rustdoc-start.service。完毕后重启设备观察效果,如无意外mdbook服务会自动启动。

注意sudo systemctl enable rustdoc-start.service的细节。此步骤在/etc/systemd/system/multi-user.target.wants创建了指向rustdoc-start.service的同名符号链接。

分析

上面的User,Type,ExecStart,WantedBy是重点。User=marly即以用户marly身份执行,ExecStart为所执行命令,本例中即刚才编写的脚本路径。WantedBy决定了服务启动时机,multi-user.target对应的系统状态大致为: (参考自 Why do most systemd examples contain WantedBy=multi-user.target?)

multi-user.target normally defines a system state where all network services are started up and the system will accept logins, but a local GUI is not started. This is the typical default system state for server systems, which might be rack-mounted headless systems in a remote server room.

所有网络服务已启动,系统已经做好接受登录的准备,但是GUI还未启动。对于无头服务器而言,这是默认的系统状态。

本例需要对外提供网络服务,所以WantedBy=multi-user.target是合适的。

最后是Type,有simple, exec, forking, oneshot, dbus, notify, idle几种类型。借用下金步国先生的翻译

如果设为 forking ,那么表示 ExecStart= 进程将会在启动过程中使用 fork() 系统调用。 也就是当所有通信渠道都已建好、启动亦已成功之后,父进程将会退出,而子进程将作为主服务进程继续运行。 这是传统UNIX守护进程的经典做法。 在这种情况下,systemd 会认为在父进程退出之后,该服务就已经启动完成。 如果使用了此种类型,那么建议同时设置 PIDFile= 选项,以帮助 systemd 准确可靠的定位该服务的主进程。 systemd 将会在父进程退出之后 立即开始启动后继单元。

个人理解,执行rustdocs.sh本身创建了一个进程,其中的两条mdbook命令又创建了2个子进程。只有2个子进程存续,我们的rust doc服务才能访问。所以oneshot,simple等都不行。

简单验证一下猜想:

$ ps x | grep rust
    403 ?        Sl     0:05 /home/marly/.cargo/bin/mdbook serve -n 0.0.0.0 -p 3000 /home/marly/Documents/rust/trpl-zh-cn
    404 ?        Sl     0:05 /home/marly/.cargo/bin/mdbook serve -n 0.0.0.0 -p 4000 /home/marly/Documents/rust/rust-by-example-cn
  63046 pts/0    S+     0:00 grep rust

验证成功。再顺便看看进程的owner:

$ stat -c '%U' /proc/403 /proc/404
marly
marly

符合预期。