English
Complete Interpretation and Best Practices of postinst Scripts

When you use apt install myapp When installing a software package, lines of logs scroll across the terminal, and finally it prompts "Installation complete." But you may not have thought about this:After installation is complete, how does the software package complete "initialization"?
The answer is postinst— the "leading protagonist" in Debian package maintainer scripts.
This article is the first chapter of the Debian packaging series, and we will take an in-depth look at postinst The complete execution mechanism of the script, covering all trigger scenarios, and providing production-grade best practices.
What is postinst?
postinst is in the Debian software package Post-install execution script, it runs after the package's files have been extracted to the system. Its responsibilities include:
Create the system user required for running.
Initialize the configuration file (first-time installation)
Register systemd service (
enable+start)Execute database migration or initialization
Trigger restarts of other services (e.g., reloading Nginx configuration)
📌Naming conventions: The script is located at debian/ Under the directory, after packaging it will be installed to /var/lib/dpkg/info/<package>.postinst.
Trigger scenarios for postinst
Many beginners think postinst Run only once during "installation".Actually, it will be triggered in the following four scenarios.:
| Scene | The first parameter | Explanation |
|---|---|---|
| First installation | configure | The package is installed on the system for the first time. |
| Version upgrade | configure | Upgrade from an old version to a new version |
| Version rollback | abort-upgrade | Upgrade failed, rolled back to the old version |
| Dependency issue | abort-remove | Uninstall aborted due to dependency conflict |
among them configure is the most common scenario, but abort-* Scenarios are often overlooked—and this is precisely the culprit behind system state inconsistency.
Standard template for postinst
Basic template (recommended)
bash
#!/bin/sh
set -e
case "$1" in
configure)
# Initialization logic on first installation or upgrade
;;
abort-upgrade)
# Cleanup during rollback after upgrade failure
;;
abort-remove)
# Recovery when uninstallation is aborted
;;
*)
echo "postinst called with unknown argument: $1" >&2
exit 1
;;
esac
# General handling: fragments generated by dh_installdeb will be appended here.
exit 0Key instructions
set -e: Exit immediately if any command fails to prevent continuing execution in an error state.case "$1": Execute different branches according to the first parameter
⚠️Important:postinst The script must use exit 0 at the end, otherwise dpkg will consider the installation failed and roll back.
Hands-on: Writing postinst for a Go application
Suppose we have a web service written in Go.myapp, need to complete the following initialization tasks:
Create a dedicated system user
myappRegister and enable the systemd service
Generate a default configuration file on first installation.
When upgrading Retain User-modified configuration file
The complete postinst script
bash
#!/bin/sh
set -e
# Get the package name from the control file
PKGNAME="$DPKG_MAINTSCRIPT_PACKAGE"
case "$1" in
configure)
# 1. Create a system user (if it does not exist)
if ! getent passwd myapp >/dev/null; then
adduser --system --group --no-create-home --home /var/lib/myapp myapp
fi
# 2. Create the data directory and set permissions
mkdir -p /var/lib/myapp /var/log/myapp
chown myapp:myapp /var/lib/myapp /var/log/myapp
# 3. Handle configuration files (first installation vs upgrade)
if [ -z "$2" ] || [ "$2" = "<old-version>" ]; then
# First installation: directly copy the default configuration
cp /usr/share/myapp/config.example.yml /etc/myapp/config.yml
else
# Upgrade: keep the existing configuration, do not overwrite
# But if the configuration file does not exist (it may have been manually deleted), regenerate it.
if [ ! -f /etc/myapp/config.yml ]; then
cp /usr/share/myapp/config.example.yml /etc/myapp/config.yml
fi
fi
# 4. Register and enable the systemd service (but do not start it)
# Use deb-systemd-helper instead of calling systemctl directly
deb-systemd-helper enable myapp.service >/dev/null
;;
abort-upgrade)
# Upgrade failure rollback: do nothing, and the system will revert to the old version.
# However, it is necessary to ensure that the old version's service status is normal.
;;
abort-remove)
# Uninstall aborted: re-enable the service
deb-systemd-helper enable myapp.service >/dev/null
;;
*)
echo "postinst called with unknown argument: $1" >&2
exit 1
;;
esac
# Code section automatically generated by invoking debhelper
# For example, dh_installdeb will append systemd startup logic here.
if [ -x /usr/share/debconf/confmodule ]; then
. /usr/share/debconf/confmodule
fi
exit 0Key Best Practices
1. Use deb-systemd-helper rather than directly systemctl
bash
# ❌ Error: Calling systemctl directly in postinst
systemctl enable myapp.service
systemctl start myapp.service
# ✅ Correct: use deb-systemd-helper
deb-systemd-helper enable myapp.service
deb-systemd-helper start myapp.serviceReason:
deb-systemd-helperwill handle it correctly--no-restart-on-upgradeand other strategiesSupport
dpkgof--no-startOption (users can disable automatic startup)Avoid errors in chroot or container environments
2. Distinguish between first-time installation and upgrade
bash
if [ -z "$2" ] || [ "$2" = "" ]; then
# First installation ($2 is empty)
else
# Upgrade ($2 is the old version number)
ficonfigure The second parameter of is Old version number:
First installation:
$2is emptyUpgrade:
$2is the old version number
3. Configuration File Retention Policy
Principle: Never overwrite configuration files that the user has modified.
bash
# Strategy 1: Generate only on first installation
if [ ! -f /etc/myapp/config.yml ]; then
cp /usr/share/myapp/config.yml.example /etc/myapp/config.yml
fi
# Strategy 2: Use ucf (recommended; automatically handles three-way merge)
# The ucf package needs to be installed additionally.About ucf We will write a separate advanced guide for the complete usage of (User Configuration File).
4. Do not start the service in postinst
bash
# ❌ Error: Directly starting in postinst
systemctl start myapp.service
# ✅ Correct: enable only; dh_installdeb will start it uniformly after installation is complete.
deb-systemd-helper enable myapp.serviceReason: When multiple packages are installed at the same time, starting services too early may result in unmet dependencies. Debian's policy is to defer startup until all packages have been installed.
Common Pitfalls and How to Avoid Them
Trap 1:set -e Causes a non-fatal error that interrupts the installation
bash
# ❌ Error: if the user does not exist, adduser will report an error and exit
adduser --system myapp
# ✅ Correct: Check preconditions
if ! getent passwd myapp >/dev/null; then
adduser --system myapp
fiTrap 2: In abort-upgrade execute destructive operations in
bash
# ❌ Error: Data was deleted during the upgrade rollback
abort-upgrade)
rm -rf /var/lib/myapp # Data loss!
;;
# ✅ Correct: abort-upgrade does nothing
abort-upgrade)
# The system will automatically roll back to the old version of the file; no additional action is required.
;;Pitfall 3: Depending on other packages that are not installed
bash
# ❌ Error: calling commands from other packages in postinst
configure)
mysql_install_db # Fail if mysql-server is not installed
;;Correct approach: Put such operations into On first startup Execute, or at control explicitly declare dependencies in it.
Sequence diagram of postinst invocation
Test your postinst
Simulated installation
bash
# Build and install package
dpkg-buildpackage -us -uc
sudo dpkg -i ../myapp_1.0.0_amd64.debSimulated upgrade
bash
# Install an older version
sudo dpkg -i myapp_0.9.0.deb
# Upgrade to the new version
sudo dpkg -i myapp_1.0.0.debSimulated rollback
bash
# Upgrade to new version (intentionally fail)
sudo dpkg -i myapp_1.0.0.deb || true
# View rollback status
sudo dpkg --get-selections | grep myappView the postinst execution log
bash
# dpkg detailed log
sudo tail -f /var/log/dpkg.log
# or use
sudo dpkg --auditSummary
| Key points | Description |
|---|---|
postinst Run in four scenarios | configure、abort-upgrade、abort-remove |
Use deb-systemd-helper | Do not use systemctl directly |
| Distinguish between first-time installation and upgrade | Through $2 Determine the old version |
| Preserve user configuration | Do not overwrite /etc/ existing configuration files under |
| Do not start the service in postinst | Only enable; startup is handled uniformly by dpkg. |
| Test all scenarios | Install, upgrade, roll back, uninstall |
Next step
In the next chapter, we will explore prerm (pre-removal script)——Its trigger scenarios are more complex than postinst, especially when handling dependency conflicts deconfigure Scene.
Next post preview:Chapter 2: The Role of the prerm Script and How to Use It Correctly
