Debian包systemd服务处理
要确保在 postinst 脚本中正确地启用或禁用 systemd 服务,并且避免依赖一个可能尚未运行的系统,关键在于避免直接使用 systemctl,而是使用 Debian 为此提供的专用工具:deb-systemd-helper。
🎯 为什么不能直接使用 systemctl?
直接在 postinst 脚本里调用 systemctl enable,可能会遇到问题,尤其是在 chroot 环境或容器中执行 dpkg 时,因为那时 systemd 用户实例可能并未运行,调用可能会失败。
✅ 推荐的打包工具:dh_installsystemd
作为开发者,最佳实践是使用 dh_installsystemd 命令(来自 debhelper 软件包)来帮你自动生成包含 deb-systemd-helper 调用的维护者脚本(postinst, prerm, postrm)。这不仅步骤简单,而且能天然兼容各种部署环境:
-
在
debian/目录下准备好你的*.service文件。 -
在
debian/rules文件的override_dh_auto_install或override_dh_install段中,确保dh_installsystemd命令被调用。
完成后,dh_installsystemd 会自动安装单元文件,并为你的维护者脚本生成处理启用、启动、停止等标准操作的代码片段。你还可以通过传入参数来控制具体行为,例如 --no-start 可生成"只启用不启动"的代码。
🛠️ 手动编写维护者脚本核心函数
如果需要手动处理,核心思路是在维护者脚本中封装对 deb-systemd-helper 的调用。下表总结了在软件包生命周期的不同阶段,各个维护者脚本的核心任务:
| 维护者脚本 | 执行阶段 | 核心任务 |
|---|---|---|
postinst |
配置后 | 启用与启动 :调用 deb-systemd-helper enable 或 systemctl(使用 ` |
prerm |
移除前 | 停止与禁用 :systemctl stop 和 deb-systemd-helper disable(避免卸载时残留启用链接)。 |
postrm |
移除后(清除时) | 清除状态 :当 "purge" 时,调用 deb-systemd-helper update-state 清除其辅助状态。 |
💡 通用兼容性代码示例
如果需要手动编写,可以采用下面这种结构,以实现最大兼容性:
bash
# 在 postinst 中启用服务
if [ -d /run/systemd/system ]; then
if [ "$1" = "configure" ]; then
deb-systemd-helper enable my_service.service || true
# 可选:使用 systemctl 启动,但最好由管理员操作
# systemctl start my_service.service || true
fi
fi
而对于卸载过程(prerm 和 postrm),则应遵循以下模式:
bash
# 在 prerm 中禁用服务
if [ -d /run/systemd/system ] && [ "$1" = "remove" ]; then
deb-systemd-helper disable my_service.service || true
systemctl stop my_service.service || true
fi
bash
# 在 postrm 中清除状态
if [ -d /run/systemd/system ] && [ "$1" = "purge" ]; then
deb-systemd-helper update-state my_service.service || true
fi
这里的 [ -d /run/systemd/system ] 是一个很重要的检查,它用于判断 systemd 是否正在运行,从而安全地执行相关操作,保障代码在非 systemd 环境下的健壮性。
💎 总结
总而言之,处理这类问题的最稳妥、最专业的方式 是依赖 debhelper 工具,而不是在维护者脚本中手动编写大量逻辑。这样做既能保证软件包行为符合预期,又能最大限度兼容不同的底层系统。
deb-systemd-helper命令详解
deb-systemd-helper 是 Debian 系统内部使用的一个工具,它的设计初衷不是给普通用户在终端直接输入命令的,而是由软件包的维护脚本在后台悄悄调用的。
它的主要职责是处理软件包中 systemd 服务文件的启用或禁用,确保软件在安装、升级或卸载时行为正确,并且即使在安装时系统并未运行 systemd,也能正常工作。
⚙️ 语法与核心命令 (Verbs)
基本使用格式如下,命令后面需要指定单元文件(如 my-service.service):
bash
deb-systemd-helper [选项] {命令} 单元文件...
| 命令 (Verb) | 作用 |
|---|---|
| enable | 启用服务,使其开机自启。此操作只在软件包首次安装时执行一次。 |
| disable | 停用服务,取消开机自启。 |
| purge | 清除服务的所有状态,包括相关的状态文件。 |
| mask | 屏蔽服务,使其无论在任何条件下都不会启动。它会记住服务之前是启用还是禁用状态。 |
| unmask | 取消屏蔽服务,恢复到屏蔽前的状态。 |
| is-enabled | 检查服务当前是否已启用。 |
| was-enabled | Debian 特有的命令,用于判断在软件包升级前服务是否已被启用。 |
| debian-installed | 另一个特有命令,检查指定服务的状态文件是否存在。 |
| update-state | 更新内部状态文件,用于清除已失效的条目或添加新文件,但不会将其启用。 |
| reenable | 相当于 disable 后再 enable,常用于更新服务文件后。 |
⚙️ 常用选项
| 选项 | 作用 |
|---|---|
--quiet |
静默模式,只输出错误信息。 |
--no-restart |
执行操作后不重启服务。 |
--no-reload |
执行操作后不重新加载 systemd 配置。 |
--no-restart-on-upgrade |
在软件包升级时,不执行服务重启。 |
--help, --version |
显示帮助信息或版本信息后退出。 |
🛠️ 实际应用场景 (由维护脚本调用)
你可以看到 deb-systemd-helper 是如何在软件包维护脚本中被使用的。例如,在一个典型的 postinst(安装后)脚本中:
bash
#!/bin/bash
set -e
# ... 其他维护任务
# 启用服务
deb-systemd-helper enable my-service.service
# 可选:在需要时重新加载 systemd 配置
systemctl daemon-reload
exit 0
在 prerm(卸载前)脚本中,则可能用它来禁用服务:
bash
#!/bin/bash
set -e
if [ "$1" = "remove" ]; then
# 在删除软件包时,禁用并停止服务
deb-systemd-helper disable my-service.service
systemctl stop my-service.service
fi
exit 0
在 postrm(卸载后)脚本中,会用 update-state 来清理状态:
bash
#!/bin/bash
set -e
if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then
# 更新状态文件,清理残留信息
deb-systemd-helper update-state my-service.service
fi
exit 0
🧪 调试技巧
如果要调试 deb-systemd-helper 的行为,可以在执行前设置调试环境变量。这会输出详细的调试信息,对排查问题很有帮助:
bash
export _DEB_SYSTEMD_HELPER_DEBUG=1
💎 总结
总结一下关键点:
-
内部工具 :这是一个供软件包维护脚本使用的内部工具,普通用户日常管理服务应使用
systemctl。 -
核心操作:它主要专注于服务的启用、禁用等状态管理,不执行服务的启动或停止。
-
常见选项 :
--no-restart和--no-reload这两个选项在软件包升级时用于控制服务行为。
deb-systemd-invoke命令详解
deb-systemd-invoke 是和 deb-systemd-helper 类似,由软件包维护脚本在后台调用的一个工具,你可以把它理解为一个"智能代理"。它的核心任务是代替 systemctl 去执行服务操作,确保软件包在任何 systemd 环境下都能稳定运行。
🎯 deb-systemd-invoke 的核心价值
与 deb-systemd-helper 主要处理服务的"启用/禁用"状态不同,deb-systemd-invoke 侧重于服务的运行时管理。它的智能之处在于:
-
检测环境 :它会先检查当前系统的初始化系统是否确实是 systemd(通过查看
/proc/1/comm)。 -
决策执行 :如果是 systemd 环境,它会遵重系统策略(
policy-rc.d) 后,把操作转给systemctl执行;如果不是,则会自动回退到传统的 SysV init 工具(如invoke-rc.d,update-rc.d)来完成任务。 -
保证兼容性:这种机制保证了无论底层初始化系统是什么,软件包的安装、升级和卸载脚本都能正确工作。
🛠️ 命令语法与支持的操作
它的基本语法如下,支持的几个核心操作与 systemctl 相似:
bash
deb-systemd-invoke [选项] {操作} 单元文件...
-
start: 启动一个或多个服务。
-
stop: 停止一个或多个服务。
-
restart: 重启一个或多个服务。
-
daemon-reload: 重新加载 systemd 的配置文件。
-
daemon-reexec: 重新执行 systemd 本身,这通常在升级 systemd 包时使用。
此外,它还能直接接收并执行 systemctl 支持的几乎所有动作 ,例如 enable, disable, status, reload, mask, unmask, preset 等-2。
⚙️ 常用选项 (Options)
| 选项 | 说明 |
|---|---|
--user |
作用于用户的 systemd 实例,而非系统服务。 |
--quiet |
静默模式,不输出任何信息。 |
--root=PATH |
指定一个不同的文件系统根目录。 |
--no-dbus |
执行 daemon-reload 或 daemon-reexec 时不通过 D-Bus 通信。 |
🧑💻 维护脚本中的典型用法
deb-systemd-invoke 通常在 postinst (安装后) 和 prerm (卸载前) 这些维护脚本中调用。
- 安装后启动服务: 软件包首次安装时,除了启用服务,还可能需要立即启动它,而不会引发冲突。
bash
# 在 postinst 脚本中
if [ "$1" = "configure" ] && [ -x "/bin/systemctl" ]; then
# 启用服务
deb-systemd-helper enable my-service.service || true
# 启动服务
deb-systemd-invoke start my-service.service || true
fi
这里用
|| true是个好习惯,可以保证即使服务启动失败,软件包安装过程也不会中断-。
- 升级时重启服务: 在软件包升级过程中,如果服务之前已在运行,通常需要重启它来应用更新。
bash
# 在 postinst 脚本中
if [ "$1" = "configure" ] && [ -n "$2" ]; then
# 这是升级操作,尝试重启服务
deb-systemd-invoke restart my-service.service || true
fi
上面的例子检查了 $2 是否存在,如果存在则表明当前是升级而不是首次安装,因此会执行重启操作。
- 卸载时停止服务: 在软件包被移除时,优雅地停止正在运行的服务。
bash
# 在 prerm 脚本中
if [ "$1" = "remove" ] && [ -x "/bin/systemctl" ]; then
# 停止服务
deb-systemd-invoke stop my-service.service || true
# 禁用服务
deb-systemd-helper disable my-service.service || true
fi
⚡ deb-systemd-invoke vs systemctl
虽然功能相似,但两者的使用场景有明确区分。
| 维度 | deb-systemd-invoke |
systemctl |
|---|---|---|
| 使用对象 | 软件包维护脚本 | 系统管理员和普通用户 |
| 核心优势 | 环境兼容性 与策略合规性 | 功能全面 ,直接控制 |
| 非systemd环境 | 能自动回退到 SysV 工具,确保脚本执行不报错-2 | 会直接报错退出,无法完成任务 |
| 交互性 | 非交互式 ,专为脚本设计-1 | 交互式和非交互式均可 |
| 策略文件 | 会主动检查并遵守 /usr/sbin/policy-rc.d 的策略-1 |
默认不检查,除非你明确调用 |
🧐 与 deb-systemd-helper 的分工
这两个工具协同工作,但分工明确。
| 工具 | 主要用途 |
|---|---|
deb-systemd-helper |
专注于服务的配置管理 。处理服务的 enable (启用) 和 disable (禁用) 操作,以及一些更复杂的状态跟踪,如 mask、is-enabled 等-。 |
deb-systemd-invoke |
专注于服务的运行时生命周期 。处理服务的 start (启动)、stop (停止)、restart (重启) 操作。 |
简单来说,deb-systemd-helper 决定服务"要不要开机自启",而 deb-systemd-invoke 决定服务"现在运不运行"。
💎 总结
-
专用工具 :与
deb-systemd-helper一样,deb-systemd-invoke是专供软件包维护脚本使用的内部工具,普通用户进行日常系统管理应该始终使用systemctl命令。 -
运行时操作 :其核心价值在于提供了一种智能、安全的方式来控制服务的运行时状态(启动/停止/重启),并能无缝兼容非 systemd 系统。
-
智能决策:它会先检查环境(是否是 systemd),然后再执行操作,为软件包的可靠性提供了保障。
样例:postinst
bash
#!/bin/sh
# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Automatically added by dh_installsysusers/13.15.3-ok1
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
systemd-sysusers ${DPKG_ROOT:+--root="$DPKG_ROOT"} linglong.conf
fi
# End automatically added section
# Automatically added by dh_installsystemd/13.15.3-ok1
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
if [ -x "$(command -v systemd-tmpfiles)" ]; then
systemd-tmpfiles ${DPKG_ROOT:+--root="$DPKG_ROOT"} --create linglong.conf || true
fi
fi
# End automatically added section
# Automatically added by dh_installsystemduser/13.15.3-ok1
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
if [ -z "${DPKG_ROOT:-}" ] ; then
# The following line should be removed in trixie or trixie+1
deb-systemd-helper --user unmask 'cn.org.linyaps.preloader.service' >/dev/null || true
# was-enabled defaults to true, so new installations run enable.
if deb-systemd-helper --quiet --user was-enabled 'cn.org.linyaps.preloader.service' ; then
# Enables the unit on first installation, creates new
# symlinks on upgrades if the unit file has changed.
deb-systemd-helper --user enable 'cn.org.linyaps.preloader.service' >/dev/null || true
else
# Update the statefile to add new symlinks (if any), which need to be
# cleaned up on purge. Also remove old symlinks.
deb-systemd-helper --user update-state 'cn.org.linyaps.preloader.service' >/dev/null || true
fi
fi
fi
# End automatically added section
# Automatically added by dh_installsystemduser/13.15.3-ok1
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
if [ -z "${DPKG_ROOT:-}" ] ; then
# The following line should be removed in trixie or trixie+1
deb-systemd-helper --user unmask 'linglong-session-helper.service' >/dev/null || true
# was-enabled defaults to true, so new installations run enable.
if deb-systemd-helper --quiet --user was-enabled 'linglong-session-helper.service' ; then
# Enables the unit on first installation, creates new
# symlinks on upgrades if the unit file has changed.
deb-systemd-helper --user enable 'linglong-session-helper.service' >/dev/null || true
else
# Update the statefile to add new symlinks (if any), which need to be
# cleaned up on purge. Also remove old symlinks.
deb-systemd-helper --user update-state 'linglong-session-helper.service' >/dev/null || true
fi
fi
fi
# End automatically added section
# Automatically added by dh_installsystemd/13.15.3-ok1
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
if [ -d /run/systemd/system ]; then
systemctl --system daemon-reload >/dev/null || true
if [ -n "$2" ]; then
_dh_action=restart
else
_dh_action=start
fi
deb-systemd-invoke $_dh_action 'org.deepin.linglong.PackageManager.service' >/dev/null || true
fi
fi
# End automatically added section
# NOTE:
# Backport dh_installsystemduser features here to (re)start user level services here.
# This is needed until debhelper-compat 14.
instances="$(systemctl --no-legend --quiet list-units 'user@*' | sed -n -r 's/.*user@([0-9]+).service.*/\1/p')"
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ]; then
if [ -z "${DPKG_ROOT:-}" ] && [ -d /run/systemd/system ]; then
# Here we reload synchronously, as we really need to block in
# order to ensure the following restart also works. Furthermore,
# if there is no D-Bus user session, the restart won't work either,
# so there's no point if falling back to signals - so either both
# of these operations work, or both fail.
# NOTE:
# orgin script from postinst-systemd-user-restart
# deb-systemd-invoke --user daemon-reload >/dev/null || true
# deb-systemd-invoke --user restart #UNITFILES# >/dev/null || true
for instance in $instances; do
systemctl --user --machine="$instance"@ daemon-reload >/dev/null || true
done
for instance in $instances; do
systemctl --user --machine="$instance"@ restart linglong-session-helper.service >/dev/null || true
systemctl --global --machine="${instance}"@ enable cn.org.linyaps.preloader.service > /dev/null || true
done
fi
fi
case "$1" in
configure)
# enable kernel.unprivileged_userns_clone
# disable kernel.apparmor_restrict_unprivileged_unconfined and kernel.apparmor_restrict_unprivileged_userns
if [ -f /usr/lib/sysctl.d/linglong.conf ]; then
sysctl -p /usr/lib/sysctl.d/linglong.conf 2>/dev/null || true
fi
;;
abort-upgrade | abort-remove | abort-deconfigure) ;;
*)
echo "postinst called with unknown argument '$1'" >&2
exit 0
;;
esac
exit 0
# vi: ft=sh