/!\ Warning: starting from dpkg 1.15.7.2, most of the actions implemented by this page snippets are implemented by dpkg-maintscript-helper, check its manpage for details.

dh_installdeb from debhelper has support for generating the required maintainer script snippets for dpkg-maintscript-helper out of debian/*.maintscript files, please use that.

This information was originally copied from dpkg.org.

Gracefully Handling conffiles

When upgrading a package, dpkg will not remove a conffile (a configuration file for which dpkg should preserve user changes) if it is not present in the newer version. There are two principal reasons for this; the first is that the conffile could've been dropped by accident and the next version could restore it, users wouldn't want their changes thrown away. The second is to allow packages to transition files from a dpkg-maintained conffile to a file maintained by the package's maintainer scripts, usually with a tool like debconf or ucf.

This means if you ever rename or remove a conffile, as a package maintainer you must take certain steps yourself to behave correctly. This wiki page attempts to document recipes for you to use.

Removing a conffile

If you completely remove a configuration file, you should make sure it's also removed from the disk. However if the user has modified it, then you have to preserve the user's modifications somehow in case they wish to refer to them (see also Policy 10.7.3).

/!\ Warning: since dpkg 1.15.7.2 this can be done using dpkg-maintscript-helper (see its man page for details). Please use that tool instead of copy/pasting yet another version of this snippet in the Debian archive. dh_installdeb from debhelper has support for generating the required maintainer script snippets for dpkg-maintscript-helper, please use that.

Alternatively, it can be done in your preinst script when given the install or upgrade argument with a package version known to have the conffile that has been removed.

# Remove a no-longer used conffile
rm_conffile() {
    local PKGNAME="$1"
    local CONFFILE="$2"

    [ -e "$CONFFILE" ] || return 0

    local md5sum="$(md5sum $CONFFILE | sed -e 's/ .*//')"
    local old_md5sum="$(dpkg-query -W -f='${Conffiles}' $PKGNAME | \
            sed -n -e "\' $CONFFILE ' { s/ obsolete$//; s/.* //; p }")"
    if [ "$md5sum" != "$old_md5sum" ]; then
        echo "Obsolete conffile $CONFFILE has been modified by you."
        echo "Saving as $CONFFILE.dpkg-bak ..."
        mv -f "$CONFFILE" "$CONFFILE".dpkg-bak
    else
        echo "Removing obsolete conffile $CONFFILE ..."
        rm -f "$CONFFILE"
    fi
}

case "$1" in
install|upgrade)
    if dpkg --compare-versions "$2" le "$LASTVERSION"; then
        rm_conffile mypackage "/etc/pkg/conf.1"
        rm_conffile mypackage "/etc/pkg/conf.2"
    fi
esac

The function first checks to make sure the conffile actually exists on the disk, if the user has removed it already there's no point carrying on as everything's fine. We then check to see whether the user has modified it from the package's version or not. We obtain the current md5sum using the tool and compare that to one taken from the dpkg database, this is available and contains the old information during preinst so this is why we do this job here.

If the user has not modified the conffile, we simply remove it from the disk. If they have, we move it to a filename that can be easily identified by the user and ignored by policy-compliant tools such as run-parts.

In the main body of the preinst script, we check the arguments of the script and make sure that the version we're upgrading is a version that contained the conffile we need to remove. The reason we check for install as well is that we need to cope with being reinstalled when the last version was removed and not purged.

Remark that this script will not reinstantiate the purged conffile if there is an abort-upgrade condition. This can be done by using the following snippets in postinst, preinst and postrm (syntax kept the same for the three snippets, even if some args are not used in postinst and postrm).

In preinst:

# Remove a no-longer used conffile
rm_conffile() {
    local PKGNAME="$1"
    local CONFFILE="$2"

    [ -e "$CONFFILE" ] || return 0

    local md5sum="$(md5sum $CONFFILE | sed -e 's/ .*//')"
    local old_md5sum="$(dpkg-query -W -f='${Conffiles}' $PKGNAME | \
            sed -n -e "\' $CONFFILE ' { s/ obsolete$//; s/.* //; p }")"
    if [ "$md5sum" != "$old_md5sum" ]; then
        echo "Obsolete conffile $CONFFILE has been modified by you."
        echo "Saving as $CONFFILE.dpkg-bak ..."
        mv -f "$CONFFILE" "$CONFFILE".dpkg-bak
    else
        echo "Removing obsolete conffile $CONFFILE ..."
        mv -f "$CONFFILE" "$CONFFILE".dpkg-del
    fi
}

case "$1" in
install|upgrade)
    if dpkg --compare-versions "$2" le "$LASTVERSION"; then
        rm_conffile mypackage "/etc/pkg/conf.1"
        rm_conffile mypackage "/etc/pkg/conf.2"
    fi
esac

In postinst:

rm_conffile() {
    local PKGNAME="$1" # Unused
    local CONFFILE="$2"
    if [ -f "$CONFFILE".dpkg-del ]; then
        rm -f "$CONFFILE".dpkg-del
    fi
}
case "$1" in
install|upgrade)
    if dpkg --compare-versions "$2" le "$LASTVERSION"; then
        rm_conffile mypackage "/etc/pkg/conf.1"
        rm_conffile mypackage "/etc/pkg/conf.2"
    fi
esac

In postrm

rm_conffile() {
    local PKGNAME="$1" # Unused
    local CONFFILE="$2"

    if [ -f "$CONFFILE".dpkg-del ]; then
        mv -f "$CONFFILE".dpkg-del "$CONFFILE"
    fi
}
# See policy 6.6, item 3 and 5
case "$1" in
abort-install|abort-upgrade)
    if dpkg --compare-versions "$2" le "$LASTVERSION"; then
        rm_conffile mypackage "/etc/pkg/conf.1"
        rm_conffile mypackage "/etc/pkg/conf.2"
    fi
esac

Moving a conffile

If a conffile is moved from one location to another, you need to make sure you move across any changes the user has made. This may seem a simple change to the removal script at first, however that will result in the user being prompted by dpkg to approve the conffile edits they have made. If you change the conffile format, that may be desirable, however often you can do that kind of checking yourself.

/!\ Warning: since dpkg 1.15.7.2 this can be done using dpkg-maintscript-helper (see its man page for details). Please use that tool instead of copy/pasting yet another version of this snippet in the Debian archive. dh_installdeb from debhelper has support for generating the required maintainer script snippets for dpkg-maintscript-helper, please use that.

Alternatively here's a recipe for moving a conffile without triggering the dpkg question.

First we check in the preinst script whether the user has removed the conffile or not. This code looks rather similar to the code for removal, but with a few differences.

# Prepare to move a conffile without triggering a dpkg question
prep_mv_conffile() {
    local PKGNAME="$1"
    local CONFFILE="$2"

    [ -e "$CONFFILE" ] || return 0

    local md5sum="$(md5sum $CONFFILE | sed -e 's/ .*//')"
    local old_md5sum="$(dpkg-query -W -f='${Conffiles}' $PKGNAME | \
            sed -n -e "\' $CONFFILE ' { s/ obsolete$//; s/.* //; p }")"
    if [ "$md5sum" = "$old_md5sum" ]; then
        rm -f "$CONFFILE"
    fi
}

case "$1" in
install|upgrade)
    if dpkg --compare-versions "$2" le "$LASTVERSION"; then
        prep_mv_conffile mypackage "/etc/pkg_conf.1"
        prep_mv_conffile mypackage "/etc/pkg_conf.2"
    fi
esac

Much like removal, the function first checks to make sure the conffile exists on the disk and whether the user has changed it or not. If the user has not changed it, we simply remove the old version from the disk; this will tell us that we don't need to move their changes over. We handle the case of a user-changed conffile in postinst in a minute...

In the main body of the preinst script, we again check the arguments of the script and make sure that the version we're upgrading is a version that contained the conffile we need to move. Again, the reason we check for install as well is that we need to cope with being reinstalled when the last version was removed and not purged.

If we moved the conffile in the preinst script, dpkg would unpack the new package and see that the conffile has changed and ask the user what to do about it. We want to avoid that, so we let dpkg unpack the package without conflict. We can now handle moving the user-changed conffile into place in the postinst script.

# Move a conffile without triggering a dpkg question
mv_conffile() {
    local OLDCONFFILE="$1"
    local NEWCONFFILE="$2"

    [ -e "$OLDCONFFILE" ] || return 0

    echo "Preserving user changes to $NEWCONFFILE ..."
    mv -f "$NEWCONFFILE" "$NEWCONFFILE".dpkg-new
    mv -f "$OLDCONFFILE" "$NEWCONFFILE"
}

case "$1" in
configure)
    if dpkg --compare-versions "$2" le "$LASTVERSION"; then
        mv_conffile "/etc/pkg_conf.1" "/etc/pkg/conf.1"
        mv_conffile "/etc/pkg_conf.2" "/etc/pkg/conf.2"
    fi
esac

This function takes two arguments, the old and new filenames of the conffile. We check to make sure the old one still exists on the disk, if the user had not modified it the preinst script would have removed it for us. If the file still exists, we know that there were changes to it we want to preserve. We move the new conffile (as unpacked by dpkg) to a file the user can compare it with if they wish and move the old conffile into place with the new filename.

In the main body of the postinst script, we check the arguments of the script and make sure that the version we last configured (ie. upgrading) is a version that contained the conffile we need to move. If so, we call the function passing both the old and new filenames.

Remark that this script will not move back the displaced conffile if there is an abort-upgrade condition.

Additional information

The following bugs might be relevant: 335276 345112 304066 345113 346282 337992

The following threads might be useful too: http://lists.debian.org/debian-mentors/2006/02/msg00068.html, http://lists.debian.org/debian-mentors/2006/03/msg00131.html, http://lists.debian.org/debian-printing/2006/01/msg00058.html, http://lists.debian.org/debian-devel/2007/05/msg01070.html

Readers may be interested in this discussion of how to do things in Squeeze and later: