Skip to content

Cleanup logic of the postrm script and purge pitfalls ​

Schematic diagram of the postrm script cleanup logic and purge pitfalls

"apt install myapp failed, with the message 'dpkg: error: package myapp is in a very bad inconsistent state'—my system is ruined!"

This is postrm The most common consequence of writing a script incorrectly:The package is in a half-installed state and cannot be reinstalled or uninstalled..

postrm(post-removal) is the "final stage" of Debian package maintainer scripts, responsible for performing cleanup after files are removed. It is also most dangerous the final hurdle—because an error here could cause permanent damage to the system state.

This article will provide an in-depth interpretation of postrm the operating mechanism, especially remove and purge the essential differences, and provide production-grade security practices.

What is postrm? ​

postrm is in the Debian package Run script after uninstall, runs after the package's files are deleted from the system. Its responsibilities include:

  • Delete configuration file (purge time)

  • Delete log files and data directory (purge time)

  • Delete system user (purge time)

  • Clean up systemd residue (purge time)

  • Clean up update-alternatives Register (purge time)

  • Handle rollback after upgrade failure

📌Naming conventions: The script is located at debian/ Under the directory, after packaging, it is installed to /var/lib/dpkg/info/<package>.postrm.

Trigger scenarios for postrm ​

ScenarioThe first parameterThe second parameterExplanation
Software package uninstallationremoveNoneUser-initiated uninstall (keep configuration files)
Completely removepurgeNoneUser actively clears (deletes all configurations)
Upgrade failedupgradeOld version numberNew version installation failed, old version restored
Rollback after uninstallationfailed-upgradeOld version numberSame as upgrade, but different context.

⚠️Core concepts:remove and purge Yes fundamentally different operation. The former retains the configuration file, while the latter completely removes it.

standard template for postrm ​

bash
#!/bin/sh
set -e

case "$1" in
    remove)
        # User uninstall: no cleanup
        # The file has been deleted, but the configuration is retained.
        ;;

    purge)
        # Completely purge: delete all configuration files, logs, and users
        ;;

    upgrade)
        # Upgrade failure rollback: clean up remnants of the new version
        ;;

    failed-upgrade)
        # Rollback after uninstallation: clean up remnants of the new version
        ;;

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

exit 0

Hands-on: Writing postrm for a Go application ​

Continue from the previous myapp Go service, we need:

  • remove time: Do nothing (keep configuration)

  • purge time: Delete configuration, logs, users, and systemd remnants

The complete postrm script ​

bash
#!/bin/sh
set -e

PKGNAME="$DPKG_MAINTSCRIPT_PACKAGE"

case "$1" in
    remove)
        # User uninstall (keep configuration): do not perform any cleanup
        # Allow users to reinstall and restore their configuration later.
        ;;

    purge)
        # ✅ Completely erase: delete all traces

        # 1. Delete the configuration file
        rm -rf /etc/myapp

        # 2. Delete log files
        rm -rf /var/log/myapp

        # 3. Delete the data directory
        rm -rf /var/lib/myapp

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

        # 5. Delete system user
        if getent passwd myapp >/dev/null 2>&1; then
            deluser --system myapp >/dev/null 2>&1 || true
        fi

        # 6. Remove the update-alternatives registration (if used)
        if [ -x /usr/bin/update-alternatives ]; then
            update-alternatives --remove myapp /usr/bin/myapp || true
        fi

        # 7. Clean up any leftover temporary files
        rm -rf /tmp/myapp-* 2>/dev/null || true
        ;;

    upgrade)
        # New version installation failed, old version restored.
        # Usually does nothing, because the file has already been overwritten by the old version.
        ;;

    failed-upgrade)
        # Likewise: do nothing.
        ;;

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

exit 0

remove vs purge: essential difference ​

OperationDelete fileDelete configurationDelete userAfter reinstallation
dpkg -r(remove)✅❌❌Configuration retained, ready to use directly
dpkg -P(purge)✅✅✅Completely clean, reinitialize

User perspective ​

bash
# remove: keep configuration
sudo apt remove myapp
# /etc/myapp/config.yml is still there

# After reinstallation, the configuration takes effect directly.
sudo apt install myapp
# Service started using the original configuration ✅

# purge: completely eliminate
sudo apt purge myapp
# The entire /etc/myapp/ directory has been deleted.

# After reinstalling, start from the default configuration.
sudo apt install myapp
# The service starts with the default configuration ✅

postrm script perspective ​

bash
remove)
    # The file has been deleted, but the configuration is still there.
    # We do nothing.
    ;;

purge)
    # The file has been deleted, and the configuration should be deleted as well.
    # We clean up everything
    rm -rf /etc/myapp /var/log/myapp /var/lib/myapp
    deluser myapp
    ;;

Key best practices ​

1. remove; keep the scene silent ​

bash
# ✅ Correct: remove does nothing
remove)
    # Preserve all configurations and data
    ;;

# ❌ Error: configuration was deleted on remove
remove)
    rm -rf /etc/myapp  # Users cannot retain the configuration!
    ;;

2. Thoroughly clean up systemd in the purge scenario ​

bash
# ✅ Correct: fully clean up systemd
purge)
    if [ -x /usr/bin/deb-systemd-helper ]; then
        deb-systemd-helper purge myapp.service >/dev/null 2>&1 || true
    fi

    # Manually clean up any residual unit files.
    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
    ;;

# ❌ Error: disable without purge
purge)
    systemctl disable myapp.service  # systemd remnants still exist!
    ;;

3. Make good use of deb-systemd-helper purge ​

deb-systemd-helper purge is a command specifically designed for postrm, and it will:

  • Remove all links to systemd unit files

  • Clean up systemd's cached state

  • Ensure systemctl status No longer show remnants

bash
# Used in purge scenarios
purge)
    deb-systemd-helper purge myapp.service
    ;;

4. Security checks when deleting a user ​

bash
# ✅ Correct: check that the user exists before deleting
purge)
    if getent passwd myapp >/dev/null 2>&1; then
        deluser --system myapp >/dev/null 2>&1 || true
    fi
    ;;

# ❌ Error: delete directly (the command fails when the user does not exist)
purge)
    deluser myapp  # If the user has been deleted, the command fails.
    ;;

5. All delete commands must be fault-tolerant. ​

bash
# ✅ Correct: No error even when the directory does not exist
rm -rf /etc/myapp  # rm -rf is naturally fault-tolerant (does not error if the directory does not exist)

# ✅ Correct: Check existence when deleting a user
if getent passwd myapp; then
    deluser myapp
fi

# ✅ Correct: use || true for fault tolerance
deb-systemd-helper purge myapp.service || true

Common Pitfalls and How to Avoid Them ​

Pitfall 1: The configuration file was deleted during remove ​

bash
# ❌ Catastrophic error
remove)
    rm -rf /etc/myapp  # The user can no longer retain the configuration.

Consequences: User executes apt remove The configuration is lost afterward, and it needs to be reconfigured after reinstallation.

Pitfall 2: forgetting to delete users during purge ​

bash
# ❌ User residue
purge)
    rm -rf /etc/myapp
    rm -rf /var/log/myapp
    # Forgot to delete user myapp

consequences: When reinstalling adduser Failed (user already exists), causing the installation to be interrupted.

Pitfall 3: systemd remnants not cleaned up ​

bash
# ❌ systemd remnants
purge)
    # Only delete files, do not clean up systemd
    rm -rf /etc/myapp

Consequences:systemctl status myapp Still displays service information, even systemctl start myapp Will attempt to start a deleted service.

Trap 4: postrm failure causes package state corruption ​

bash
# ❌ Error: postrm script failed
purge)
    deluser myapp  # If the user does not exist, the command fails
    rm -rf /etc/myapp  # This line will not be executed
    # The script exited with a non-zero status, and dpkg marked the package status as "half-configured".

Consequences: The package cannot be reinstalled or uninstalled. The system reports "package is in a very bad inconsistent state".

Methods for repairing state corruption ​

If you have already fallen into Trap 4, you can recover using the following methods:

bash
# Force reinstall
sudo dpkg --force-depends -i myapp_1.0.0.deb

# or force removal (without executing scripts)
sudo dpkg --remove --force-remove-reinstreq myapp

# Or manually clear the dpkg information
sudo rm -rf /var/lib/dpkg/info/myapp.*
sudo dpkg --remove --force-remove-reinstreq myapp

Coordination between postrm and other scripts ​

Test your postrm ​

Simulate remove (retain configuration) ​

bash
# installation package
sudo dpkg -i myapp_1.0.0.deb

# Check whether the configuration file exists
ls -la /etc/myapp/

# remove (trigger postrm remove)
sudo dpkg -r myapp

# Check whether the configuration file still exists
ls -la /etc/myapp/  # It should still exist ✅

Simulate purge (completely remove) ​

bash
# Reinstall
sudo dpkg -i myapp_1.0.0.deb

# purge (trigger postrm purge)
sudo dpkg -P myapp

# Check whether it has been completely cleaned.
ls -la /etc/myapp/     # It probably doesn't exist ❌
ls -la /var/log/myapp/ # It probably doesn't exist ❌
getent passwd myapp    # It probably doesn't exist ❌
systemctl status myapp # should display "not found" ❌

Test reinstallation ​

bash
# reinstall after purge
sudo dpkg -i myapp_1.0.0.deb

# Check whether it is working properly.
systemctl status myapp  # Should run normally ✅

View the postrm execution log ​

bash
# dpkg detailed logs
sudo grep postrm /var/log/dpkg.log

postrm security checklist ​

Before releasing your Debian package, check against the following checklist.postrm:

  • remove The scenario does not perform any destructive operations (preserving configuration, data, and users).

  • purge Scenario delete all configuration files (/etc/ all files under)

  • purge The scenario deletes all logs and data directories.

  • purge Scenario: Delete system user (check existence)

  • purge Scenario cleanup systemd remnants (deb-systemd-helper purge)

  • purge Scene cleanup update-alternatives(if used)

  • All delete commands have fault tolerance handling (|| true or condition evaluation)

  • The script uses exit 0 ending

  • upgrade and failed-upgrade The scenario is empty or only performs harmless operations.

  • Tested the complete remove → install → purge → install workflow.

Summary ​

Key pointsExplanation
remove vs purgeremove retains configuration, purge completely removes
postrm removeDo nothing (retain user data)
postrm purgeDelete configuration, logs, users, and systemd remnants
Use deb-systemd-helper purgeDedicated to cleaning up systemd remnants
All commands must be fault-tolerant.Prevent postrm failures from corrupting package state
Test the complete linkremove → install → purge → install

Maintainer Script Series Summary ​

ScriptExecution timingCore responsibilitiesbiggest trap
postinstAfter installationInitialize and enable servicesOverride user configuration
prermBefore uninstallationStop serviceDelete user data
postrmAfter uninstallationClean up remnantsScript failure causes state corruption

These three scripts together constitute the Debian package's Full lifecycle management Master them, and you will grasp the "soul" of Debian packaging.

Next step ​

In the next chapter, we will enter Practical packaging Phase, apply the maintainer script knowledge learned earlier to a real Golang project.

Next article preview:Chapter 4: A Complete Guide to Packaging a Golang Project into a Debian Package

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