Purging outdated kernels on systems with unattended-upgrades

Whenever I install a production system/server I tend to enable full automatic updating, because
it's usually better that something breaks because of a failed update rather than leaving a production machine unpatched; and if you pick a rather stable distribution (Centos or Ubuntu LTS are good examples) chances that you'll end up with a broken system are fairly low.

Btw let's get back to the point: in such cases you may end up with a disk which was filled up with obsolete kernels. The following snippets allows you to get all outdated kernels which are not in use, so you can automagically pipe its output to an apt-get remove -y in a cron job.

That may sound trivial, but what happens if you're not careful (e.g. if you try deleting all kernels but the latest) is that you might try removing the currently running kernel - and it's pretty bad if that happens in an unattended cron job.

#!/usr/bin/env python

# print all installed kernel/headers/etc BUT the current and the latest;
# suitable to be used with apt-get remove.
# requires aptitude to be installed. Works on Ubuntu and probably Debian

from subprocess import check_output
import re

def runcmd(s):
    return check_output(s.split())

# should work for anything until kernel 3.100
kernel_version_pattern = re.compile("[23]\.\d{1,2}\.\d{1,2}-\d{1,3}")
def get_kernel_version(kernelstring):
    return kernel_version_pattern.search(kernelstring).group(0)

def get_providing_packages(pkg):
    return filter(lambda x: x != "",
        map(str.strip,
            runcmd('aptitude search ~i~P%s -F%%p' % pkg).split("\n")
            )
        )

def get_nonmatching_packages(package_list, excluding):
    return [k for k in package_list if not get_kernel_version(k) in
        excluding]


all_kernels = get_providing_packages("linux-image")
all_kernels.sort() # lexicographical

kernel_versions = map(get_kernel_version, all_kernels)
latest_version = kernel_versions[-1]
current_version = get_kernel_version(runcmd("uname -r"))

kernels_to_remove = get_nonmatching_packages(all_kernels, (current_version,
    latest_version))

impl_headers = get_providing_packages("linux-headers")

headers_to_remove = get_nonmatching_packages(impl_headers, (current_version,
    latest_version))

# This seems to be needed in Ubuntu >= 13.04
#base_headers_to_remove = [header.replace("-generic", "") for header in headers_to_remove]
#headers_to_remove += base_headers_to_remove

print " ".join(kernels_to_remove + headers_to_remove)

Suggested use in a cron job, of course, is just:

apt-get remove --purge -y `ubuntu_get_obsolete_kernel_and_headers_pkgs.py`

WARNING: recent changes to the way Ubuntu marks dependencies and provided names for kernel headers require that you uncomment a couple of lines in the script towards the end; just remove the # at the beginning of the lines 46 and 47.

UPDATE: in recent Ubuntu versions, just invoking apt-get autoremove --purge will do the trick. Such command will remove all automatically installed packages that are no longer required in the running system. It can remove other packages beyond outdated kernels, by the way.