openSUSE:打包 init 脚本
本文档描述了 SysV 样式的 Init 脚本的指南,用于在 openSUSE 包中进行使用和包含。它们符合 LSB 标准。对于 systemd 单元文件,请参阅 openSUSE:Systemd_packaging_guidelines。
名称
Init 脚本的名称必须符合 LSB 标准,因此必须在 http://www.lanana.org/lsbreg/init/init.txt 上列出。可以按照 http://www.lanana.org/lsbreg/instructions.html 中描述的方式注册新名称。
结构
Init 脚本的结构在 /etc/init.d/skeleton 中描述得很好。此文件也可以用作新 init 脚本的有效模板。
LSB 头部
Init 脚本是 shell 脚本,因此文件以通常的头部开始
#!/bin/sh
或
#!/bin/bash
然后通常会有注释。这些应该提及作者、版权或许可信息。该文件必须包含一个特殊的注释头部,提供有关 init 脚本的一些元信息。LSB 头部由注释限定,具体来说,头部开始标记为
### BEGIN INIT INFO
LSB 头部结束标记为
### END INIT INFO
所有 LSB 头部条目必须具有这些边界注释。
此示例取自 /etc/init.d/esound
# 1995-2002, 2008 SUSE Linux Products GmbH, Nuernberg, Germany. # All rights reserved. # # Author: Stanislav Brabec, feedback to http://www.suse.de/feedback # ### BEGIN INIT INFO # Provides: esound # Required-Start: alsasound $remote_fs # Should-Start: $network $portmap # Required-Stop: alsasound $remote_fs # Should-Stop: $network $portmap # Default-Start: 5 # Default-Stop: # Short-Description: Sound daemon with network support # Description: Starts esound server to allow remote access to sound # card. To use esound locally, you do not need to start this # server on boot. You should edit server settings before # starting it via sysconfig editor: Network/Sound/Esound ### END INIT INFO
LSB 头部由以下部分组成
# Provides: 行
LSB 头部中的 # Provides: 行列出了此服务提供的任何启动设施。其他服务可以在其 # Required-Start: 和 # Required-Stop: 行中引用这些启动设施。通常是守护进程的名称。如果多个包可以提供相同的设施(例如,sendmail 与 postfix、dhcpcd 与 dhclient),则两个 init 脚本都应提供相同的设施名称。
# Provides: boot_facility_1 [boot_facility_2...]
当使用 start 参数运行 init 脚本时,由 Provides 关键字指定的启动设施应被视为存在,因此需要这些启动设施的 init 脚本应稍后启动。当使用 stop 参数运行 init 脚本时,由 Provides 关键字指定的启动设施应被视为不再存在。
# Required-Start: 行
LSB 头部中的 # Required-Start: 行列出了此服务启动期间必须可用的任何启动设施。
# Required-Start: boot_facility_1 [boot_facility_2...]
如果 init 脚本不需要在启动之前需要其他启动设施,则此行是必需的,即使为空也必须使用 Required-Start!
# Required-Stop: 行
LSB 头部中的 # Required-Stop: 行列出了在关闭此服务之前不应停止的任何启动设施。
# Required-Stop: boot_facility_1 [boot_facility_2...]
如果 init 脚本不需要在启动之前需要其他启动设施,则此行是必需的,即使为空也必须使用 Required-Start!
# Should-Start: 行
LSB 头部中的 # Should-Start: 行列出了任何设施,如果存在,则应在启动此服务期间可用。目的是允许“可选”依赖项,如果未提供设施,则不会导致服务失败。
# Should-Start: boot_facility_1 [boot_facility_2...]
此行是可选的,如果 init 脚本不需要在启动之前启动其他可选依赖项,则应省略它。
# Should-Stop: 行
LSB 头部中的 # Should-Stop: 行列出了任何设施,如果存在,则应仅在关闭此服务后停止。目的是允许“可选”依赖项,如果未提供设施,则不会导致服务失败。
# Should-Stop: boot_facility_1 [boot_facility_2...]
如果 init 脚本不需要在关闭后阻止其他可选依赖项停止,则应省略此行。
LSB 允许定义特定于发行版的此头部的扩展。此类关键字以 X-VendorTag- 为前缀。以下关键字在 SUSE Linux 上可用
# X-Start-Before: 行
LSB 头部中的 # X-Start-Before: 行表示使用此关键字的脚本应在指定的设施之前启动。
# X-Start-Before: boot_facility_1 [boot_facility_2...]
此行是可选的,并且当前特定于供应商(openSUSE)。
# X-Stop-After: 行
LSB 头部中的 # X-Stop-After: 行表示使用此关键字的脚本应在指定的设施之后停止。
# X-Stop-After: boot_facility_1 [boot_facility_2...]
此行是可选的,并且当前特定于供应商(openSUSE)。
# X-Start-Before: 和 # X-Stop-After: 都允许脚本编写者在不修改其他例如系统 init 脚本的情况下使用依赖项。# Default-Start: 行
LSB 头部中的 # Default-Start: 行列出服务将默认启用的运行级别。这些运行级别用空格分隔。
# Default-Start: run_level_1 [run_level_2...]
每个需要默认在任何运行级别启动的 SystemV 样式的 init 脚本都必须在 LSB 头部包含此行。只有真正需要用于关键系统的服务才应在此处定义运行级别。如果服务不在任何运行级别默认启动,则应省略此行。例如,如果服务仅在运行级别 3、4 和 5 默认启动,则 init 脚本中的 LSB 头部将指定
# Default-Start: 3 4 5
# Default-Stop: 行
LSB 头部中的 # Default-Stop: 行列出服务将不会默认启动的运行级别。这些运行级别用空格分隔,并且必须包含未在 # Default-Start: 行中使用的所有数字运行级别。
# Default-Stop: run_level_1 [run_level_2...]
每个需要默认在任何运行级别启动的 SystemV 样式的 init 脚本都必须在 LSB 头部包含此行(如果存在 # Default-Start: 行,则也应存在 # Default-Stop: 行)。
例如,如果服务仅在运行级别 3、4 和 5 默认启动,则 LSB 头部中的 # Default-Stop: 行必须指定运行级别 0、1、2 和 6
# Default-Stop: 0 1 2 6
# Default-Stop: 被忽略,因为引导脚本概念使用 init.d(7) 手册页中描述的差异链接方案。# Short-Description: 行
LSB 头部中的 # Short-Description: 行提供 init 脚本操作的简要摘要。这不能超过单行 80 个字符的文本。
# Short-Description: This service is a mail server.
所有 openSUSE SysV 样式的 init 脚本必须在 LSB 头部包含 # Short-Description: 行。可以将其大致等同于 RPM 规范文件中的 Summary: 字段。它显示在 YaST 运行级别编辑器中,例如。
# Description: 行
LSB 头部中的 # Description: 行提供 init 脚本操作的更完整描述。它可以跨越多行,其中每行延续必须以 '#' 后跟制表符字符或 '#' 后跟至少两个空格字符开头。多行描述在不符合此标准的第一个行处终止。此信息也显示在 YaST 运行级别编辑器中。
示例
# Description: Bluetooth services for service discovery, authentication, # Human Interface Devices, etc.
所有 openSUSE SysV 样式的 init 脚本必须在 LSB 头部包含 # Description: 行。可以将其大致等同于 RPM 规范文件中的 %description 部分。
设施
如 LSB 所要求,SUSE Linux init 脚本已经提供了一些默认的设施名称。这些在 /etc/insserv.conf 中定义。目前,提供了以下系统设施名称
$local_fs— 所有本地文件系统都已挂载。大多数服务应该需要此功能。$remote_fs— 所有远程文件系统都已挂载。因为/usr可能是远程的,所以许多服务也应该需要此功能。$syslog— 系统日志设施已启动。$network— 低级网络(eth 卡等)已启动。$named— 可用主机名解析。$netdaemons— 所有网络守护进程正在运行。这在 LSB 1.2 中已被删除。目前为了向后兼容仍然可用。$time— 系统时间已正确设置。$portmap— SunRPC 端口映射服务可用。$null— 在Required-Stop和Should-Stop的情况下强制执行空依赖项,否则insserv假定与 Start 情况相同的依赖项。
除了在 /etc/insserv.conf 上 openSUSE 的 LSB 兼容系统设施之外,还已知以下系统设施
$all此设施表示服务应插入所有服务的末尾。显然,使用此设施的所有服务将被分组到一种启动顺序中。$null用于在# Should-Stop:和# Required-Stop:的情况下强制执行空依赖项,否则 insserv(8) 假定与# Should-Start:处指定的依赖项相同
分别在 # Required-Start: 处。
其他(非系统)设施可以在 LSB 头部中的 # Provides: 行中定义。
操作
请参阅 /etc/init.d/skeleton,其中包含示例代码并包含一些有价值的注释。以下示例取自 /etc/init.d/cron
case "$1" in
start)
echo -n "Starting CRON daemon"
startproc $CRON_BIN
rc_status -v
;;
stop)
echo -n "Shutting down CRON daemon"
killproc -TERM $CRON_BIN
rc_status -v
;;
try-restart)
$0 status >/dev/null && $0 restart
rc_status
;;
restart)
$0 stop
$0 start
rc_status
;;
force-reload)
echo -n "Reload service Cron"
checkproc $CRON_BIN
rc_status -v
;;
reload)
rc_status -v
;;
status)
echo -n "Checking for Cron: "
checkproc $CRON_BIN
rc_status -v
;;
probe)
;;
*)
echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}"
exit 1
;;
esac
如 LSB 所定义,所有 init 脚本都应知道如何处理以下参数
start— 启动服务。stop— 停止服务。restart— 如果服务正在运行,则停止并重新启动该服务。否则,启动服务。reload— 在不实际停止和重新启动服务的情况下,导致重新加载服务的配置。force-reload— 如果服务支持,则导致重新加载配置。否则,将重新启动该服务。status— 打印服务的当前状态。usage- 默认情况下,如果 init 脚本没有任何操作运行,则应列出“用法消息”,其中包含所有操作(用于使用)
start、stop、restart、force-reload 和状态操作必须被所有 init 脚本支持。reload 操作是可选的。
SUSE 定义了以下附加操作
- try-restart — 仅当服务之前处于活动状态时才重新启动该服务。此操作现在是 LSB 的一部分(截至 1.9)。Red Hat 有一个类似的名为 condrestart 的操作。
- probe — 探测是否需要重新加载。根据服务,如果需要重新加载,则打印 "reload" 或 "restart"。如果不需要重新加载,则不打印任何内容。此操作是可选的,并且不是 LSB 的一部分(截至 1.9)。
SystemV 样式的 init 脚本应支持 try-restart 和 condrestart 操作。这两个操作旨在服务于相同的目的,并且行为不得不同。事实上,强烈建议打包者将 try-restart 和 condrestart 作为 case 语句中的等效选项来实现
try-restart|condrestart)
$0 status
if test $? = 0; then
$0 restart
else
rc_reset
fi
rc_status
;;
您不受禁止添加其他命令,但应将所有您打算交互使用过的命令列出到 usage 消息中。
退出状态码
参考:请参阅 LSB 规范。它定义了 init 脚本的以下退出状态码(状态操作除外,见下文)
| 退出状态码 | 描述 |
|---|---|
| 0 | 成功 |
| 1 | 通用或未指定的错误 |
| 2 | 无效或过多的参数 |
| 3 | 未实现的功能(例如,“reload”) |
| 4 | 用户权限不足 |
| 5 | 程序未安装 |
| 6 | 程序未配置 |
| 7 | 程序未运行 |
| 8-99 | 保留供未来 LSB 使用 |
| 100-149 | 保留供发行版使用 |
| 150-199 | 保留供应用程序使用 |
| 200-254 | 保留 |
对于所有其他 init 脚本操作,如果操作成功,init 脚本必须返回零的退出状态。除了直接成功之外,还应考虑以下情况为成功
- 使用 force-reload 参数重新启动服务
- 对已经运行的服务运行 start
- 对已经停止或未运行的服务运行 stop
- 对已经停止或未运行的服务运行 restart
- 对已经停止或未运行的服务运行 condrestart 或 try-restart
状态函数
LSB 定义了状态操作的以下退出代码
| 退出状态码 | 描述 |
|---|---|
| 0 | 程序正在运行或服务正常 |
| 1 | 程序已死并且 /var/run pid 文件存在 |
| 2 | 程序已死并且 /var/lock lock 文件存在 |
| 3 | 程序未运行 |
| 4 | 程序或服务状态未知 |
| 5-99 | 保留供未来 LSB 使用 |
| 100-149 | 保留供发行版使用 |
| 150-199 | 保留供应用程序使用 |
| 200-254 | 保留 |
在 /etc/rc.status 中定义的函数有助于记录、显示和返回 init 脚本中的实际 rc 状态信息。它们可以通过以下方式被 init 脚本引用
. /etc/rc.status
以下函数可用
rc_active
此函数检查服务是否已启用(通过符号链接)。如果服务在某个运行级别中已启用,则返回“0”,否则返回“1”。
rc_exit
此函数使用适当的 rc 状态终止 init 脚本。
rc_failed [num]
此函数将本地和全局 rc 状态设置为参数 num 定义的值。默认情况下,使用“1”作为值。
rc_check
此函数检查最后一个命令 ($?) 的退出状态,如果该值不同于“0”,则将本地 rc 状态设置为此值。然后,如果它与“0”不同,则将全局 rc 状态设置为本地 rc 状态的值。此函数由其他 rc 状态函数内部使用。
rc_reset
此函数将本地和全局 rc 状态都设置为“0”。
rc_status [-r] [-s] [-u] [-v[num]]
此函数检查、设置和显示 rc 状态。默认情况下,它是静默的:它仅调用 rc_check。因此,必须使用一个选项来显示状态来调用它。选项的含义如下
-r调用 rc_reset。此选项可以与-v一起使用。命令 rc_status -v -r 检查、设置和显示当前的 rc 状态。然后调用 rc_reset。-s显示“skipped”并将状态设置为“3”。这意味着未实现的功能。-u显示“unused”并将状态设置为“3”。这意味着未实现的功能。-v[num] 显示实际状态并将本地状态重置为“0”。默认情况下,状态显示在实际行上。参数num定义了它应该在实际光标位置上方显示num行。
安装
init 脚本通常包含在软件包源代码中,以及一个额外的源文件。它安装在 %install 部分中。通常,init 脚本不应标记为 %config 文件。
虽然 init 文件位于 /etc 中,但它们是需要执行的脚本,而不是需要配置的脚本。任何配置都应该通过 /etc/sysconfig/
Init 脚本需要具有 0755 或 0700 权限。
也可能存在一个名为 rcname 的符号链接,指向 /etc/init.d/name。该符号链接位于 /sbin 或 /usr/sbin 中,具体取决于安装服务的目录前缀。它对于可以通过手动启动、停止、重新启动的 init 脚本很有用。
最后,在软件包安装后可以启用 init 脚本,并在软件包删除后禁用 init 脚本。在更新后应重新启动该服务,并在删除软件包之前应停止该服务。宏 %fillup_and_insserv、%insserv_force_if_yast、%restart_on_update、%insserv_cleanup 和 %stop_on_removal 旨在用于此目的。
请注意,SUSE Linux 提供了实用程序 insserv 来启用或禁用 init 脚本。有关更多详细信息,请参阅 insserv(8) 的手册页。
SUSE Linux 上的 init 脚本默认情况下是禁用的,除非那些对于最小系统功能是必要的。因此,%fillup_and_insserv 和 %insserv_force_if_yast 宏仅在提供此类基本服务的软件包中使用。
Spec 文件示例
...
Requires(pre): %insserv_prereq %fillup_prereq
...
%install
...
install -D -m 755 service.init %{buildroot}%{_initrddir}/service
...
mkdir -p %{buildroot}%{_sbindir}
ln -sf %{_initrddir}/service %{buildroot}%{_sbindir}/rcservice
...
%post
%fillup_and_insserv service
%preun
%stop_on_removal service
%postun
%restart_on_update service
%insserv_cleanup
...
%files
%defattr(-,root,root)
...
%{_initrddir}/service
%{_sbindir}/rcservice
...
如果 init 脚本需要启用、重新启动或停止,则必须将 init 脚本的名称列为相关宏的参数。唯一的区别是宏 %insserv_cleanup。它不需要任何参数,因为它无论如何都会删除所有悬挂的符号链接。
具有 SystemV 样式 init 脚本的软件包必须将它们放入 /etc/init.d 中。有一个 rpm 宏用于此目录,%_initrddir。
LSB 实际如何在 openSUSE 上工作
基于 LSB 的系统使用 /usr/lib/lsb/install_initd 进行脚本激活,并使用 /usr/lib/lsb/remove_initd 进行停用。当这些任务发生时,将读取 LSB 依赖项,然后调整脚本的启动和停止优先级以满足这些依赖项。
这意味着 LSB 标头依赖项受到尊重(尽管采用静态机制)。对于 LSB 标头中发现的覆盖依赖项,可以使用系统工具 insserv(8),有关如何执行此操作的更多说明,应阅读 insserv(8) 的手册页。
初始化环境变量
由于 init 脚本可能由系统管理员使用非标准环境变量值(例如 PATH、USER、LOGNAME 等)手动运行,因此 init 脚本不应依赖这些环境变量的值。如果需要它们,则应将它们设置为已知/默认值。
Init 脚本必须表现良好
SystemV 样式的 init 脚本必须在服务已经运行时启动或服务未运行时停止时表现良好。它们不得因为正常操作而杀死无关的(但可能名称相似的)用户进程。实现此目的的最佳方法是使用 /etc/rc.status 提供的 init 脚本函数
# Source function library. . /etc/rc.status rc_reset
以及系统工具 /sbin/start_daemon 或 /sbin/startproc 用于启动进程,/sbin/killproc 用于停止进程,以及 /sbin/checkproc 用于检查正在运行的进程。有关更多说明,应查阅 start_daemon(8) 或 startproc(8)、killproc(8) 和 checkproc(8) 的手册页。
startproc 命令启动由可执行文件路径名称标识的进程。退出状态是 LSB,请参阅 “Exit Status Codes” 以获取更多详细信息。它可以被 LSB 命令 start_daemon 替换,后者在启动可执行文件之前不会 fork。这要求新进程本身 fork 并断开与终端的连接(请参阅手册页 startproc(8)、daemon(3)、fork(2) 和 setsid(2))。LSB 命令 killproc 将信号发送到由可执行文件的完整路径名称标识的进程。(请参阅手册页 killproc(8))。LSB 命令 pidofproc 使用基本名称来查找进程,而 checkproc 命令使用可执行文件的完整路径来执行相同的操作(请参阅手册页 pidofproc(8))。
然后通常会检查服务是否正确安装以及相关的 sysconfig 文件是否被读取。如果出现问题,必须返回符合 LSB 的错误值。有关更多详细信息,请参阅 “Exit Status Codes”。
此示例取自 /etc/init.d/ypbind
YPBIND_BIN=/usr/sbin/ypbind
test -x $YPBIND_BIN || { echo "$YPBIND_BIN not installed";
if [ "$1" = "stop" ]; then exit 0; else exit 5; fi; }
YPBIND_CONFIG=/etc/sysconfig/ypbind
test -r $YPBIND_CONFIG || { echo "$YPBIND_CONFIG not existing";
if [ "$1" = "stop" ]; then exit 0; else exit 6; fi; }
# Read config
. $YPBIND_CONFIG
如果服务自动重新加载其配置(例如,cron),则 init 脚本的 reload 操作必须表现为配置已成功重新加载。restart、condrestart、try-restart、reload 和 force-reload 操作可能是原子的;也就是说,如果已知服务在重新启动或重新加载后未运行,则脚本可以在没有任何进一步操作的情况下返回错误。
SystemV init 脚本的系统工具
start_daemon(8) 和 startproc(8) 工具
- start_daemon 如果尚未运行,则启动守护程序。
- startproc 如果尚未运行,则将进程作为守护程序启动。
start_daemon [-fLve] [-n +/-<prio>] [-u user] [-g group] [-l log_file|-q|-d] [-p pid_file]
[-i ignore_file][-c root]/path/to/executable [arguments for executable]
startproc [-fLves] [[-n ]+/-<prio>] [-(t|T) <sec>] [-u user] [-g group] [-l log_file|-q|-d] [-p pid_file]
[-i ignore_file] [-c root] /path/to/executable [arguments for executable]
killproc(8) 工具
向程序发送信号;默认情况下,它发送 SIGTERM,如果进程未死,则几秒钟后发送 SIGKILL。它还会尝试删除找到的 pid 文件。
killproc [-vqLN] [-g|-G] [-p pid_file] [-i ingnore_file] [-c root] [-t <sec>] [-<SIG>] /full/path/to/executable killproc -n [-vq] [-g|-G] [-t <sec>] [-<SIG>] name_of_kernel_thread killproc [-vq] [-g|-G] [-t <sec>] [-<SIG>] basename_of_executable
checkproc(8) 和 pidofproc(8) 工具
尝试查找程序的状态和 pid;检查可能的 pid 文件。主要是 pidofproc 是 checkproc 的详细版本。
checkproc [-vLkNz] [-p pid_file] [-i ingnore_file] [-c root] /full/path/to/executable checkproc -n [-vk] name_of_kernel_thread checkproc [-vk] basename_of_executable pidofproc [-LkNz] [-p pid_file] [-i ingnore_file] [-c root] /full/path/to/executable pidofproc -n [-k] name_of_kernel_thread pidofproc [-k] basename_of_executable
LSB shell 函数
上述系统工具也将由位于 /lib/lsb/init-functions 中的 shell 函数提供
# Load the LSB shell functions . /lib/lsb/init-functions
以及 LSB 3.1 及更高版本指定的语法
start_daemon [-f] [-n +/-<prio>] /path/to/executable [arguments for executable] killproc [-p pid_file] /full/path/to/executable [-<SIG>] pidofproc [-p pid_file] /full/path/to/executable