English
Cleanup logic of the postrm script 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 (
purgetime)Delete log files and data directory (
purgetime)Delete system user (
purgetime)Clean up
systemdresidue (purgetime)Clean up
update-alternativesRegister (purgetime)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
| Scenario | The first parameter | The second parameter | Explanation |
|---|---|---|---|
| Software package uninstallation | remove | None | User-initiated uninstall (keep configuration files) |
| Completely remove | purge | None | User actively clears (deletes all configurations) |
| Upgrade failed | upgrade | Old version number | New version installation failed, old version restored |
| Rollback after uninstallation | failed-upgrade | Old version number | Same 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
Basic Template (Recommended)
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 0Hands-on: Writing postrm for a Go application
Continue from the previous myapp Go service, we need:
removetime: Do nothing (keep configuration)purgetime: 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 0remove vs purge: essential difference
| Operation | Delete file | Delete configuration | Delete user | After 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 statusNo 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 || trueCommon 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 myappconsequences: 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/myappConsequences: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 myappCoordination 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.logpostrm security checklist
Before releasing your Debian package, check against the following checklist.postrm:
removeThe scenario does not perform any destructive operations (preserving configuration, data, and users).purgeScenario delete all configuration files (/etc/all files under)purgeThe scenario deletes all logs and data directories.purgeScenario: Delete system user (check existence)purgeScenario cleanup systemd remnants (deb-systemd-helper purge)purgeScene cleanupupdate-alternatives(if used)All delete commands have fault tolerance handling (
|| trueor condition evaluation)The script uses
exit 0endingupgradeandfailed-upgradeThe scenario is empty or only performs harmless operations.Tested the complete remove → install → purge → install workflow.
Summary
| Key points | Explanation |
|---|---|
remove vs purge | remove retains configuration, purge completely removes |
postrm remove | Do nothing (retain user data) |
postrm purge | Delete configuration, logs, users, and systemd remnants |
Use deb-systemd-helper purge | Dedicated to cleaning up systemd remnants |
| All commands must be fault-tolerant. | Prevent postrm failures from corrupting package state |
| Test the complete link | remove → install → purge → install |
Maintainer Script Series Summary
| Script | Execution timing | Core responsibilities | biggest trap |
|---|---|---|---|
| postinst | After installation | Initialize and enable services | Override user configuration |
| prerm | Before uninstallation | Stop service | Delete user data |
| postrm | After uninstallation | Clean up remnants | Script 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
