BackupPC RSS feed and email status HOWTO
-------------------------------------------------------------------------------
1. I created a script[see step 5] called BackupPC_statusUpdate modeled
on BackupPC_sendEmail. The script parses the backup status of each host,
creates an RSS feed and also sends the information by email.
BackupPC_statusUpdate resides in $BinDir (/usr/lib/backuppc/bin/ in my
case) and runs once each night.
2. Added $Conf{EMailStatusUserName} to the main config
file /var/lib/backuppc/conf/config.pl for email address(es) to receive
nightly status emails:
#
$Conf{EMailFromUserName} = 'backuppc';
+#
+# Destination address for daily positive status email.
+#
+$Conf{EMailStatusUserName} = '[EMAIL PROTECTED]';
#
# Destination address to an administrative user who will receive a
3. Added a call to BackupPC_statusUpdate in BackupPC_nightly (note the
addition of the semicolon on the first system command below):
if ( $opts{m} ) {
print("log BackupPC_nightly now running BackupPC_sendEmail\n");
! system("$BinDir/BackupPC_sendEmail");
+ # RSS and positive status email
+ #
+ print("log BackupPC_nightly now running BackupPC_statusUpdate\n");
+ system("$BinDir/BackupPC_statusUpdate");
}
4. Added header (to advertise feed to RSS readers e.g. Firefox) on my
backup server documentation webpage (this can be any spot viewable from
your intranet) at /var/www/localhost/htdocs/index.html. This is an
optional step. The link path is the place in the webroot that the main
script writes the xml file.
</style>
+<link rel="alternate" type="application/rss+xml"
+ href="backuppc/backuppc_status.xml" title="BackupPC RSS feed">
</head>
5. BackupPC_statusUpdate
#!/usr/bin/perl
#=============================================================
-*-perl-*-
#
# BackupPC_statusUpdate
#
# DESCRIPTION
#
# This script implements a positive status email and an RSS feed.
#
# The script is called from BackupPC_nightly.
#
# AUTHOR
# Travis Fraser [EMAIL PROTECTED]
#
# Credit to Rich Duzenbury for the original idea.
#
#
# Requires XML::RSS
#
# Edit the variable $serverName to suit depending on DNS status on your
# network.
# Edit the "use lib ..." in the 3rd line of code below.
# Edit the $base_url in the RSS section to reflect the correct path to
# the cgi page.
# Edit the "$rss->save ..." line near the end of the script to suit.
#
#========================================================================
use strict;
no utf8;
#
# The lib path needs to match that in the stock backuppc files.
#
use lib "/usr/lib/backuppc/lib";
use BackupPC::Lib;
use XML::RSS;
use Data::Dumper;
use Getopt::Std;
use DirHandle ();
use vars qw($Lang $TopDir $BinDir %Conf);
#########################################################################
# Variables
#########################################################################
my($fullTot, $fullSizeTot, $incrTot, $incrSizeTot, $str, $mesg,
$strNone, $strGood, $hostCntGood, $hostCntNone);
$hostCntGood = $hostCntNone = 0;
my $serverName = '192.168.1.3';
#########################################################################
# Initialize
#########################################################################
die("BackupPC::Lib->new failed\n") if ( !(my $bpc =
BackupPC::Lib->new) );
$TopDir = $bpc->TopDir();
$BinDir = $bpc->BinDir();
%Conf = $bpc->Conf();
$Lang = $bpc->Lang();
$bpc->ChildInit();
my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
if ( $err ) {
print("Can't connect to server ($err)\n");
exit(1);
}
#########################################################################
# Retrieve status of hosts
#########################################################################
my $reply = $bpc->ServerMesg("status hosts");
$reply = $1 if ( $reply =~ /(.*)/s );
my(%Status, %Info, %Jobs, @BgQueue, @UserQueue, @CmdQueue);
eval($reply);
#
# Ignore status related to admin and trash jobs
foreach my $host ( grep(/admin/, keys(%Status)) ) {
delete($Status{$host}) if ( $bpc->isAdminJob($host) );
}
delete($Status{$bpc->trashJob});
#########################################################################
# Set up RSS feed
#########################################################################
my $now = $bpc->timeStamp(time);
#
# The cgi page in this case is over HTTPS
#
my $base_url = 'https://' . $serverName . '/cgi-bin/BackupPC_Admin';
my $rss = new XML::RSS (version => '2.0', encoding => 'ISO-8859-1');
$rss->channel( title => 'BackupPC Server',
link => $base_url,
language => 'en',
lastBuildDate => $now
);
#########################################################################
# Calculate stats for each host
#########################################################################
foreach my $host ( sort(keys(%Status)) ) {
my($fullDur, $incrCnt, $incrAge, $fullSize, $fullRate,
$reasonHilite);
my($shortErr);
my @Backups = $bpc->BackupInfoRead($host);
my $fullCnt = $incrCnt = 0;
my $fullAge = $incrAge = -1;
$bpc->ConfigRead($host);
%Conf = $bpc->Conf();
next if ( $Conf{XferMethod} eq "archive" );
# Loop through each backup for $host
#
for ( my $i = 0 ; $i < @Backups ; $i++ ) {
if ( $Backups[$i]{type} eq "full" ) {
$fullCnt++;
if ( $fullAge < 0 || $Backups[$i]{startTime} > $fullAge ) {
$fullAge = $Backups[$i]{startTime};
$fullSize = $Backups[$i]{size} / (1024 * 1024);
$fullDur = $Backups[$i]{endTime} -
$Backups[$i]{startTime};
}
# total size for all backups / all hosts
$fullSizeTot += $Backups[$i]{size} / (1024 * 1024);
} else {
$incrCnt++;
if ( $incrAge < 0 || $Backups[$i]{startTime} > $incrAge ) {
$incrAge = $Backups[$i]{startTime};
}
$incrSizeTot += $Backups[$i]{size} / (1024 * 1024);
}
}
if ( $fullAge < 0 ) {
$fullAge = "";
$fullRate = "";
} else {
$fullAge = sprintf("%.1f", (time - $fullAge) / (24 * 3600));
$fullRate = sprintf("%.2f",
$fullSize / ($fullDur <= 0 ? 1 : $fullDur));
}
if ( $incrAge < 0 ) {
$incrAge = "";
} else {
$incrAge = sprintf("%.1f", (time - $incrAge) / (24 * 3600));
}
# increment total backup counts for all hosts
#
$fullTot += $fullCnt;
$incrTot += $incrCnt;
# size of full backup for the host
#
$fullSize = sprintf("%.2f", $fullSize / 1000);
# If status is not backup/restore in progress && an error exists,
# format short error message.
#
if ( $Status{$host}{state} ne "Status_backup_in_progress"
&& $Status{$host}{state} ne "Status_restore_in_progress"
&& $Status{$host}{error} ne "" ) {
($shortErr = $Status{$host}{error}) =~ s/(.{48}).*/$1.../;
$shortErr = " ($shortErr)";
}
# keep track of number of hosts that have backups
#
if ( @Backups == 0 ) {
$hostCntNone++;
} else {
$hostCntGood++;
}
my $host_state = $Lang->{$Status{$host}{state}};
my $host_last_attempt = $Lang->{$Status{$host}{reason}} .
$shortErr;
#
# assemble string for description of host status
#
$str = <<EOF;
Full Count: $fullCnt\t Full age/days: $fullAge
Full Size/GB: $fullSize\t Speed MB/sec: $fullRate
Incremental Count: $incrCnt\t Incremental Age/Days: $incrAge
State: $host_state\t Last Attempt: $host_last_attempt\n
EOF
#
# add rss item for each host
#
$rss->add_item(title => $host . ', ' .
$host_state . ', ' .
$host_last_attempt,
link => $base_url . '?host=' . $host,
description => $str);
# assemble message string for email
#
$mesg .= "Host: $host\n" . $str;
} # end foreach $host
# overall server stats (not used yet)
#
$fullSizeTot = sprintf("%.2f", $fullSizeTot / 1000);
$incrSizeTot = sprintf("%.2f", $incrSizeTot / 1000);
$rss->channel( description => "RSS feed for BackupPC: $hostCntGood
hosts with good backups");
# save rss file within webroot (somewhere writeable by $BackupPCUser)
#
$rss->save("/var/www/localhost/htdocs/backuppc/backuppc_status.xml");
#########################################################################
# Format and send mail
#########################################################################
if ( $Conf{EMailStatusUserName} ne "" ) {
$mesg = <<EOF;
To: $Conf{EMailStatusUserName}
Subject: BackupPC status: $hostCntGood hosts with good backups
${mesg}
EOF
SendMail($mesg);
}
sub SendMail
{
my($mesg) = @_;
my($from) = $Conf{EMailFromUserName};
local(*MAIL);
$from = "-f $from" if ( $from ne "" );
if ( !open(MAIL, "|$Conf{SendmailPath} -t $from") ) {
printf("Can't run sendmail ($Conf{SendmailPath}): $!\n");
return;
}
print MAIL $mesg;
close(MAIL);
}