Rev 167 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
#! /usr/bin/env perl
use strict;
use warnings;
use YAML::Tiny; # pkg install p5-YAML-Tiny-1.74
BEGIN {
use FindBin;
use File::Spec;
# use libraries from the directory this script is in
use Cwd 'abs_path';
use File::Basename;
use lib dirname( abs_path( __FILE__ ) );
}
my $cwd = $FindBin::RealBin;
my $configFileName = $cwd . '/sync.yaml';
my $replicateScript = $cwd . '/replicate';
my $TESTING=1;
my $configuration;
use Data::Dumper;
use Email::Simple; # cpan install Email::Simple
# load Configuration File
# read the config file and return it
sub readConfig {
my $filename = shift;
my $yaml = YAML::Tiny->new( {} );
if ( -f $filename ) {
$yaml = YAML::Tiny->read( $filename );
}
return $yaml->[0];
}
# this calls gshred which will overwrite the file 3 times, then
# remove it.
# NOTE: this will not work on ZFS, since ZFS is CopyOnWrite (COW)
# so assuming /tmp is a ramdisk
sub shredFile {
my $filename = shift;
`/usr/local/bin/gshred -u -f -s 32 $filename`;
}
# runs a command, redirecting stderr to stdout (which it ignores)
# then returns 0 on success.
# if error, returns string describing error
sub runCommand {
my $command = shift;
qx/$command 2>&1/;
if ($? == -1) {
return "failed to execute: $!";
} elsif ($? & 127) {
return sprintf "child died with signal %d, %s coredump",
($? & 127), ($? & 128) ? 'with' : 'without';
} else {
return sprintf "child exited with value %d", $? >> 8 if $? >> 8;
}
return 0;
}
# grabs the encryption key from the remote server, and uses it to unlock the
# datasets, then mount the drives.
# a return of '' is success, anything else is an error
sub mountDrives {
my $config = shift;
# try to grab the file from the remote machine
&runCommand( "scp $config->{remoteMachine}->{ip}:$config->{remoteMachine}->{encryptionKeyPath} $config->{localMachine}->{encryptionKeyPath}" );
# If we do not have the encryption key, we need to abort
return "Could not copy file $config->{remoteMachine}->{ip}:$config->{remoteMachine}->{encryptionKeyPath}, aborting"
unless -f $config->{'localMachine'}->{'encryptionKeyPath'};
my $error = '';
# load the key into zfs and unlock all volumes
$error = &runCommand( "zfs load-key -a" );
# finally, remount all of the zfs shares which need the key
$error = &runCommand( "zfs mount -a" ) unless $error;
# if we succeeded, we want to shred the keyfile
&shredFile( $config->{localMachine}->{encryptionKeyPath} ) if -f $config->{localMachine}->{encryptionKeyPath};
return $error;
}
# a very simple mailer, using Email::Simple to just get status messages out
sub sendMail {
my ($message, $config, $subject ) = @_;
$config->{'email'}->{'notify'} = 'root' unless $config->{'email'}->{'notify'};
die "No message in outgoing message\n" unless $message;
my $email = Email::Simple->create(
header => [
To => $config->{'email'}->{'notify'},
Subject=> $config->{'email'}->{'subject'} . ( $subject ? " - $subject" : '' ),
From => $config->{'email'}->{'from'}
],
body => $message
);
$message = $email->as_string;
`echo '$message' | sendmail $config->{'email'}->{'notify'}`;
}
# checks to see if we should be in maintenance mode
# if $remoteMachine->{'maintenanceMode'} exists, set mode
# otherwise, wait localMachine->{'waittime'} minutes, then check
# $localMachine->{'maintenanceMode'}.
# if neither exists, begin sync
sub checkMaintenance {
my $config = shift;
# see if maintenance is set on remote. If so, simply return the message
if ( $config->{'remoteMachine'}->{'up'} ) {
if ( ! &runCommand( "ssh $configuration->{remoteMachine}->{ip} 'ls $configuration->{remoteMachine}->{maintenanceFlag}'" ) ) {
# remove the file from the remote server
&runCommand( "ssh $configuration->{remoteMachine}->{ip} 'rm $configuration->{remoteMachine}->{maintenanceFlag}'" );
# create a valid return, which will exit the program
return "Maintenance Flag found on remote machine";
}
}
# not on remote machine, so give them waitTime seconds to put it here
# we'll loop, checking every $sleepTime seconds until our wait time
# ($config->{'localMachine'}->{'waitTime'}) has expired
my $sleepTime = 60;
for ( my $i = $config->{'localMachine'}->{'waitTime'}; $i > 0; $i -= $sleepTime ) {
sleep $sleepTime;
# then look for the maintenance flag file on the local machine
return "Maintenance Flag found on local machine" if -f $config->{'localMachine'}->{'maintenanceFlag'};
}
# no maintenance flags found, so return false
return 0;
}
sub shutdownMachine {
my $config = shift;
my $subject = shift;
push @_, "Shutting down";
&sendMail( join( "\n", @_), $configuration, $subject );
&runCommand( "poweroff" ) unless $TESTING;
die "Shutting down machine now\n";
}
# returns the current time as a string
sub currentTime {
my $format = shift;
# default to YY-MM-DD HH-MM-SS
$format = '%Y-%m-%d %H-%M-%S' unless $format;
use POSIX;
return POSIX::strftime( $format, localtime() );
}
my @status;
$configuration = &readConfig($configFileName);
&sendMail( "mc-009 has been started, " . ¤tTime() . " checking for maintenance mode", $configuration );
# see if remote machine is up by sending one ping. Expect response in 5 seconds
$configuration->{'remoteMachine'}->{'up'} =
! &runCommand( "ping -c 1 -t 5 " . $configuration->{'remoteMachine'}->{'ip'} );
push @status, "remote machine ($configuration->{'remoteMachine'}->{'ip'}) is " . ( $configuration->{'remoteMachine'}->{'up'} ? 'Up' : 'Down' ) . "\n";
# check for maintenance flags, exit if we should go into mainteannce mode
#if ( my $result = &checkMaintenance( $configuration ) ) {
# push @status,$result;
# &sendMail( join( "\n", @status), $configuration, "Maintenance Mode" );
# exit;
#}
# we can not connect to the remote server, so just shut down
&shutdownMachine( $configuration, "No connection to remote machine", @status ) unless $configuration->{'remoteMachine'}->{'up'};
# try to mount the datasets
my $error = &mountDrives( $configuration );
if ( $error ) { # could not mount datasets
push @status, $error;
&shutdownMachine( $configuration, "Mount Drive Error", @status );
}
&sendMail( "Backup has been started at " . ¤tTime(), $configuration, "Backup Starting" );
push @status, "Backup started at: " . ¤tTime();
# For each dataset, let's find the snapshots we need
foreach my $sourceDir ( keys %{$configuration->{'remoteMachine'}->{'dataset'}} ) {
my $command = $replicateScript . ' ' .
$configuration->{'remoteMachine'}->{'ip'} . ':' .
$configuration->{'remoteMachine'}->{'dataset'}->{$sourceDir} . '/' . $sourceDir . ' ' .
$configuration->{'localMachine'}->{'targetDataset'} . '/' . $sourceDir;
print "$command\n";
}
push @status, "Backup finished at: " . ¤tTime();
&shutdownMachine( $configuration, "Backup Complete", @status );
1;