Skip to content

postrm 脚本的清理逻辑与 purge 陷阱

postrm 脚本的清理逻辑与 purge 陷阱示意图

"apt install myapp 失败了,提示 'dpkg: error: package myapp is in a very bad inconsistent state'——我的系统废了!"

这是 postrm 脚本写错时最常见的后果:软件包处于半安装状态,无法重新安装,也无法卸载

postrm(post-removal)是 Debian 包维护者脚本的"最后一关",负责在文件被删除后执行清理。它也是最危险的一关——因为这里的错误可能导致系统状态永久性损坏。

本文将深入解读 postrm 的运行机制,特别是 removepurge 的本质区别,并给出生产级的安全实践。

postrm 是什么?

postrm 是 Debian 软件包中的卸载后执行脚本,在包的文件被从系统删除后运行。它的职责包括:

  • 删除配置文件(purge 时)
  • 删除日志文件和数据目录(purge 时)
  • 删除系统用户(purge 时)
  • 清理 systemd 残留(purge 时)
  • 清理 update-alternatives 注册(purge 时)
  • 处理升级失败后的回滚

📌 命名规范:脚本位于 debian/ 目录下,打包后被安装到 /var/lib/dpkg/info/<package>.postrm

postrm 的触发场景

场景第一个参数第二个参数说明
软件包卸载remove用户主动卸载(保留配置文件)
彻底清除purge用户主动清除(删除所有配置)
升级失败upgrade旧版本号新版本安装失败,旧版本恢复
卸载后回滚failed-upgrade旧版本号同 upgrade,但上下文不同

⚠️ 核心概念removepurge本质不同的操作。前者保留配置文件,后者彻底清除。

postrm 的标准模板

基础模板(推荐)

bash
#!/bin/sh
set -e

case "$1" in
    remove)
        # 用户卸载:不做任何清理
        # 文件已删除,但配置保留
        ;;

    purge)
        # 彻底清除:删除所有配置文件、日志、用户
        ;;

    upgrade)
        # 升级失败回滚:清理新版本残留
        ;;

    failed-upgrade)
        # 卸载后回滚:清理新版本残留
        ;;

    *)
        echo "postrm called with unknown argument: $1" >&2
        exit 1
        ;;
esac

exit 0

实战:为 Go 应用编写 postrm

延续之前的 myapp Go 服务,我们需要:

  1. remove:什么都不做(保留配置)
  2. purge:删除配置、日志、用户、systemd 残留

完整的 postrm 脚本

bash
#!/bin/sh
set -e

PKGNAME="$DPKG_MAINTSCRIPT_PACKAGE"

case "$1" in
    remove)
        # 用户卸载(保留配置):不做任何清理
        # 让用户以后可以重新安装并恢复配置
        ;;

    purge)
        # ✅ 彻底清除:删除所有痕迹

        # 1. 删除配置文件
        rm -rf /etc/myapp

        # 2. 删除日志文件
        rm -rf /var/log/myapp

        # 3. 删除数据目录
        rm -rf /var/lib/myapp

        # 4. 删除 systemd 残留
        if [ -x /usr/bin/deb-systemd-helper ]; then
            deb-systemd-helper purge myapp.service >/dev/null 2>&1 || true
        fi

        # 5. 删除系统用户
        if getent passwd myapp >/dev/null 2>&1; then
            deluser --system myapp >/dev/null 2>&1 || true
        fi

        # 6. 删除 update-alternatives 注册(如果使用)
        if [ -x /usr/bin/update-alternatives ]; then
            update-alternatives --remove myapp /usr/bin/myapp || true
        fi

        # 7. 清理可能残留的临时文件
        rm -rf /tmp/myapp-* 2>/dev/null || true
        ;;

    upgrade)
        # 新版本安装失败,旧版本恢复
        # 通常什么都不做,因为文件已经被旧版本覆盖了
        ;;

    failed-upgrade)
        # 同样:什么都不做
        ;;

    *)
        echo "postrm called with unknown argument: $1" >&2
        exit 1
        ;;
esac

exit 0

remove vs purge:本质区别

操作删除文件删除配置删除用户重新安装后
dpkg -r(remove)配置保留,直接可用
dpkg -P(purge)完全干净,重新初始化

用户视角

bash
# remove:保留配置
sudo apt remove myapp
# /etc/myapp/config.yml 还在

# 重新安装后,配置直接生效
sudo apt install myapp
# 服务使用原有配置启动 ✅

# purge:彻底清除
sudo apt purge myapp
# /etc/myapp/ 整个目录被删除

# 重新安装后,从默认配置开始
sudo apt install myapp
# 服务使用默认配置启动 ✅

postrm 脚本视角

bash
remove)
    # 文件已删除,但配置还在
    # 我们什么都不做
    ;;

purge)
    # 文件已删除,配置也要删除
    # 我们清理一切
    rm -rf /etc/myapp /var/log/myapp /var/lib/myapp
    deluser myapp
    ;;

关键最佳实践

1. remove 场景保持沉默

bash
# ✅ 正确:remove 什么都不做
remove)
    # 保留所有配置和数据
    ;;

# ❌ 错误:remove 时删除了配置
remove)
    rm -rf /etc/myapp  # 用户无法保留配置!
    ;;

2. purge 场景彻底清理 systemd

bash
# ✅ 正确:完整清理 systemd
purge)
    if [ -x /usr/bin/deb-systemd-helper ]; then
        deb-systemd-helper purge myapp.service >/dev/null 2>&1 || true
    fi

    # 手动清理可能残留的单元文件
    rm -f /etc/systemd/system/multi-user.target.wants/myapp.service 2>/dev/null || true
    rm -f /lib/systemd/system/myapp.service 2>/dev/null || true

    systemctl daemon-reload >/dev/null 2>&1 || true
    ;;

# ❌ 错误:只 disable 不 purge
purge)
    systemctl disable myapp.service  # systemd 残留仍存在!
    ;;

3. 善用 deb-systemd-helper purge

deb-systemd-helper purge 是专门为 postrm 设计的命令,它会:

  • 删除所有 systemd 单元文件的链接
  • 清理 systemd 的缓存状态
  • 确保 systemctl status 不再显示残留
bash
# 在 purge 场景中使用
purge)
    deb-systemd-helper purge myapp.service
    ;;

4. 删除用户时的安全检查

bash
# ✅ 正确:检查用户存在再删除
purge)
    if getent passwd myapp >/dev/null 2>&1; then
        deluser --system myapp >/dev/null 2>&1 || true
    fi
    ;;

# ❌ 错误:直接删除(用户不存在时命令失败)
purge)
    deluser myapp  # 如果用户已删除,命令失败
    ;;

5. 所有删除命令都要容错

bash
# ✅ 正确:目录不存在时也不报错
rm -rf /etc/myapp  # rm -rf 天然容错(目录不存在不报错)

# ✅ 正确:删除用户时检查存在性
if getent passwd myapp; then
    deluser myapp
fi

# ✅ 正确:使用 || true 容错
deb-systemd-helper purge myapp.service || true

常见陷阱与排坑

陷阱1:remove 时删除了配置文件

bash
# ❌ 灾难性错误
remove)
    rm -rf /etc/myapp  # 用户无法保留配置了

后果:用户执行 apt remove 后配置丢失,重新安装后需要重新配置。

陷阱2:purge 时忘记删除用户

bash
# ❌ 用户残留
purge)
    rm -rf /etc/myapp
    rm -rf /var/log/myapp
    # 忘记删除用户 myapp

后果:重新安装时 adduser 失败(用户已存在),导致安装中断。

陷阱3:systemd 残留未清理

bash
# ❌ systemd 残留
purge)
    # 只删除文件,不清理 systemd
    rm -rf /etc/myapp

后果systemctl status myapp 仍然显示服务信息,甚至 systemctl start myapp 会尝试启动已删除的服务。

陷阱4:postrm 失败导致包状态损坏

bash
# ❌ 错误:postrm 脚本失败
purge)
    deluser myapp  # 如果用户不存在,命令失败
    rm -rf /etc/myapp  # 这行不会执行
    # 脚本以非 0 退出,dpkg 标记包状态为 "half-configured"

后果:包无法重新安装,也无法卸载。系统提示 "package is in a very bad inconsistent state"。

修复"状态损坏"的方法

如果已经踩了陷阱4,可以用以下方法恢复:

bash
# 强制重新安装
sudo dpkg --force-depends -i myapp_1.0.0.deb

# 或强制移除(不执行脚本)
sudo dpkg --remove --force-remove-reinstreq myapp

# 或手动清除 dpkg 信息
sudo rm -rf /var/lib/dpkg/info/myapp.*
sudo dpkg --remove --force-remove-reinstreq myapp

postrm 与其他脚本的协作

测试你的 postrm

模拟 remove(保留配置)

bash
# 安装包
sudo dpkg -i myapp_1.0.0.deb

# 检查配置文件是否存在
ls -la /etc/myapp/

# remove(触发 postrm remove)
sudo dpkg -r myapp

# 检查配置文件是否还在
ls -la /etc/myapp/  # 应该仍然存在 ✅

模拟 purge(彻底清除)

bash
# 重新安装
sudo dpkg -i myapp_1.0.0.deb

# purge(触发 postrm purge)
sudo dpkg -P myapp

# 检查是否完全清理
ls -la /etc/myapp/     # 应该不存在 ❌
ls -la /var/log/myapp/ # 应该不存在 ❌
getent passwd myapp    # 应该不存在 ❌
systemctl status myapp # 应该显示 "not found" ❌

测试重新安装

bash
# purge 后重新安装
sudo dpkg -i myapp_1.0.0.deb

# 检查是否正常工作
systemctl status myapp  # 应该正常运行 ✅

查看 postrm 执行日志

bash
# dpkg 详细日志
sudo grep postrm /var/log/dpkg.log

postrm 安全清单

在发布你的 Debian 包之前,对照以下清单检查 postrm

  • [ ] remove 场景不做任何破坏性操作(保留配置、数据、用户)
  • [ ] purge 场景删除所有配置文件(/etc/ 下的所有文件)
  • [ ] purge 场景删除所有日志和数据目录
  • [ ] purge 场景删除系统用户(检查存在性)
  • [ ] purge 场景清理 systemd 残留(deb-systemd-helper purge
  • [ ] purge 场景清理 update-alternatives(如果使用)
  • [ ] 所有删除命令都做了容错处理(|| true 或条件判断)
  • [ ] 脚本以 exit 0 结尾
  • [ ] upgradefailed-upgrade 场景为空或只做无害操作
  • [ ] 测试了 remove → install → purge → install 完整链路

小结

要点说明
remove vs purgeremove 保留配置,purge 彻底清除
postrm remove什么都不做(保留用户数据)
postrm purge删除配置、日志、用户、systemd 残留
使用 deb-systemd-helper purge专门清理 systemd 残留
所有命令都要容错避免 postrm 失败导致包状态损坏
测试完整链路remove → install → purge → install

维护者脚本系列总结

脚本运行时机核心职责最大陷阱
postinst安装后初始化、启用服务覆盖用户配置
prerm卸载前停止服务删除用户数据
postrm卸载后清理残留脚本失败导致状态损坏

这三个脚本共同构成了 Debian 包的完整生命周期管理。掌握它们,你就掌握了 Debian 打包的"灵魂"。

下一步

在下一章中,我们将进入实战打包阶段,把前面学到的维护者脚本知识应用到真实的 Golang 项目中。


下一篇预告第四章:Golang 项目打包为 Debian 包完整指南

最后更新2026/07/25 14:21
如果你觉得这篇文章有帮助,或者想聊聊技术、工作,欢迎通过下面方式联系我:
contact fishfinal