Samstag, 6. Oktober 2012

Time Machine Failing - Get alerted by Growl

I had it now 2 times that my Time Machine backups were failing. First because of a dead hard drive, which I didn't notice for more than a month, second because of - I don't know. It simply failed.

Unfortunately there is no notification at all if this happens. No mail, no hint, no nothing.

So I had the idea to write a small perl script which is checking every hour (by cron) how old the last successful backup is, and give me a growl notification if it's older than 2 hours.

So without any further ado, this is the script. Copy and Paste it into a file called "time-machine-check.pl", make it executable and put into your crontab something like this:

7 * * * * /Users/shk/bin/time-machine-check.pl

to make it execute every 7 minutes after the hour.

#!/usr/bin/perl
use strict;
use warnings;
use Time::Local;
use File::Basename;
use Getopt::Long qw(:config no_ignore_case);
use Pod::Usage;

my $MAXAGO= 2;
my $DEFAULTS    = '/usr/bin/defaults';
my $LOCK_FILE   = '/private/tmp/.'.(basename $0);
my $TM_FILE     = '/private/var/db/.TimeMachine.Results';
my $KEY=          'BACKUP_COMPLETED_DATE';
my @READ_BACKUP = ($DEFAULTS, 'read',  $TM_FILE, $KEY);
my @GROWL_NOTIFY= ('/usr/local/bin/growlnotify',
    '-t' => 'Last backup too long ago!',
    '-n' => 'Time Machine',
    '-a' => 'Time Machine',
    '-w',
);

my (
    $test,
    $age,
);

help() unless GetOptions(
    't'          => \$test,
    'a=i'        => \$age,
    'h|help'     => \&help,
    'm|man'      => \&man,
);
$age||= $MAXAGO;

sub help { pod2usage(-verbose=>1); exit; }
sub man  { pod2usage(-verbose=>2); exit; }

# Get date of last successful backup
my $lastbackup= execute(@READ_BACKUP);

my ($y,$m,$d,$H,$M,$S)= ($lastbackup=~ /^(\d+)-(\d+)-(\d+)\s+(\d+):(\d+):(\d+)\s/);
my $ago= time - timelocal($S, $M, $H, $d, $m-1, $y);

# Convert to hours
$ago/= 3600;

# Prepare the message
my $msg;
if ($ago >= 24) {
    $msg= plural(int($ago/24), "%d day", "%d days");
}
elsif ($ago >= 1) {
    $msg= plural(int($ago), "%d hour", "%d hours");
}
else {
    $msg= plural(int($ago*60), "%d minute", "%d minutes");
}

# Do we already have a message and is it younger than 1 day?
my $second_message;
if (-e $LOCK_FILE) {
    my $second_message= 1;
    if (-M $LOCK_FILE < 1) {
        undef $msg unless $test;
    }
}

# Make the message sticky by default
my $sticky= '-s';
if ($ago < $age) {
    # unless it's a test message and not an alert
    $sticky= '';
    undef $msg unless $test;
}

# Now display the message
my $lf;
if ($sticky) {
    open $lf, '>>', $LOCK_FILE;
    print $lf $$,$/;
    close $lf;
    $lf= $LOCK_FILE unless $second_message;
    END {
        unlink $lf if defined $lf;
    }
}
execute(@GROWL_NOTIFY, $sticky, '-m' => "The last successful backup was done more than $msg ago.") if $msg;


sub plural {
    my($num, $singular, $plural, $zero)= @_;
    if ($num == 1) {
        return sprintf($singular, $num);
    }
    if ($zero and not $num) {
        return $zero;
    }
    return sprintf($plural, $num);
}

sub execute {
    open my $in, '-|', @_ or die "Failed to execute\n\t".join(' ',@_).": $!\n";
    $_= do { local $/; <$in> };
    close $in;
    return $_;
}

=head1 NAME

time-machine-check.pl - a small script to notify via growl when no backup was done.

=head1 SYNOPSIS

time-machine-check.p -a hours -t

=head1 DES‎CRIP‎TION

This is a small perl script to be used as a cronjob like so:

7 * * * * /usr/local/bin/time-machine-check.pl -a 2

which will give a growl notification in case the last backup is older than 2 hours.

=head1 OPTIONS

=over 4

=item B<-a> hours

Maximum allowed backup age before notifying.

=item B<-t>

Test option. Notify in any case.

=back

=head1 BUGS

none yet

=head1 TODO

let's see...

=author

time-machine-check-2012.mail.skeeve@xoxy.net

=cut