如何在 shell 脚本中确定它是否被 systemd 调用?

Jef*_*ler 24 shell-script systemd

我们有一个 shell 脚本——出于各种原因——包装供应商的应用程序。我们有系统管理员和应用程序所有者,他们对 systemd 的熟悉程度参差不齐。因此,在应用程序失败的情况下(systemctl 表示同样多),一些最终用户(包括“root”系统管理员)可能会使用包装脚本“直接”启动应用程序,而不是使用systemctl restart. 这可能会在重新启动期间导致问题,因为 systemd 不会调用正确的关闭脚本——因为就其而言,应用程序已经停止。

为了帮助指导向 systemd 的过渡,我想更新包装器脚本以确定它是由 systemd 还是由最终用户调用;如果它systemd之外被调用,我想向调用者打印一条消息,告诉他们使用 systemctl。

如何在 shell 脚本中确定它是否被 systemd 调用?

你可以假设:

  • 包装脚本的 bash shell
  • 包装脚本成功启动和停止应用程序
  • systemd 服务按预期工作

systemd 服务的一个例子可能是:

[Unit]
Description=Vendor's Application 
After=network-online.target

[Service]
ExecStart=/path/to/wrapper start
ExecStop=/path/to/wrapper stop
Type=forking

[Install]
WantedBy=multi-user.target
Run Code Online (Sandbox Code Playgroud)

我对检测 init system不感兴趣,因为我已经知道它是 systemd。

Jef*_*ler 25

来自Lucas Werkmeister对 Server Fault的翔实回答

  • 在 systemd 版本 231 及更高版本中,有一个 JOURNAL_STREAM 变量设置为 stdout 或 stderr 连接到日志的服务。
  • 对于 systemd 232 及更高版本,设置了一个 INVOCATION_ID 变量。

如果您不想依赖这些变量,或者对于 231 之前的 systemd 版本,您可以检查父 PID 是否等于 1:

if [[ $PPID -ne 1 ]]
then
  echo "Don't call me directly; instead, call 'systemctl start/stop service-name'"
  exit 1
fi >&2
Run Code Online (Sandbox Code Playgroud)


Cha*_*ffy 13

简短的回答

if ! grep -qEe '[.]service$' /proc/self/cgroup; then
    echo "This script should be started with systemctl" >&2
    exit 1
fi
Run Code Online (Sandbox Code Playgroud)

...或者,如果您知道您希望运行的特定服务名称,并且希望对阻止创建用户会话的错误配置保持稳健:

if ! grep -qEe '/myservice[.]service$' /proc/self/cgroup; then
    echo "This service should be started with systemctl start myservice" >&2
    exit 1
fi
Run Code Online (Sandbox Code Playgroud)

为什么有效

确定哪个服务(如果有)启动了当前进程的一种方法是检查/proc/self/cgroup。对于 -systemd触发的服务,这将包含服务名称;例如:

12:pids:/system.slice/dhcpcd.service
11:rdma:/
10:memory:/system.slice/dhcpcd.service
9:blkio:/system.slice/dhcpcd.service
8:devices:/system.slice/dhcpcd.service
7:hugetlb:/
6:cpuset:/
5:freezer:/
4:cpu,cpuacct:/system.slice/dhcpcd.service
3:net_cls,net_prio:/
2:perf_event:/
1:name=systemd:/system.slice/dhcpcd.service
0::/system.slice/dhcpcd.service
Run Code Online (Sandbox Code Playgroud)

...而对于与用户会话关联的进程,cgroup 将更像/user.slice/user-1000.slice/session-337.scope(假设这是自上次重新启动以来系统上第 337 个会话的用户,该用户具有 UID 1000)。


更高级的实现

如果想要检测正在运行的特定服务,也可以从/proc/self/cgroup. 考虑,例如:

cgroup_full=$(awk -F: '$1 == 0 { print $3 }' /proc/self/cgroup)
cgroup_short=${cgroup_full##*/}
case $cgroup_full in
  /system.slice/*.service) echo "Run from system service ${cgroup_short%.*}";;
  /user.slice/*.service)   echo "Run from user service ${cgroup_short%.*}";;
  *.service)               echo "Service ${cgroup_short%.*} type unknown";;
  *)                       echo "Not run from a systemd service; in $cgroup_full";;
esac
Run Code Online (Sandbox Code Playgroud)


ilk*_*chu 7

想到的另一个明显的解决方案是添加类似

Environment=FROM_SYSTEMD=1
Run Code Online (Sandbox Code Playgroud)

到服务文件,并在该 envvar 上进行测试。

  • @JeffSchaller,如果您从包装器脚本中得到一个错误,告诉您运行 `systemctl`,这样做比通过脚本找出要设置的 envvar 容易得多。另一方面,如果您有具有 root 访问权限的共同管理员,他们积极违反指令……好吧,那么您无能为力。 (5认同)

wyr*_*yrm 5

我喜欢 Jeff Schaller 的回答,这可能是 The Right Thing™。另一种方法是使用两个脚本。将实际包装器从/path/to/wrapper其他文件名移至其他文件名,并在 systemd 单元文件中使用该名称。然后使用原始名称创建另一个脚本,该脚本只显示有用的错误消息。