Skip to content

Complete Interpretation and Best Practices of postinst Scripts ​

Complete Interpretation of postinst Scripts and Best Practices Diagram

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.:

SceneThe first parameterExplanation
First installationconfigureThe package is installed on the system for the first time.
Version upgradeconfigureUpgrade from an old version to a new version
Version rollbackabort-upgradeUpgrade failed, rolled back to the old version
Dependency issueabort-removeUninstall 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 ​

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 0

Key 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 myapp

  • Register 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 0

Key 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.service

Reason:

  • deb-systemd-helper will handle it correctly --no-restart-on-upgrade and other strategies

  • Support dpkg of --no-start Option (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)
fi

configure The second parameter of is Old version number:

  • First installation:$2 is empty

  • Upgrade:$2 is 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.service

Reason: 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
fi

Trap 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.deb

Simulated 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.deb

Simulated 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 myapp

View the postinst execution log ​

bash
# dpkg detailed log
sudo tail -f /var/log/dpkg.log

# or use
sudo dpkg --audit

Summary ​

Key pointsDescription
postinst Run in four scenariosconfigure、abort-upgrade、abort-remove
Use deb-systemd-helperDo not use systemctl directly
Distinguish between first-time installation and upgradeThrough $2 Determine the old version
Preserve user configurationDo not overwrite /etc/ existing configuration files under
Do not start the service in postinstOnly enable; startup is handled uniformly by dpkg.
Test all scenariosInstall, 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

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