Update monitoring under Debian Linux

I’ve recently been migrating some of my servers at work from SuSE Linux over to Debian based distributions.  We can’t do this in every instance because enterprise vendor support for Debian doesn’t exist for every product we use.  We like migrating over to Debian for the following reasons:

  • Security patches are freely available and frequent.  One complaint I’ve had with Novell is the fact that they charge a yearly fee for product updates.  Many of those updates are the same updates that are free to everyone not using Novell’s products.
  • Debian, for a community based distribution, has a very slow turn over rate.  As a sysadmin, we already spend enough of our time trying to keep up with just the product and security patches that are necessary for the day to day usage of the systems, so we don’t want to invest additional time in upgrading base operating systems if we can avoid it.

In addition to the above reasons, I’ve also discovered that it’s pretty easy to get Debian to automatically inform me when I need to download/install updates.  For those of you who are running X windows/KDE/Gnome, you already know that there are desktop utilities that will do the same thing.  However, in a server environment,  you don’t necessarily have the luxury of running a full GUI desktop, so command line solutions are the only tools available.

This brings me to the point of my post.  I wanted to have a cron job give me a report of what updates and security patches were available to be installed on my system.  I further wanted the server to give me this information in an email message so that I don’t have to login to the server on a regular basis just to find out if updates are available (this is what I like to refer to as babysitting the server and I just don’t have time nor patience for it).  As it turns out, this is fairly easy to do with a bit of python code, but the details aren’t widely known.

In Ubuntu based distributions when you open an ssh connection to the server, it will happily display the following:

15 packages can be updated.
18 updates are security updates.

What’s curious here is that the system is obviously running some program in the background to update /etc/motd with the information I want to have in my report, but further searches online failed to identify what mechanism is responsible for the update.  After digging around on the web for an hour trying to figure this out, I eventually downloaded the source code for the update-notifier package which is the code base for the graphical desktop widget that prompts the user to install updates.

The source code for that package allowed me to eventually figure out that /usr/lib/update-notifier/apt-check was being run in the background.  This little python script spits out a very simple pair of numbers.  The first number is the total number of packages that can be upgraded and the second number is the number of security patches available.  Further reading of the script showed that it took another command  line option which would cause it to list each of the packages that needed to be upgraded.

Hmm.  I’ve never really written python before, but how hard could it be?  So after some fiddling with the script, I ended up with the following modified version of apt-check:

#!/usr/bin/python

#nice apt-get -s -o Debug::NoLocking=true upgrade | grep ^Inst

import apt_pkg
import os
import sys
from optparse import OptionParser
from gettext import gettext as _
import gettext
SYNAPTIC_PINFILE = "/var/lib/synaptic/preferences"

def clean(cache,depcache):
    # mvo: looping is too inefficient with the new auto-mark code
    #for pkg in cache.Packages:
    #    depcache.MarkKeep(pkg)
    depcache.Init()

def saveDistUpgrade(cache,depcache):
    """ this functions mimics a upgrade but will never remove anything """
    depcache.Upgrade(True)
    if depcache.DelCount > 0:
        clean(cache,depcache)
    depcache.Upgrade()

def _handleException(type, value, tb):
    sys.stderr.write("E: "+ "Unkown Error: '%s' (%s)" % (type,value))
    sys.exit(-1)

# -------------------- main ---------------------

# be nice
os.nice(19)

# setup a exception handler to make sure that uncaught stuff goes
# to the notifier
sys.excepthook = _handleException

# gettext
APP="update-notifier"
DIR="/usr/share/locale"
gettext.bindtextdomain(APP, DIR)
gettext.textdomain(APP)

# check arguments
parser = OptionParser()
parser.add_option("-p",
                  "--package-names",
                  action="store_true",
                  dest="show_package_names",
                  help="show the packages that are going to be installed/upgraded")
parser.add_option("-r",
                  "--report",
                  action="store_true",
                  dest="create_report",
                  help="Report on packages that need upgrading and summarize results")
(options, args) = parser.parse_args()
#print options.security_only

# init
apt_pkg.init()

# get caches
try:
    cache = apt_pkg.GetCache()
except SystemError, e:
    sys.stderr.write("E: "+ _("Error: Opening the cache (%s)") % e)
    sys.exit(-1)
depcache = apt_pkg.GetDepCache(cache)

# read the pin files
depcache.ReadPinFile()
# read the synaptic pins too
if os.path.exists(SYNAPTIC_PINFILE):
    depcache.ReadPinFile(SYNAPTIC_PINFILE)

# init the depcache
depcache.Init()

if depcache.BrokenCount > 0:
    sys.stderr.write("E: "+ _("Error: BrokenCount > 0"))
    sys.exit(-1)

# do the upgrade (not dist-upgrade!)
try:
    saveDistUpgrade(cache,depcache)
except SystemError, e:
    sys.stderr.write("E: "+ _("Error: Marking the upgrade (%s)") % e)
    sys.exit(-1)

# check for upgrade packages, we need to do it this way
# because of ubuntu #7907
upgrades = 0
security_updates = 0
for pkg in cache.Packages:
    if depcache.MarkedInstall(pkg) or depcache.MarkedUpgrade(pkg):
        # check if this is really a upgrade or a false positive
        # (workaround for ubuntu #7907)
        if depcache.GetCandidateVer(pkg) != pkg.CurrentVer:
                upgrades = upgrades + 1
                ver = depcache.GetCandidateVer(pkg)
                for (file, index) in ver.FileList:
                    if (file.Archive.endswith("-security") and
                        file.Origin == "Ubuntu"):
                        security_updates += 1

# print the number of upgrades
if options.show_package_names:
    pkgs = filter(lambda pkg: depcache.MarkedInstall(pkg) or depcache.MarkedUpgrade(pkg), cache.Packages)
    sys.stderr.write("\n".join(map(lambda p: p.Name, pkgs)))
elif options.create_report:
    sys.stderr.write("\n===========================================\n")
    sys.stderr.write("Packages to be upgraded:\n    ")
    pkgs = filter(lambda pkg: depcache.MarkedInstall(pkg) or depcache.MarkedUpgrade(pkg), cache.Packages)
    sys.stderr.write("\n    ".join(map(lambda p: p.Name, pkgs)))
    sys.stderr.write("\n===========================================\n")
    sys.stderr.write("packages needing upgrades: %s\n" % (upgrades))
    sys.stderr.write("security updates: %s\n" % (security_updates))
else:
    # print the number of regular upgrades and the number of
    # security upgrades
    sys.stderr.write("%s;%s" % (upgrades,security_updates))

sys.exit(0)

The above script isn’t quite finished yet.  The report it gives me has some visual artifiacts that could be eliminated from the resulting email.  Here’s a sample report:

Reading package lists... 0%

Reading package lists... 100%

Reading package lists... Done

Building dependency tree... 0%

Building dependency tree... 0%

Building dependency tree... 1%

Building dependency tree... 50%

Building dependency tree... 50%

Building dependency tree

Reading state information... 0%

Reading state information... 0%

Reading state information... Done

===========================================
Packages to be upgraded:
 libssl0.9.8
 openssl
===========================================
packages needing upgrades: 2
security updates: 0

For the moment, I’ll leave the finishing touches as an exercise to the reader.  I’ll make the changes when I have more free time in my schedule, but for the time does the bare minimum that I needed.

One Comment

  1. Hyman says:

    Thanks for finally talking about > Aperature.org � Blog Archive � Update monitoring under Debian Linux < Loved it!
    Check out my site – Hyman <Note: link removed – after having bad experiences with others trying to use my site for their personal gain, I don’t allow links in comments anymore. Thanks for the kind words though. – admin >