Subversion Repositories sysadmin_scripts

Rev

Rev 169 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
166 rodolico 1
#! /usr/bin/env perl
2
 
3
use strict;
4
use warnings;
5
 
6
use YAML::Tiny; # pkg install p5-YAML-Tiny-1.74
7
 
8
BEGIN {
9
   use FindBin;
10
   use File::Spec;
11
   # use libraries from the directory this script is in
12
   use Cwd 'abs_path';
13
   use File::Basename;
14
   use lib dirname( abs_path( __FILE__ ) );
15
}
16
 
17
my $cwd = $FindBin::RealBin;
18
my $configFileName = $cwd . '/sync.yaml';
19
my $replicateScript = $cwd . '/replicate';
20
 
169 rodolico 21
my $TESTING=0;
166 rodolico 22
 
23
my $configuration;
24
 
25
use Data::Dumper;
26
use Email::Simple; # cpan install Email::Simple
27
 
28
# load Configuration File
29
# read the config file and return it
30
sub readConfig {
31
   my $filename = shift;
168 rodolico 32
   die "Config file $filename not found: $!" unless -f $filename;
166 rodolico 33
   my $yaml = YAML::Tiny->new( {} );
34
   if ( -f $filename ) {
35
      $yaml = YAML::Tiny->read( $filename );
36
   }
37
   return $yaml->[0];
38
}
39
 
40
 
41
 
42
# this calls gshred which will overwrite the file 3 times, then
43
# remove it.
44
# NOTE: this will not work on ZFS, since ZFS is CopyOnWrite (COW)
45
# so assuming /tmp is a ramdisk
46
sub shredFile {
47
   my $filename = shift;
48
   `/usr/local/bin/gshred -u -f -s 32 $filename`;
49
}
50
 
51
 
52
# runs a command, redirecting stderr to stdout (which it ignores)
53
# then returns 0 on success.
54
# if error, returns string describing error
55
sub runCommand {
56
   my $command = shift;
168 rodolico 57
   my $output = qx/$command 2>&1/;
166 rodolico 58
   if ($? == -1) {
168 rodolico 59
      return (-1, "failed to execute: $!" );
166 rodolico 60
   } elsif ($? & 127) {
168 rodolico 61
      return (-1,sprintf "child died with signal %d, %s coredump",
62
        ($? & 127),  ($? & 128) ? 'with' : 'without');
166 rodolico 63
   } else {
168 rodolico 64
      return ( $?>>8, sprintf "child exited with value %d", $? >> 8 ) if $? >> 8;
166 rodolico 65
   }
168 rodolico 66
   return (0, $output);
166 rodolico 67
}
68
 
69
# grabs the encryption key from the remote server, and uses it to unlock the 
70
# datasets, then mount the drives.
71
# a return of '' is success, anything else is an error
72
sub mountDrives {
73
   my $config = shift;
74
   # try to grab the file from the remote machine
75
   &runCommand( "scp $config->{remoteMachine}->{ip}:$config->{remoteMachine}->{encryptionKeyPath} $config->{localMachine}->{encryptionKeyPath}" );
76
   # If we do not have the encryption key, we need to abort
77
   return "Could not copy file $config->{remoteMachine}->{ip}:$config->{remoteMachine}->{encryptionKeyPath}, aborting" 
78
      unless -f $config->{'localMachine'}->{'encryptionKeyPath'};
79
   my $error = '';
168 rodolico 80
   my $output = '';
166 rodolico 81
   # load the key into zfs and unlock all volumes
168 rodolico 82
   ($error,$output) = &runCommand( "zfs load-key -a" );
166 rodolico 83
   # finally, remount all of the zfs shares which need the key
168 rodolico 84
   ($error,$output) = &runCommand( "zfs mount -a" ) unless $error;
166 rodolico 85
   # if we succeeded, we want to shred the keyfile
86
   &shredFile( $config->{localMachine}->{encryptionKeyPath} ) if -f $config->{localMachine}->{encryptionKeyPath};
87
   return $error;
88
}
89
 
90
# a very simple mailer, using Email::Simple to just get status messages out
91
sub sendMail {
92
   my ($message, $config, $subject ) = @_;
93
   $config->{'email'}->{'notify'} = 'root' unless $config->{'email'}->{'notify'};
94
   die "No message in outgoing message\n" unless $message;
95
   my $email = Email::Simple->create(
96
      header => [
97
         To     => $config->{'email'}->{'notify'},
98
         Subject=> $config->{'email'}->{'subject'} . ( $subject ? " - $subject" : '' ),
99
         From   => $config->{'email'}->{'from'}
100
      ],
101
      body => $message
102
   );
103
   $message = $email->as_string;
104
   `echo '$message' | sendmail $config->{'email'}->{'notify'}`;
105
}
106
 
107
# checks to see if we should be in maintenance mode
108
# if $remoteMachine->{'maintenanceMode'} exists, set mode
109
# otherwise, wait localMachine->{'waittime'} minutes, then check
110
# $localMachine->{'maintenanceMode'}.
111
# if neither exists, begin sync
112
sub checkMaintenance {
113
   my $config = shift;
114
   # see if maintenance is set on remote. If so, simply return the message
115
   if ( $config->{'remoteMachine'}->{'up'} ) {
168 rodolico 116
      my ($error, $output) = &runCommand( "ssh $configuration->{remoteMachine}->{ip} 'ls $configuration->{remoteMachine}->{maintenanceFlag}'" );
117
      if ( ! $error ) {
166 rodolico 118
         # remove the file from the remote server
119
         &runCommand( "ssh $configuration->{remoteMachine}->{ip} 'rm $configuration->{remoteMachine}->{maintenanceFlag}'" );
120
         # create a valid return, which will exit the program
121
         return "Maintenance Flag found on remote machine";
122
      }
123
   }
124
   # not on remote machine, so give them waitTime seconds to put it here
125
   # we'll loop, checking every $sleepTime seconds until our wait time
126
   # ($config->{'localMachine'}->{'waitTime'}) has expired
127
   my $sleepTime = 60;
128
   for ( my $i = $config->{'localMachine'}->{'waitTime'}; $i > 0; $i -= $sleepTime ) {
129
      sleep $sleepTime;
130
      # then look for the maintenance flag file on the local machine
131
      return "Maintenance Flag found on local machine" if -f $config->{'localMachine'}->{'maintenanceFlag'};
132
   }
133
   # no maintenance flags found, so return false
134
   return 0;
135
}
136
 
137
sub shutdownMachine {
138
   my $config = shift;
139
   my $subject = shift;
140
   push @_, "Shutting down";
141
   &sendMail( join( "\n", @_), $configuration, $subject );   
142
   &runCommand( "poweroff" ) unless $TESTING;
143
   die "Shutting down machine now\n";
144
}
145
 
146
# returns the current time as a string
147
sub currentTime {
148
   my $format = shift;
149
   # default to YY-MM-DD HH-MM-SS
150
   $format = '%Y-%m-%d %H-%M-%S' unless $format;
151
   use POSIX;
152
   return POSIX::strftime( $format, localtime() );
153
}
154
 
155
my @status;   
156
 
157
$configuration = &readConfig($configFileName);
158
 
159
&sendMail( "mc-009 has been started, " . &currentTime() . " checking for maintenance mode", $configuration );
160
# see if remote machine is up by sending one ping. Expect response in 5 seconds
168 rodolico 161
my ( $error,$output) = &runCommand( "ping -c 1 -t 5 " . $configuration->{'remoteMachine'}->{'ip'} );
162
$configuration->{'remoteMachine'}->{'up'} = ! $error;
166 rodolico 163
 
164
push @status, "remote machine ($configuration->{'remoteMachine'}->{'ip'}) is " . ( $configuration->{'remoteMachine'}->{'up'} ? 'Up' : 'Down' ) . "\n";
165
# check for maintenance flags, exit if we should go into mainteannce mode
169 rodolico 166
if ( my $result = &checkMaintenance( $configuration ) ) {
167
   push @status,$result;
168
   &sendMail( join( "\n", @status), $configuration, "Maintenance Mode" );
170 rodolico 169
   die;
169 rodolico 170
}
166 rodolico 171
 
172
# we can not connect to the remote server, so just shut down
173
&shutdownMachine( $configuration, "No connection to remote machine", @status ) unless $configuration->{'remoteMachine'}->{'up'};
174
 
175
# try to mount the datasets
168 rodolico 176
($error,$output) = &mountDrives( $configuration );
166 rodolico 177
if ( $error ) { # could not mount datasets
178
   push @status, $error;
168 rodolico 179
   &shutdownMachine( $configuration, "Mount Drive Error: [$output]", @status );
166 rodolico 180
}
181
 
182
&sendMail( "Backup has been started at " . &currentTime(), $configuration, "Backup Starting" );
183
push @status, "Backup started at: " . &currentTime();
184
 
185
 
186
# For each dataset, let's find the snapshots we need
187
foreach my $sourceDir ( keys %{$configuration->{'remoteMachine'}->{'dataset'}} ) {
188
   my $command = $replicateScript . ' ' .
189
                 $configuration->{'remoteMachine'}->{'ip'} . ':' . 
190
                 $configuration->{'remoteMachine'}->{'dataset'}->{$sourceDir} . '/' . $sourceDir . ' ' .
191
                 $configuration->{'localMachine'}->{'targetDataset'} . '/' . $sourceDir;
168 rodolico 192
   push @status, "=== Running $command at " . &currentTime();
193
   my ($error, $output) = &runCommand( $command );
194
   push @status, $output;
195
   push @status, "=== Completed $command with status $error at " . &currentTime();
166 rodolico 196
}
197
 
168 rodolico 198
push @status, "==== Backup finished at: " . &currentTime();
166 rodolico 199
 
200
&shutdownMachine( $configuration, "Backup Complete", @status );
201
 
202
1;