diff options
Diffstat (limited to 'files')
| -rwxr-xr-x | files/checkbackups.pl | 2 | ||||
| -rw-r--r-- | files/nagios_plugins/duplicity/README.md | 24 | ||||
| -rw-r--r-- | files/nagios_plugins/duplicity/backupninja_duplicity_freshness.sh | 268 | ||||
| -rw-r--r-- | files/nagios_plugins/duplicity/check_backupninja_duplicity.py | 123 | 
4 files changed, 416 insertions, 1 deletions
| diff --git a/files/checkbackups.pl b/files/checkbackups.pl index 80fc07f..dba2b4a 100755 --- a/files/checkbackups.pl +++ b/files/checkbackups.pl @@ -61,7 +61,7 @@ if (defined($opt_o)) {  	@hosts=qx{hostname -f};  } else {  	# XXX: this should be a complete backup registry instead -	@hosts=qx{ls $backupdir}; +	@hosts=qx{ls $backupdir | grep -v lost+found};  }  chdir($backupdir); diff --git a/files/nagios_plugins/duplicity/README.md b/files/nagios_plugins/duplicity/README.md new file mode 100644 index 0000000..1cd349a --- /dev/null +++ b/files/nagios_plugins/duplicity/README.md @@ -0,0 +1,24 @@ +duplicity-backup-status +======================= + +Backupninja generates duplicity configfiles, this nagios plugin can check their freshness. Currently only the config files generated by backupninja can be parsed and we depend on that. + +## Prerequisites + +Make sure you have python-argparse installed (yes an extra dependency, getopt doubles the amount of code, so I gave up on that). The Python script will look for the duplicity_freshness.sh shell script in /usr/local/lib/nagios/plugins/ or /usr/lib/nagios/plugins/ make sure you copy it there and make executable.  + +## Getting started + +Run the python script from your nagios. Don't forget to specify some extras like when warnings or criticalities should be emerged. + +-  -w WARNINC   Number of hours allowed for incremential backup warning level default 28 +-  -W WARNFULL  Number of hours allowed for incremential backup critical level default 40 +-  -c CRITINC   Number of days allowed for full backup warning level default 52 +-  -C CRITFULL  Number of days allowed for full backup critical level default 60 + + +## TODO: + +- make it cuter, tidy up +- make it more robust +- support other config backends as backupninja - this can be done by writing more scripts like backupninja_duplicity_freshness.sh and parsing an extra parameter diff --git a/files/nagios_plugins/duplicity/backupninja_duplicity_freshness.sh b/files/nagios_plugins/duplicity/backupninja_duplicity_freshness.sh new file mode 100644 index 0000000..7af2bf7 --- /dev/null +++ b/files/nagios_plugins/duplicity/backupninja_duplicity_freshness.sh @@ -0,0 +1,268 @@ +#!/bin/bash +# -*- mode: sh; sh-basic-offset: 3; indent-tabs-mode: nil; -*- +# vim: set filetype=sh sw=3 sts=3 expandtab autoindent: + +# Load backupninja library/helpers, because why reinventing the wheel? [Because my wheels weren't round] +# some duplication is to be expected +# this is only supposed to work with duplicity + +## Functions +# simple lowercase function +function tolower() { +   echo "$1" | tr '[:upper:]' '[:lower:]' +} + +# we grab the current time once, since processing +# all the configs might take more than an hour. +nowtime=`LC_ALL=C date +%H` +nowday=`LC_ALL=C date +%d` +nowdayofweek=`LC_ALL=C date +%A` +nowdayofweek=`tolower "$nowdayofweek"` + +conffile="/etc/backupninja.conf" + +# find $libdirectory +libdirectory=`grep '^libdirectory' $conffile | /usr/bin/awk '{print $3}'` +if [ -z "$libdirectory" ]; then +   if [ -d "/usr/lib/backupninja" ]; then +      libdirectory="/usr/lib/backupninja" +   else +      echo "Could not find entry 'libdirectory' in $conffile." +      fatal "Could not find entry 'libdirectory' in $conffile." +   fi +else +   if [ ! -d "$libdirectory" ]; then +      echo "Lib directory $libdirectory not found." +      fatal "Lib directory $libdirectory not found." +   fi +fi + +. $libdirectory/tools + +setfile $conffile + +# get global config options (second param is the default) +getconf configdirectory /etc/backup.d +getconf scriptdirectory /usr/share/backupninja +getconf reportdirectory +getconf reportemail +getconf reporthost +getconf reportspace +getconf reportsuccess yes +getconf reportinfo no +getconf reportuser +getconf reportwarning yes +getconf loglevel 3 +getconf when "Everyday at 01:00" +defaultwhen=$when +getconf logfile /var/log/backupninja.log +getconf usecolors "yes" +getconf SLAPCAT /usr/sbin/slapcat +getconf LDAPSEARCH /usr/bin/ldapsearch +getconf RDIFFBACKUP /usr/bin/rdiff-backup +getconf CSTREAM /usr/bin/cstream +getconf MYSQLADMIN /usr/bin/mysqladmin +getconf MYSQL /usr/bin/mysql +getconf MYSQLHOTCOPY /usr/bin/mysqlhotcopy +getconf MYSQLDUMP /usr/bin/mysqldump +getconf PGSQLDUMP /usr/bin/pg_dump +getconf PGSQLDUMPALL /usr/bin/pg_dumpall +getconf PGSQLUSER postgres +getconf GZIP /bin/gzip +getconf GZIP_OPTS --rsyncable +getconf RSYNC /usr/bin/rsync +getconf admingroup root + +if [ ! -d "$configdirectory" ]; then +   echo "Configuration directory '$configdirectory' not found." +   fatal "Configuration directory '$configdirectory' not found." +fi + +# get the duplicity configuration +function get_dupconf(){ +   setfile $1 +   getconf options +   getconf testconnect yes +   getconf nicelevel 0 +   getconf tmpdir +    +   setsection gpg +   getconf password +   getconf sign no +   getconf encryptkey +   getconf signkey +    +   setsection source +   getconf include +   getconf vsnames all +   getconf vsinclude +   getconf exclude +    +   setsection dest +   getconf incremental yes +   getconf increments 30 +   getconf keep 60 +   getconf keepincroffulls all +   getconf desturl +   getconf awsaccesskeyid +   getconf awssecretaccesskey +   getconf cfusername +   getconf cfapikey +   getconf cfauthurl +   getconf ftp_password +   getconf sshoptions +   getconf bandwidthlimit 0 +   getconf desthost +   getconf destdir +   getconf destuser +   destdir=${destdir%/} +} + +### some voodoo to mangle the correct commands + +function mangle_cli(){ + +   execstr_options="$options " +   execstr_source= +   if [ -n "$desturl" ]; then +      [ -z "$destuser" ] || warning 'the configured destuser is ignored since desturl is set' +      [ -z "$desthost" ] || warning 'the configured desthost is ignored since desturl is set' +      [ -z "$destdir" ] || warning 'the configured destdir is ignored since desturl is set' +      execstr_serverpart="$desturl" +   else +      execstr_serverpart="scp://$destuser@$desthost/$destdir" +   fi +    +    +   ### Symmetric or asymmetric (public/private key pair) encryption +   if [ -n "$encryptkey" ]; then +      execstr_options="${execstr_options} --encrypt-key $encryptkey" +   fi +    +   ### Data signing (or not) +   if [ "$sign" == yes ]; then +      # duplicity is not able to sign data when using symmetric encryption +      [ -n "$encryptkey" ] || fatal "The encryptkey option must be set when signing." +      # if needed, initialize signkey to a value that is not empty (checked above) +      [ -n "$signkey" ] || signkey="$encryptkey" +      execstr_options="${execstr_options} --sign-key $signkey" +   fi +    +   ### Temporary directory +   precmd= +   if [ -n "$tmpdir" ]; then +      if [ ! -d "$tmpdir" ]; then +         #info "Temporary directory ($tmpdir) does not exist, creating it." +         mkdir -p "$tmpdir" +         [ $? -eq 0 ] || fatal "Could not create temporary directory ($tmpdir)." +         chmod 0700 "$tmpdir" +      fi +      #info "Using $tmpdir as TMPDIR" +      precmd="${precmd}TMPDIR=$tmpdir " +   fi +    +   ### Source +    +   set -o noglob +    +   # excludes +   SAVEIFS=$IFS +   IFS=$(echo -en "\n\b") +   for i in $exclude; do +      str="${i//__star__/*}" +      execstr_source="${execstr_source} --exclude '$str'" +   done +   IFS=$SAVEIFS +    +   # includes +   SAVEIFS=$IFS +   IFS=$(echo -en "\n\b") +   for i in $include; do +      [ "$i" != "/" ] || fatal "Sorry, you cannot use 'include = /'" +      str="${i//__star__/*}" +      execstr_source="${execstr_source} --include '$str'" +   done +   IFS=$SAVEIFS +    +   set +o noglob +    +   execstr_options="${execstr_options} --ssh-options '$sshoptions'" +   if [ "$bandwidthlimit" != 0 ]; then +      [ -z "$desturl" ] || warning 'The bandwidthlimit option is not used when desturl is set.' +      execstr_precmd="trickle -s -d $bandwidthlimit -u $bandwidthlimit" +   fi +} + +#function findlastdates(){ +#   outputfile=$1 +#   lastfull=0 +#   lastinc=0 +#   backuptime=0 +#    +#   while read line; do +#      atime=0 +#      arr=() +#      sort='' +#      test=$(echo $line|awk '{if (NF == 7); if ($1 == "Full" || $1 == "Incremental") {print $4, $3, $6, $5}}'  ) +#    +#      if [ -n "$test"  ]; then +#         backuptime=$(date -u -d "$test" +%s) +#    +#         arr=($(echo $line|awk '{print $1, $2, $3, $4, $5, $6}')) +#         if [ ${arr[0]} == "Incremental" ] && [ "$lastinc" -lt "$backuptime" ] ; then +#            lastinc=$backuptime +#         elif [ ${arr[0]} == "Full" ] && [ "$lastfull" -lt "$backuptime" ] ; then +#            lastfull=$backuptime +#         fi +#    +#      fi +#    +#   done < $outputfile +#      # a full backup can be seen as incremental too +#      lastinc=$(echo $lastinc | awk 'max=="" || $1 > max {max=$1} END{ print max}') +#} + +function check_status() { +   grep -q 'No orphaned or incomplete backup sets found.' $1 +   if [ $? -ne 0 ] ; then +     exit 2 +   fi +} + +## +## this function handles the freshness check of a backup action +## + +function process_action() { +   local file="$1" +   local suffix="$2" +   setfile $file +   get_dupconf $1 +   mangle_cli +    +   outputfile=`maketemp backupout` +   export PASSPHRASE=$password +   export FTP_PASSWORD=$ftp_password +   output=` su -c \ +            "$execstr_precmd duplicity $execstr_options collection-status $execstr_serverpart >$outputfile 2>&1"` +   exit_code=$? +   echo -n $outputfile + +   #check_status +   #findlastdates +} + +files=`find $configdirectory -follow -mindepth 1 -maxdepth 1 -type f ! -name '.*.swp' | sort -n` + +for file in $files; do +   [ -f "$file" ] || continue +   suffix="${file##*.}" +   base=`basename $file` +   if [ "${base:0:1}" == "0" -o "$suffix" == "disabled" ]; then +      continue +   fi +   if [ -e "$scriptdirectory/$suffix" -a "$suffix" == "dup" ]; then +      process_action $file $suffix +   fi +done + diff --git a/files/nagios_plugins/duplicity/check_backupninja_duplicity.py b/files/nagios_plugins/duplicity/check_backupninja_duplicity.py new file mode 100644 index 0000000..8ed9ce6 --- /dev/null +++ b/files/nagios_plugins/duplicity/check_backupninja_duplicity.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python + +# Inspired by Arne Schwabe <arne-nagios@rfc2549.org> [with BSD license] +# Inspired by backupninja [that's gpl some version] +# minor changes by someon who doesn't understand all the license quirks + +from subprocess import Popen,PIPE +import sys +import time +import os +import argparse +import getopt + +def main(): +    # getopt = much more writing +    parser = argparse.ArgumentParser(description='Nagios Duplicity status checker') + +    parser.add_argument("-w", dest="warninc", default=28, type=int,  +                        help="Number of hours allowed for incremential backup warning level, default 28") +    parser.add_argument("-W", dest="warnfull", default=31, type=int,  +                        help="Number of days allowed for full backup warning level, default 31") +    parser.add_argument("-c", dest="critinc", default=52, type=int,  +                        help="Number of hours allowed for incremential backup critical level, default 52") +    parser.add_argument("-C", dest="critfull", default=33, type=int,  +                        help="Number of days allowed for full backup critical level, default 33") +    args = parser.parse_args() +     +    okay = 0 + +    # *sigh* check_output is from python 2.7 and onwards. Debian, upgrade yourself. +    #output , err = check_output(['/root/freshness.sh']) + +    if os.path.isfile("/usr/lib/nagios/plugins/backupninja_duplicity_freshness.sh") and os.access("/usr/lib/nagios/plugins/backupninja_duplicity_freshness.sh", os.X_OK): +      checkstatus, err = Popen(['/bin/bash', '/usr/lib/nagios/plugins/backupninja_duplicity_freshness.sh'], stdout=PIPE, stderr=PIPE, env={'HOME': '/root', 'PATH': os.environ['PATH']}).communicate() +    elif os.path.isfile("/usr/local/lib/nagios/plugins/backupninja_duplicity_freshness.sh") and os.access("/usr/local/lib/nagios/plugins/backupninja_duplicity_freshness.sh", os.X_OK): +      checkstatus, err = Popen(['/bin/bash', '/usr/local/lib/nagios/plugins/backupninja_duplicity_freshness.sh'], stdout=PIPE, stderr=PIPE, env={'HOME': '/root', 'PATH': os.environ['PATH']}).communicate() + +    # Don't use exec(), popen(), etc. to execute external commands without explicity using the full path of the external program.  Hijacked search path could be problematic. +    #checkstatus, err = Popen(['/bin/bash', './freshness.sh'], stdout=PIPE, stderr=PIPE, env={'HOME': '/root', 'PATH': os.environ['PATH']}).communicate() + +    #another sigh: Debian testing, upgrade yourself, this is only needed because Debian testing uses duplicity 0.6.18-3  +    # open file read/write +    f = open (checkstatus,"r") +    checklines = f.readlines() +    f.close() + +    # remove the line that says Import of duplicity.backends.giobackend Failed: No module named gio +    f = open(checkstatus,"w") +    for line in checklines: +      if not 'Import of duplicity.backends.giobackend Failed: No module named gio' in line: +        f.write(line) +    f.close() + +    output = open(checkstatus).read() + +    lastfull, lastinc = findlastdates(output) + +    sincelastfull = time.time() - lastfull  +    sincelastinc  =  time.time() - lastinc  + +    msg = "OK: " +     +    if sincelastfull > (args.warnfull * 24 * 3600) or sincelastinc > (args.warninc * 3600): +        okay = 1 +        msg = "WARNING: " +    if sincelastfull > (args.critfull * 24 * 3600) or sincelastinc > (args.critinc * 3600): +        okay = 2 +        msg = "CRITICAL: " +    if not checkoutput(output): +        okay = max(okay,1) +        msg = "WARNING: duplicity output: %s " % repr(output) +    if err: +        okay=2 +        msg = "Unexpected output: %s, " % repr(err) + +    print msg, "last full %s ago, last incremential %s ago|lastfull=%d, lastinc=%d" % ( formattime(sincelastfull), formattime(sincelastinc), sincelastfull, sincelastinc) +     +    #clean up cruft +    os.remove(checkstatus) +    sys.exit(okay) + +def checkoutput(output): +    if not 'No orphaned or incomplete backup sets found.' in output: +        return False +     +    return True + +def formattime(seconds): +    days = seconds / (3600 * 24) +    hours = seconds / 3600 % 24 + +    if days: +        return "%d days %d hours" % (days,hours) +    else: +        return "%d hours" % hours + + +def findlastdates(output): +    lastfull = 0 +    lastinc = 0 + +    for line in output.split("\n"): +        parts = line.split() + +        # ['Incremental', 'Sun', 'Oct', '31', '03:00:04', '2010', '1'] +        if len (parts) == 7 and parts[0] in ["Full","Incremental"]: +            foo = time.strptime(" ".join(parts[1:6]),"%a %b %d %H:%M:%S %Y") +     +            backuptime =  time.mktime(foo) +     +            if parts[0] == "Incremental" and lastinc < backuptime: +                lastinc = backuptime +            elif parts[0] == "Full" and lastfull < backuptime: +                lastfull = backuptime +         + +    # Count a full backup as incremental backup +    lastinc = max(lastfull,lastinc) +    return (lastfull, lastinc) +	 + +if __name__=='__main__': +   main() | 
