Maintainer Scripts and Install Remove Lifecycle of Linux Packages
Both build chapters used a single maintainer script — postinst on the Debian side, %post on the RPM side — to print a simple confirmation message. That barely scratches what these scripts are actually capable of. This chapter covers the full lifecycle on both sides, a genuinely realistic use case, and the security weight of what it means for these scripts to run as root.
The Four Debian Hook Points
A .deb package can include up to four separate scripts, each triggered at a specific moment:
| Script | Runs |
|---|---|
preinst | Before files are unpacked onto the system |
postinst | After files are unpacked |
prerm | Before files are removed |
postrm | After files are removed |
Each of these is a separate, individually executable file under the package’s DEBIAN/ directory, exactly matching where the postinst example lived in the earlier .deb build chapter.
The RPM Equivalent: Scriptlets
RPM covers the same four moments with scriptlets — embedded directly inside the spec file as sections, rather than existing as standalone files:
| Section | Runs |
|---|---|
%pre | Before files are installed |
%post | After files are installed |
%preun | Before files are removed |
%postun | After files are removed |
Side by side, the mapping is exact:
| Moment | Debian | RPM |
|---|---|---|
| Before install | preinst | %pre |
| After install | postinst | %post |
| Before removal | prerm | %preun |
| After removal | postrm | %postun |
A Realistic Use Case: Creating A Dedicated Service Account
This is genuinely one of the most common real reasons a package needs a maintainer script at all — a service that shouldn’t run as root needs its own dedicated system account, and that account needs to exist before the service can ever start. This directly applies the system-account pattern from the Users & Groups section of this course.
On the Debian side, in preinst (before files are even unpacked, ensuring the account exists the moment anything might need it):
#!/bin/bash
if ! getent passwd myservice > /dev/null; then
useradd -r -s /usr/sbin/nologin myservice
fi
exit 0getent passwd myservice checks whether the account already exists first — genuinely important for a script that might run again during an upgrade, where creating the same account a second time would simply error out.
The corresponding cleanup, in postrm, but only on a genuine purge rather than an ordinary remove:
#!/bin/bash
if [ "$1" = "purge" ]; then
userdel myservice
fi
exit 0That $1 check matters — Debian maintainer scripts receive an argument describing exactly what triggered them (configure, remove, purge, upgrade, and others depending on the script), letting a script behave differently depending on context rather than assuming every invocation means the same thing.
On the RPM side, the equivalent lives directly in the spec file:
%pre
getent passwd myservice > /dev/null || useradd -r -s /sbin/nologin myservice
exit 0
%postun
if [ "$1" = "0" ]; then
userdel myservice
fi
exit 0RPM’s convention here is a numeric argument instead of a named one — %postun’s $1 is 0 on a genuine final removal, but a nonzero value during an upgrade (since the old package’s %postun and the new package’s %pre/%post both run as part of the same upgrade transaction) — the same underlying idea as Debian’s argument, expressed differently.
Why This Is A Genuine Security Consideration?
This is worth stating plainly, tying directly back to the trust discussion from earlier in this section: every one of these scripts runs as root, unconditionally, the moment a package is installed — before you’ve had any chance to review what it actually does beyond reading the package’s advertised description. A maintainer script isn’t sandboxed, isn’t restricted in scope, and isn’t limited to “reasonable” setup actions — it’s arbitrary code, running with full system privileges, triggered automatically by nothing more than apt install or dnf install.
This is precisely why repository signature verification matters as much as it does. A signed, trusted repository is a guarantee (as strong as your trust in that repository’s maintainers and their own security practices) that the package — including every maintainer script inside it — genuinely came from who it claims to. Disabling signature checks, or installing an untrusted .deb/.rpm file from an unverified source, means running arbitrary root-level code with no verification of its origin at all.
Warning
Before installing any package from outside your distribution’s official, signed repositories, consider actually reading its maintainer scripts first — for a .deb, extract it with dpkg-deb -R and inspect the DEBIAN/ directory; for an .rpm, rpm -qp --scripts package.rpm prints every scriptlet without installing anything. This is a genuinely worthwhile habit for anything from a less-established source, since these scripts are exactly where a malicious package would do real damage.
What Happens When A Script Fails?
A maintainer script’s exit code matters — a nonzero exit code signals failure, and the package manager treats this seriously rather than silently continuing.
On the Debian side, a failed postinst leaves the package in the iF (or similar half-configured) state from the dpkg -l status table covered earlier in this section — exactly the situation dpkg --configure -a exists to recover from, attempting to re-run the interrupted configuration step once whatever caused the original failure has been fixed.
On the RPM side, a failed scriptlet during rpmbuild itself will typically halt the build outright, while a failure during actual installation is reported directly, leaving the transaction in a state worth investigating with rpm -V before assuming the install genuinely succeeded.
Inspecting Scripts Without Running Them
Both ecosystems let you review a package’s maintainer scripts before ever installing it — worth doing routinely for anything from outside your normal trusted sources:
dpkg-deb -R somepackage.deb /tmp/inspect
cat /tmp/inspect/DEBIAN/postinstrpm -qp --scripts somepackage.rpmWhat’s Next
With the full lifecycle understood — including a genuinely realistic account-management use case and the real security weight behind it — the next chapter maps out something this whole section has been building toward: every single location, on both distro families, that a package can actually touch on disk.