Line 1... |
Line 1... |
1 |
#! /usr/bin/env perl
|
1 |
#! /usr/bin/env perl
|
2 |
|
2 |
|
3 |
use strict;
|
3 |
use strict;
|
4 |
use warnings;
|
4 |
use warnings;
|
5 |
|
5 |
|
6 |
use YAML::Tiny; # pkg install p5-YAML-Tiny-1.74
|
- |
|
7 |
|
6 |
|
8 |
BEGIN {
|
7 |
BEGIN {
|
9 |
use FindBin;
|
8 |
use FindBin;
|
10 |
use File::Spec;
|
9 |
use File::Spec;
|
11 |
# use libraries from the directory this script is in
|
10 |
# use libraries from the directory this script is in
|
12 |
use Cwd 'abs_path';
|
11 |
use Cwd 'abs_path';
|
13 |
use File::Basename;
|
12 |
use File::Basename;
|
14 |
use lib dirname( abs_path( __FILE__ ) );
|
13 |
use lib dirname( abs_path( __FILE__ ) );
|
15 |
}
|
14 |
}
|
16 |
|
15 |
|
- |
|
16 |
use YAML::Tiny; # pkg install p5-YAML-Tiny-1.74
|
- |
|
17 |
use Data::Dumper;
|
- |
|
18 |
use Email::Simple; # cpan install Email::Simple
|
- |
|
19 |
|
17 |
my $cwd = $FindBin::RealBin;
|
20 |
my $cwd = $FindBin::RealBin;
|
18 |
my $configFileName = $cwd . '/sync.yaml';
|
21 |
my $configFileName = $cwd . '/sync.yaml';
|
19 |
my $replicateScript = $cwd . '/replicate';
|
22 |
my $replicateScript = $cwd . '/replicate';
|
20 |
|
23 |
|
21 |
my $TESTING=0;
|
- |
|
22 |
|
- |
|
23 |
my $configuration;
|
24 |
my $configuration;
|
24 |
|
25 |
|
25 |
use Data::Dumper;
|
- |
|
26 |
use Email::Simple; # cpan install Email::Simple
|
- |
|
27 |
|
26 |
|
28 |
# load Configuration File
|
27 |
# load Configuration File
|
29 |
# read the config file and return it
|
28 |
# read the config file and return it
|
30 |
sub readConfig {
|
29 |
sub readConfig {
|
31 |
my $filename = shift;
|
30 |
my $filename = shift;
|
Line 68... |
Line 67... |
68 |
|
67 |
|
69 |
# grabs the encryption key from the remote server, and uses it to unlock the
|
68 |
# grabs the encryption key from the remote server, and uses it to unlock the
|
70 |
# datasets, then mount the drives.
|
69 |
# datasets, then mount the drives.
|
71 |
# a return of '' is success, anything else is an error
|
70 |
# a return of '' is success, anything else is an error
|
72 |
sub mountDrives {
|
71 |
sub mountDrives {
|
73 |
my $config = shift;
|
72 |
my $configuration = shift;
|
- |
|
73 |
return (0, 'No encrypted target found' ) unless defined( $configuration->{'target'}->{'encryptionKeyPath'} ) && $configuration->{'target'}->{'encryptionKeyPath'};
|
74 |
# try to grab the file from the remote machine
|
74 |
# try to grab the file from the remote machine
|
75 |
&runCommand( "scp $config->{remoteMachine}->{ip}:$config->{remoteMachine}->{encryptionKeyPath} $config->{localMachine}->{encryptionKeyPath}" );
|
75 |
&runCommand( "scp $configuration->{remoteMachine}->{ip}:$configuration->{remoteMachine}->{encryptionKeyPath} $configuration->{localMachine}->{encryptionKeyPath}" );
|
76 |
# If we do not have the encryption key, we need to abort
|
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"
|
77 |
return "Could not copy file $configuration->{remoteMachine}->{ip}:$configuration->{remoteMachine}->{encryptionKeyPath}, aborting"
|
78 |
unless -f $config->{'localMachine'}->{'encryptionKeyPath'};
|
78 |
unless -f $configuration->{'target'}->{'encryptionKeyPath'};
|
79 |
my $error = '';
|
79 |
my $error = '';
|
80 |
my $output = '';
|
80 |
my $output = '';
|
81 |
# load the key into zfs and unlock all volumes
|
81 |
# load the key into zfs and unlock all volumes
|
82 |
($error,$output) = &runCommand( "zfs load-key -a" );
|
82 |
($error,$output) = &runCommand( "zfs load-key -a" );
|
83 |
# finally, remount all of the zfs shares which need the key
|
83 |
# finally, remount all of the zfs shares which need the key
|
84 |
($error,$output) = &runCommand( "zfs mount -a" ) unless $error;
|
84 |
($error,$output) = &runCommand( "zfs mount -a" ) unless $error;
|
85 |
# if we succeeded, we want to shred the keyfile
|
85 |
# if we succeeded, we want to shred the keyfile
|
86 |
&shredFile( $config->{localMachine}->{encryptionKeyPath} ) if -f $config->{localMachine}->{encryptionKeyPath};
|
86 |
&shredFile( $configuration->{localMachine}->{encryptionKeyPath} ) if -f $configuration->{localMachine}->{encryptionKeyPath};
|
87 |
return $error;
|
87 |
return $error;
|
88 |
}
|
88 |
}
|
89 |
|
89 |
|
90 |
# a very simple mailer, using Email::Simple to just get status messages out
|
90 |
# a very simple mailer, using Email::Simple to just get status messages out
|
91 |
sub sendMail {
|
91 |
sub sendMail {
|
92 |
my ($message, $config, $subject ) = @_;
|
92 |
my ($message, $configuration, $subject ) = @_;
|
93 |
$config->{'email'}->{'notify'} = 'root' unless $config->{'email'}->{'notify'};
|
93 |
$configuration->{'email'}->{'notify'} = 'root' unless $configuration->{'email'}->{'notify'};
|
94 |
die "No message in outgoing message\n" unless $message;
|
94 |
die "No message in outgoing message\n" unless $message;
|
95 |
my $email = Email::Simple->create(
|
95 |
my $email = Email::Simple->create(
|
96 |
header => [
|
96 |
header => [
|
97 |
To => $config->{'email'}->{'notify'},
|
97 |
To => $configuration->{'email'}->{'notify'},
|
98 |
Subject=> $config->{'email'}->{'subject'} . ( $subject ? " - $subject" : '' ),
|
98 |
Subject=> $configuration->{'email'}->{'subject'} . ( $subject ? " - $subject" : '' ),
|
99 |
From => $config->{'email'}->{'from'}
|
99 |
From => $configuration->{'email'}->{'from'}
|
100 |
],
|
100 |
],
|
101 |
body => $message
|
101 |
body => $message
|
102 |
);
|
102 |
);
|
103 |
$message = $email->as_string;
|
103 |
$message = $email->as_string;
|
- |
|
104 |
if ( $configuration->{'testing'} > 1 ) {
|
- |
|
105 |
print "$message\n";
|
- |
|
106 |
} else {
|
104 |
`echo '$message' | sendmail $config->{'email'}->{'notify'}`;
|
107 |
`echo '$message' | sendmail $configuration->{'email'}->{'notify'}`;
|
- |
|
108 |
}
|
105 |
}
|
109 |
}
|
106 |
|
110 |
|
107 |
# checks to see if we should be in maintenance mode
|
111 |
# checks to see if we should be in maintenance mode
|
108 |
# if $remoteMachine->{'maintenanceMode'} exists, set mode
|
112 |
# if $remoteMachine->{'maintenanceMode'} exists, set mode
|
109 |
# otherwise, wait localMachine->{'waittime'} minutes, then check
|
113 |
# otherwise, wait localMachine->{'waittime'} minutes, then check
|
110 |
# $localMachine->{'maintenanceMode'}.
|
114 |
# $localMachine->{'maintenanceMode'}.
|
111 |
# if neither exists, begin sync
|
115 |
# if neither exists, begin sync
|
112 |
sub checkMaintenance {
|
116 |
sub checkMaintenance {
|
113 |
my $config = shift;
|
117 |
my $configuration = shift;
|
- |
|
118 |
return 0 unless # exit if maintenanceFlag has not been set at all
|
- |
|
119 |
( defined( $configuration->{'target'}->{'maintenanceFlag'} ) && $configuration->{'target'}->{'maintenanceFlag'} ) ||
|
- |
|
120 |
( defined( $configuration->{'source'}->{'maintenanceFlag'} ) && $configuration->{'source'}->{'maintenanceFlag'} );
|
114 |
# see if maintenance is set on remote. If so, simply return the message
|
121 |
# see if maintenance is set on remote. If so, simply return the message
|
115 |
if ( $config->{'remoteMachine'}->{'up'} ) {
|
122 |
if ( $configuration->{'source'}->{'up'} ) {
|
116 |
my ($error, $output) = &runCommand( "ssh $configuration->{remoteMachine}->{ip} 'ls $configuration->{remoteMachine}->{maintenanceFlag}'" );
|
123 |
my ($error, $output) = &runCommand( "ssh $configuration->{remoteMachine}->{ip} 'ls $configuration->{remoteMachine}->{maintenanceFlag}'" );
|
117 |
if ( ! $error ) {
|
124 |
if ( ! $error ) {
|
118 |
# remove the file from the remote server
|
125 |
# remove the file from the remote server
|
119 |
&runCommand( "ssh $configuration->{remoteMachine}->{ip} 'rm $configuration->{remoteMachine}->{maintenanceFlag}'" );
|
126 |
&runCommand( "ssh $configuration->{remoteMachine}->{ip} 'rm $configuration->{remoteMachine}->{maintenanceFlag}'" );
|
120 |
# create a valid return, which will exit the program
|
127 |
# create a valid return, which will exit the program
|
121 |
return "Maintenance Flag found on remote machine";
|
128 |
return "Maintenance Flag found on remote machine";
|
122 |
}
|
129 |
}
|
123 |
}
|
130 |
}
|
124 |
# not on remote machine, so give them waitTime seconds to put it here
|
131 |
# 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
|
132 |
# we'll loop, checking every $sleepTime seconds until our wait time
|
126 |
# ($config->{'localMachine'}->{'waitTime'}) has expired
|
133 |
# ($configuration->{'target'}->{'waitTime'}) has expired
|
127 |
my $sleepTime = 60;
|
134 |
my $sleepTime = 60;
|
128 |
for ( my $i = $config->{'localMachine'}->{'waitTime'}; $i > 0; $i -= $sleepTime ) {
|
135 |
for ( my $i = $configuration->{'target'}->{'waitTime'}; $i > 0; $i -= $sleepTime ) {
|
129 |
sleep $sleepTime;
|
136 |
sleep $sleepTime;
|
130 |
# then look for the maintenance flag file on the local machine
|
137 |
# then look for the maintenance flag file on the local machine
|
131 |
return "Maintenance Flag found on local machine" if -f $config->{'localMachine'}->{'maintenanceFlag'};
|
138 |
return "Maintenance Flag found on local machine" if -f $configuration->{'target'}->{'maintenanceFlag'};
|
132 |
}
|
139 |
}
|
133 |
# no maintenance flags found, so return false
|
140 |
# no maintenance flags found, so return false
|
134 |
return 0;
|
141 |
return 0;
|
135 |
}
|
142 |
}
|
136 |
|
143 |
|
137 |
sub shutdownMachine {
|
144 |
sub shutdownMachine {
|
138 |
my $config = shift;
|
145 |
my $configuration = shift;
|
139 |
my $subject = shift;
|
146 |
my $subject = shift;
|
140 |
push @_, "Shutting down";
|
147 |
push @_, "Shutting down" if $configuration->{'shutdown'};
|
141 |
&sendMail( join( "\n", @_), $configuration, $subject );
|
148 |
&sendMail( join( "\n", @_), $configuration, $subject );
|
- |
|
149 |
# do not actually shut down the server unless we are told to
|
- |
|
150 |
exit unless $configuration->{'shutdown'};
|
142 |
&runCommand( "poweroff" ) unless $TESTING;
|
151 |
&runCommand( "poweroff" ) unless $configuration->{'testing'};
|
143 |
die "Shutting down machine now\n";
|
152 |
die "Shutting down machine now\n";
|
144 |
}
|
153 |
}
|
145 |
|
154 |
|
146 |
# returns the current time as a string
|
155 |
# returns the current time as a string
|
147 |
sub currentTime {
|
156 |
sub currentTime {
|
Line 150... |
Line 159... |
150 |
$format = '%Y-%m-%d %H-%M-%S' unless $format;
|
159 |
$format = '%Y-%m-%d %H-%M-%S' unless $format;
|
151 |
use POSIX;
|
160 |
use POSIX;
|
152 |
return POSIX::strftime( $format, localtime() );
|
161 |
return POSIX::strftime( $format, localtime() );
|
153 |
}
|
162 |
}
|
154 |
|
163 |
|
- |
|
164 |
sub checkRemoteUp {
|
- |
|
165 |
my $configuration = shift;
|
- |
|
166 |
my $ip;
|
- |
|
167 |
if ( defined( $configuration->{'target'}->{'server'} ) && $configuration->{'target'}->{'server'} ) {
|
- |
|
168 |
$ip = $configuration->{'target'}->{'server'};
|
- |
|
169 |
} else {
|
- |
|
170 |
$ip = $configuration->{'source'}->{'server'};
|
- |
|
171 |
}
|
- |
|
172 |
my ($error, $message ) = $ip ? &runCommand( "ping -c 1 -t 5 $ip" ) : (0,'No address defined for either target or server' );
|
- |
|
173 |
$message = "Checking IP $ip\n" . $message;
|
- |
|
174 |
#die "error is $error, message is $message for $ip\n";
|
- |
|
175 |
return ($error, $message);
|
- |
|
176 |
}
|
- |
|
177 |
|
155 |
my @status;
|
178 |
my @status;
|
- |
|
179 |
my $error = 0;
|
- |
|
180 |
my $output = '';
|
156 |
|
181 |
|
157 |
$configuration = &readConfig($configFileName);
|
182 |
$configuration = &readConfig($configFileName);
|
158 |
|
183 |
|
- |
|
184 |
# die Dumper( $configuration ) . "\n";
|
- |
|
185 |
|
- |
|
186 |
my $servername = `hostname`;
|
- |
|
187 |
chomp $servername;
|
- |
|
188 |
|
159 |
&sendMail( "mc-009 has been started, " . ¤tTime() . " checking for maintenance mode", $configuration );
|
189 |
&sendMail( "Replication on $servername has been started, " . ¤tTime(), $configuration, "Replication on $servername started" );
|
- |
|
190 |
|
160 |
# see if remote machine is up by sending one ping. Expect response in 5 seconds
|
191 |
# see if remote machine is up by sending one ping. Expect response in 5 seconds
|
161 |
my ( $error,$output) = &runCommand( "ping -c 1 -t 5 " . $configuration->{'remoteMachine'}->{'ip'} );
|
192 |
( $error,$output) = &checkRemoteUp( $configuration );
|
162 |
$configuration->{'remoteMachine'}->{'up'} = ! $error;
|
193 |
$configuration->{'up'} = ! $error;
|
- |
|
194 |
push @status, "remote machine is " . ( $configuration->{'up'} ? 'Up' : 'Down' ) . "\n";
|
- |
|
195 |
# we can not connect to the remote server, so just shut down
|
- |
|
196 |
&shutdownMachine( $configuration, "No connection to remote machine", @status ) unless $configuration->{'up'};
|
- |
|
197 |
|
163 |
|
198 |
|
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
|
199 |
# check for maintenance flags, exit if we should go into mainteance mode
|
166 |
if ( my $result = &checkMaintenance( $configuration ) ) {
|
200 |
if ( my $result = &checkMaintenance( $configuration ) ) {
|
167 |
push @status,$result;
|
201 |
push @status,$result;
|
168 |
&sendMail( join( "\n", @status), $configuration, "Maintenance Mode" );
|
202 |
&sendMail( join( "\n", @status), $configuration, "Maintenance Mode" );
|
169 |
die;
|
203 |
die;
|
170 |
}
|
204 |
}
|
171 |
|
205 |
|
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
|
206 |
# try to mount the datasets if they are encrypted
|
176 |
($error,$output) = &mountDrives( $configuration );
|
207 |
($error,$output) = &mountDrives( $configuration );
|
177 |
if ( $error ) { # could not mount datasets
|
208 |
if ( $error ) { # could not mount datasets
|
178 |
push @status, $error;
|
209 |
push @status, $error;
|
179 |
&shutdownMachine( $configuration, "Mount Drive Error: [$output]", @status );
|
210 |
&shutdownMachine( $configuration, "Mount Drive Error: [$output]", @status );
|
180 |
}
|
211 |
}
|
181 |
|
212 |
|
182 |
&sendMail( "Backup has been started at " . ¤tTime(), $configuration, "Backup Starting" );
|
213 |
&sendMail( "Backup has been started at " . ¤tTime(), $configuration, "Backup Starting" );
|
183 |
push @status, "Backup started at: " . ¤tTime();
|
214 |
push @status, "Backup started at: " . ¤tTime();
|
184 |
|
215 |
|
- |
|
216 |
$configuration->{'source'}->{'server'} = $configuration->{'source'}->{'server'} ? $configuration->{'source'}->{'server'} . ':' : '';
|
- |
|
217 |
$configuration->{'target'}->{'server'} = $configuration->{'target'}->{'server'} ? $configuration->{'target'}->{'server'} . ':' : '';
|
185 |
|
218 |
|
186 |
# For each dataset, let's find the snapshots we need
|
219 |
# For each dataset, let's find the snapshots we need
|
187 |
foreach my $sourceDir ( keys %{$configuration->{'remoteMachine'}->{'dataset'}} ) {
|
220 |
foreach my $sourceDir ( keys %{$configuration->{'source'}->{'dataset'}} ) {
|
- |
|
221 |
print "Looking for $sourceDir\n";
|
- |
|
222 |
print "syncing to $configuration->{target}->{targetDataset}\n";
|
188 |
my $command = $replicateScript . ' ' .
|
223 |
my $command = $replicateScript . ' ' .
|
189 |
$configuration->{'remoteMachine'}->{'ip'} . ':' .
|
224 |
$configuration->{'source'}->{'server'} .
|
190 |
$configuration->{'remoteMachine'}->{'dataset'}->{$sourceDir} . '/' . $sourceDir . ' ' .
|
225 |
$configuration->{'source'}->{'dataset'}->{$sourceDir} . '/' . $sourceDir . ' ' .
|
- |
|
226 |
$configuration->{'target'}->{'server'} .
|
191 |
$configuration->{'localMachine'}->{'targetDataset'} . '/' . $sourceDir;
|
227 |
$configuration->{'target'}->{'targetDataset'} . '/' . $sourceDir;
|
192 |
push @status, "=== Running $command at " . ¤tTime();
|
228 |
push @status, "=== Running $command at " . ¤tTime();
|
193 |
my ($error, $output) = &runCommand( $command );
|
229 |
($error, $output) = &runCommand( $command ) unless $configuration->{'testing'};
|
194 |
push @status, $output;
|
230 |
push @status, $output;
|
195 |
push @status, "=== Completed $command with status $error at " . ¤tTime();
|
231 |
push @status, "=== Completed $command with status $error at " . ¤tTime();
|
196 |
}
|
232 |
}
|
197 |
|
233 |
|
198 |
push @status, "==== Backup finished at: " . ¤tTime();
|
234 |
push @status, "==== Backup finished at: " . ¤tTime();
|
199 |
|
235 |
|
- |
|
236 |
if ($configuration->{'testing'}) {
|
- |
|
237 |
print join( "\n", @status ) . "\n";
|
- |
|
238 |
} else {
|
200 |
&shutdownMachine( $configuration, "Backup Complete", @status );
|
239 |
&shutdownMachine( $configuration, "Backup Complete", @status );
|
- |
|
240 |
}
|
201 |
|
241 |
|
202 |
1;
|
242 |
1;
|