rpi-apt

Author Wim Nelis
Compatibility Xymon 4.2
Requirements Perl
Download None
Last Update 2018-03-11

Script rpi-apt.pl is a client-side script, which determines if there are any packages which can be upgraded at a Raspberry Pi running Raspbian. The upgrades are grouped into three classes, the security related upgrades, the normal upgrades and the upgrades which will not be installed. An alert is generated if at least one upgrade in the first two classes is found. The upgrades which are kept back are only reported, as a reminder. Script rpi-apt.pl will update the package list cache periodically.

Client side

Copy the following script into file /usr/lib/xymon/client/ext/rpi-apt.pl :

Show Code ⇲

Hide Code ⇱

#!/usr/bin/perl -w
#
# Script apt.pl is a xymon client-side script. It retrieves the list of
# outstanding patches for raspbian. These are divided into two groups,
# security-related and the rest. At the same time, this script will reguarly
# update the local cache of the package lists.
#
# Written by W.J.M. Nelis, wim.nelis@ziggo.nl, 201712
#
use strict ;
use Time::Piece ;
use Time::Local ;

#
# Installation constants.
# -----------------------
#
my $XyDisp  = $ENV{XYMSRV} ;		# Name of monitor server
my $XySend  = $ENV{XYMON} ;		# Monitor interface program
my $XyLife  = '+13h' ;			# Status lifetime, default 30m
my $FmtDate = "%Y.%m.%d %H:%M:%S" ;	# Default date format
   $FmtDate = $ENV{XYMONDATEFORMAT} if exists $ENV{XYMONDATEFORMAT} ;
my $HostName= `hostname` ;		# 'Source' of this test
chomp( $HostName ) ;			# Remove trailing newline
my $TestName= 'apt' ;			# Name of test

my @ColourOf= ( 'red', 'yellow', 'clear', 'green' ) ;
my %ColourOf= () ;
for ( my $i=0 ; $i<@ColourOf ; $i++ ) { $ColourOf{$ColourOf[$i]}= $i ; }

# Package list cache update parameters.
my $uplcFileName= '/var/cache/apt/pkgcache.bin' ;
my $OneDay= 24*60*60 ;			# Length of one day, expressed in [s]
my $uplcRefreshAge= 2 * $OneDay ;	# Minimum age of cache before refresh
my $uplcRefreshCmd= 'sudo apt-get update' ;	# Cache refresh command

#
# Updatable package list parameters.
my $upCmd= 'apt-get --just-print upgrade' ;	# Command
my $upCnt= 0 ;				# Total number of upgradable packages

#
# Global variables.
# -----------------
#
my $Now= localtime ;			# Timestamp of tests
   $Now= $Now->strftime( $FmtDate ) ;
my $Colour= $#ColourOf ;		# Test status
my $Result= '' ;			# Message to sent to Xymon

my %Class= ( High => 'red', Normal => 'yellow', Postponed => 'green' ) ;
my %UPL  = () ;
$UPL{$_} = {}			foreach ( keys %Class ) ;

my @Lines ;


sub min($$) { return $_[0] < $_[1] ? $_[0] : $_[1] ; }

#
# Function UpdatePackageListCache updates the package list cache, if it hasn't
# been updated for some time.
#
sub UpdatePackageListCache() {
  my (@Time,$Time) ;			# Time of last change of cache
  
  @Lines= `stat $uplcFileName` ;	# Cache file statistics
  return			unless @Lines ;	# Ignore error condition
  
  foreach ( @Lines ) {
    next		unless @Time= m/^Modify: (\d{4})-(\d+)-(\d+) (\d+):(\d+):(\d+)\./ ;
    $Time[1]-- ;			# Adjust month ordinal
    $Time= timelocal( reverse @Time ) ;	# Compute time stamp
    return		if time - $Time < $uplcRefreshAge ;
    
    `$uplcRefreshCmd` ;			# Update package list cache
    last ;				# Skip rest of lines
  }  # of foreach

}  # of UpdatePackageListCache

#
# Function GetListOfUpdates gets the list of the updatable packages. A typical
# line of output, specifying an updatable package, looks like the following
# line:
#  Inst python2.7 [2.7.13-2] (2.7.13-2+deb9u2 Raspbian:stable [armhf]) []
#
sub GetListOfUpdates() {
  my $InPostponed= 0 ;			# Flag: in list of kept back packages

  @Lines= `$upCmd` ;			# Current list of updatable packages
  foreach ( @Lines ) {
    chomp ;

 # Handle the list of packages which are not to be upgraded (which are kept
 # back). The format of such a list is as follows:
 #  The following packages have been kept back:
 #    firmware-brcm80211
 #
    if ( $InPostponed ) {
      if ( m/^\s\s(\S+)/ ) {
	$UPL{Postponed}{$1}= [ $1, '?', '?' ] ;
	next ;				# Line is handled
      } else {
	$InPostponed= 0 ;		# Enf o section found
      }  # of else
    } elsif ( m/^The following packages have been kept back/ ) {
      $InPostponed= 1 ;			# Start of section found
      next ;				# Line is handled
    }  # of elsif

    if ( m/^Inst (.+?) \[(.+?)] \((.+?) / ) {
      if ( m/securi/i ) {
	$UPL{High}{$1}  = [ $1, $2, $3 ] ;
      } else {
	$UPL{Normal}{$1}= [ $1, $2, $3 ] ;
      }  # of else
      $upCnt++ ;
      
    } elsif ( m/^Conf / ) {
#     next ;				# Do nothing

    } else {
      next			unless $upCnt ;
      print "$Now : $_\n" ;
    }  # of else
  }  # of foreach
}  # of GetListOfUpdates

#
# Function BuildMessage creates the message to be sent to Xymon. Any errors
# should be recorded in %ErrMsg (too).
#
sub BuildMessage() {
  my $SubClr ;

 #
 # Build a table showing a summary of the results.
 #
  $Result = $upCnt == 0 ? "\n" : "Summary\n\n" ;
  $Result.= "<table>\n" ;
  $Result.= " <tr><th>Priority</th><th>Count</th><th>Status</th></tr>\n" ;
  foreach ( sort keys %UPL ) {
    $SubClr= keys %{$UPL{$_}} ? $ColourOf{$Class{$_}} : $ColourOf{'green'} ;
    $Colour= min( $Colour, $SubClr ) ;
    $SubClr= $ColourOf[$SubClr] ;
    $Result.= " <tr><td>&nbsp;$_&nbsp;</td>" .
	      "<td align='right'>" . scalar(keys %{$UPL{$_}}) . "</td>" .
	      "<td>&nbsp;&nbsp;&$SubClr</tr>\n" ;
  }  # of foreach
  $Result.= " <tr><td>&nbsp;</td><td>-----</td><td>&nbsp;</td></tr>\n" ;
  $Result.= " <tr><td>&nbsp;Total&nbsp;</td><td align='right'>$upCnt</td>" .
	    "<td>&nbsp;&nbsp;&$ColourOf[$Colour]</td></tr>\n" ;
  $Result.= "</table>\n" ;

  return		if $upCnt == 0  and  keys(%{$UPL{Postponed}}) == 0 ;
  
  $Result.= "\nList of upgradeable packages\n\n" ;
  $Result.= "<table>\n" ;
  $Result.= " <tr><th>Package</th><th>Priority</th>" .
	    "<th>Current version</th><th>New version</th></tr>\n" ;
  foreach my $class ( sort keys %UPL ) {
    my $hr= $UPL{$class} ;
    foreach my $up ( sort keys %$hr ) {
      $Result.= " <tr><td>&nbsp;$up&nbsp;</td><td>&nbsp;$class&nbsp;</td>" .
		"<td>&nbsp;$$hr{$up}[1]&nbsp;</td><td>&nbsp;$$hr{$up}[2]&nbsp;</td></tr>\n" ;
    }  # of foreach
  }  # of foreach
  $Result.= "</table>\n" ;
}  # of BuildMessage

#
# Function InformXymon sends the message, in global variable $Result, to the
# Xymon server. Any error messages in %ErrMsg are prepended to the message and
# the status (colour) of the message is adapted accordingly.
#
sub InformXymon($) {
  $Colour= $ColourOf[$Colour] ;
  $Result= "\"status$XyLife $HostName.$TestName $Colour $Now\n" .
	   "<b>$_[0]</b>\n\n$Result\"\n" ;
  `$XySend $XyDisp $Result` ;		# Inform Xymon
}  # of InformXymon


#
# MAIN PROGRAM.
# =============
#
UpdatePackageListCache ;		# `apt-get update`
GetListOfUpdates ;			# Retrieve list of outstanding updates
BuildMessage ;				# Build message for xymon
InformXymon( 'Package updates' ) ;	# Send message to xymon server

Copy the configuration snippet below to file /usr/lib/xymon/client/etc/clientlaunch.d/rpi-apt.cfg:

#
# Test rpi-apt retrieves the list of upgradeable packages, and warns if there
# are any high (security) or low prio upgrades.
#
[rpi-apt]
	ENVFILE $XYMONCLIENTHOME/etc/xymonclient.cfg
	CMD $XYMONCLIENTHOME/ext/rpi-apt.pl
	LOGFILE $XYMONCLIENTLOGS/rpi-apt.log
	INTERVAL 6h

Restart the (client part of) xymon.

Server side

There is nothing to install or to configure at the server-side, except possibly for the alerting.

There are no known issues as per 2018.01.20.

  • 2018-01-20
    • Initial release
  • 2018-03-11
    • Report the packages which are kept back too
  • monitors/rpi-apt.txt
  • Last modified: 2018/03/11 10:15
  • by wnelis