| Line 45... |
Line 45... |
| 45 |
|
45 |
|
| 46 |
use FindBin;
|
46 |
use FindBin;
|
| 47 |
use lib "$FindBin::Bin/..";
|
47 |
use lib "$FindBin::Bin/..";
|
| 48 |
use Data::Dumper;
|
48 |
use Data::Dumper;
|
| 49 |
use ZFS_Utils qw(loadConfig shredFile logMsg makeReplicateCommands mountDriveByLabel unmountDriveByLabel mountGeli runCmd sendReport fatalError cleanDirectory $logFileName $displayLogsOnConsole);
|
49 |
use ZFS_Utils qw(loadConfig shredFile logMsg makeReplicateCommands mountDriveByLabel unmountDriveByLabel mountGeli runCmd sendReport fatalError cleanDirectory $logFileName $displayLogsOnConsole);
|
| - |
|
50 |
use Getopt::Long qw(GetOptions);
|
| - |
|
51 |
Getopt::Long::Configure ("bundling");
|
| 50 |
|
52 |
|
| 51 |
my $scriptDirectory = $FindBin::RealBin;
|
53 |
my $scriptDirectory = $FindBin::RealBin;
|
| 52 |
my $scriptFullPath = "$scriptDirectory/" . $FindBin::Script;
|
54 |
my $scriptFullPath = "$scriptDirectory/" . $FindBin::Script;
|
| 53 |
|
55 |
|
| 54 |
|
- |
|
| 55 |
# if set, will not actually write files to disk
|
- |
|
| 56 |
my $DEBUG = 1;
|
- |
|
| 57 |
|
- |
|
| 58 |
# display all log messages on console in addition to the log file
|
56 |
# display all log messages on console in addition to the log file
|
| 59 |
$displayLogsOnConsole = 1;
|
57 |
$displayLogsOnConsole = 1;
|
| 60 |
|
58 |
|
| 61 |
my $configFileName = "$scriptFullPath.conf.yaml";
|
59 |
my $configFileName = "$scriptFullPath.conf.yaml";
|
| 62 |
|
60 |
|
| 63 |
my $config = {
|
61 |
my $config = {
|
| - |
|
62 |
'dryrun' => 0,
|
| - |
|
63 |
'verbosity' => 1,
|
| 64 |
# file created on source server to track last copyed dataset
|
64 |
# file created on source server to track last copyed dataset
|
| 65 |
'status_file' => "$scriptFullPath.status",
|
65 |
'status_file' => "$scriptFullPath.status",
|
| 66 |
'log_file' => "$scriptFullPath.log",
|
66 |
'log_file' => "$scriptFullPath.log",
|
| 67 |
#information about source server
|
67 |
#information about source server
|
| 68 |
'source' => {
|
68 |
'source' => {
|
| Line 201... |
Line 201... |
| 201 |
my ($config, $statusList) = @_;
|
201 |
my ($config, $statusList) = @_;
|
| 202 |
my $newStatus = [];
|
202 |
my $newStatus = [];
|
| 203 |
foreach my $dataset ( sort keys %{$config->{datasets}} ) {
|
203 |
foreach my $dataset ( sort keys %{$config->{datasets}} ) {
|
| 204 |
logMsg("Processing dataset '$dataset'");
|
204 |
logMsg("Processing dataset '$dataset'");
|
| 205 |
# get list of all snapshots on dataset
|
205 |
# get list of all snapshots on dataset
|
| - |
|
206 |
my $sourceList;
|
| - |
|
207 |
if ( -e "$scriptDirectory/test.status") {
|
| 206 |
my $root = $config->{datasets}->{$dataset}->{source} . '/' . $config->{datasets}->{$dataset}->{dataset};
|
208 |
$sourceList = getStatusFile( "$scriptDirectory/test.status" );
|
| - |
|
209 |
} else {
|
| 207 |
my $sourceList = [ runCmd( "zfs list -rt snap -H -o name $root" ) ];
|
210 |
$sourceList = [ runCmd( "zfs list -rt snap -H -o name $config->{datasets}-{$dataset}->{source}" ) ];
|
| - |
|
211 |
}
|
| - |
|
212 |
|
| 208 |
# remove the parent part, leave the dataset itself
|
213 |
# remove the parent part, leave the dataset itself
|
| 209 |
$sourceList =~ s|$config->{datasets}->{$dataset}->{source}/||;
|
214 |
$sourceList =~ s|/||;
|
| 210 |
# process dataset here
|
215 |
# process dataset here
|
| 211 |
my $commands = makeReplicateCommands( $sourceList, $statusList, $newStatus );
|
216 |
my $commands = makeReplicateCommands(
|
| 212 |
|
- |
|
| - |
|
217 |
$sourceList,
|
| - |
|
218 |
$statusList,
|
| - |
|
219 |
$dataset,
|
| - |
|
220 |
$config->{datasets}->{$dataset}->{source},
|
| - |
|
221 |
$config->{datasets}->{$dataset}->{target},
|
| - |
|
222 |
$newStatus
|
| - |
|
223 |
);
|
| 213 |
if ( %$commands ) {
|
224 |
if ( %$commands ) {
|
| 214 |
foreach my $cmd ( keys %$commands ) {
|
225 |
foreach my $cmd ( keys %$commands ) {
|
| 215 |
my $command = $commands->{$cmd};
|
226 |
my $command = $commands->{$cmd};
|
| 216 |
my $outputFile = $cmd;
|
227 |
my $outputFile = $cmd;
|
| 217 |
$outputFile =~ s/^$root//;
|
228 |
#$outputFile =~ s/^$root//;
|
| 218 |
$outputFile = replaceSlashWithDot($outputFile);
|
229 |
$outputFile = replaceSlashWithDot($outputFile);
|
| 219 |
#$command .= " | openssl enc -aes-256-cbc -K $config->{transport}->{encryption}->{key} -iv $config->{transport}->{encryption}->{IV} " if $config->{transport}->{encryption}->{key};
|
230 |
#$command .= " | openssl enc -aes-256-cbc -K $config->{transport}->{encryption}->{key} -iv $config->{transport}->{encryption}->{IV} " if $config->{transport}->{encryption}->{key};
|
| 220 |
$command .= " > $config->{transport}->{mount_point}/" . $outputFile;
|
231 |
$command .= " > $config->{transport}->{mount_point}/" . $cmd;
|
| 221 |
logMsg("Running command: $command");
|
232 |
logMsg("Running command: $command");
|
| 222 |
runCmd( $command ) unless $DEBUG;
|
233 |
runCmd( $command ) unless $config->{dryrun};
|
| 223 |
}
|
234 |
}
|
| 224 |
} else {
|
235 |
} else {
|
| 225 |
logMsg( "Nothing to do for $dataset" );
|
236 |
logMsg( "Nothing to do for $dataset" );
|
| 226 |
}
|
237 |
}
|
| 227 |
}
|
238 |
}
|
| Line 241... |
Line 252... |
| 241 |
chomp $servername;
|
252 |
chomp $servername;
|
| 242 |
logMsg( "Zpools on server $servername:\n" . join( "\n", runCmd( "zpool list" ) ) . "\n" );
|
253 |
logMsg( "Zpools on server $servername:\n" . join( "\n", runCmd( "zpool list" ) ) . "\n" );
|
| 243 |
$config->{$config->{runningAs}}->{report}->{subject} //= "Replication Report for $config->{runningAs} server $servername";
|
254 |
$config->{$config->{runningAs}}->{report}->{subject} //= "Replication Report for $config->{runningAs} server $servername";
|
| 244 |
$message //= "Replication completed on $config->{runningAs} server $servername.";
|
255 |
$message //= "Replication completed on $config->{runningAs} server $servername.";
|
| 245 |
# unmount the sneakernet drive
|
256 |
# unmount the sneakernet drive
|
| 246 |
unmountDriveByLabel( $config->{transport} );
|
257 |
unmountDriveByLabel( $config->{transport} ) unless $config->{dryrun};
|
| 247 |
sendReport( $config->{$config->{runningAs}}->{report}, $message, $config->{log_file} );
|
258 |
sendReport( $config->{$config->{runningAs}}->{report}, $message, $config->{log_file} );
|
| 248 |
# If they have requested shutdown, do it now
|
259 |
# If they have requested shutdown, do it now
|
| 249 |
if ( $config->{$config->{runningAs}}->{shutdown_after_replication} ) {
|
260 |
if ( $config->{$config->{runningAs}}->{shutdown_after_replication} ) {
|
| 250 |
logMsg( "Shutting down target server as per configuration" );
|
261 |
logMsg( "Shutting down target server as per configuration" );
|
| 251 |
runCmd( "shutdown -p now" ) unless $DEBUG;
|
262 |
runCmd( "shutdown -p now" ) unless $config->{dryrun};
|
| 252 |
}
|
263 |
}
|
| 253 |
}
|
264 |
}
|
| 254 |
|
265 |
|
| 255 |
# update the target datasets from the files on the transport drive
|
266 |
# update the target datasets from the files on the transport drive
|
| 256 |
sub updateTarget {
|
267 |
sub updateTarget {
|
| Line 268... |
Line 279... |
| 268 |
|
279 |
|
| 269 |
# If a YAML config file exists next to the script, load and merge it
|
280 |
# If a YAML config file exists next to the script, load and merge it
|
| 270 |
$config = loadConfig($configFileName, $config );
|
281 |
$config = loadConfig($configFileName, $config );
|
| 271 |
exit 1 unless keys %$config;
|
282 |
exit 1 unless keys %$config;
|
| 272 |
|
283 |
|
| - |
|
284 |
# parse CLI options
|
| - |
|
285 |
GetOptions( $config,
|
| - |
|
286 |
'dryrun|n',
|
| - |
|
287 |
'verbose|v+',
|
| - |
|
288 |
'version|V',
|
| - |
|
289 |
'help|h',
|
| - |
|
290 |
) or do { print "Invalid options\n"; exit 2 };
|
| - |
|
291 |
if (defined ($config->{help})) {
|
| - |
|
292 |
print "Usage: $FindBin::Script [--dryrun] [--verbose] [--help]\n";
|
| - |
|
293 |
print " --dryrun, -n Run in dry-run mode (no writes)\n";
|
| - |
|
294 |
print " --verbose, -v Run in verbose mode (more v's mean more verbose)\n";
|
| - |
|
295 |
print " --version, -V Display version number\n";
|
| - |
|
296 |
exit 0;
|
| - |
|
297 |
} elsif (defined $config->{version}) {
|
| - |
|
298 |
print "$FindBin::Script v$VERSION\n";
|
| - |
|
299 |
exit 0;
|
| - |
|
300 |
}
|
| - |
|
301 |
|
| 273 |
# set some defaults
|
302 |
# set some defaults
|
| 274 |
$config->{'status_file'} //= "$scriptFullPath.status";
|
303 |
$config->{'status_file'} //= "$scriptFullPath.status";
|
| 275 |
# set log file name for sub logMsg in ZFS_Utils, and remove the old log if it exists
|
304 |
# set log file name for sub logMsg in ZFS_Utils, and remove the old log if it exists
|
| 276 |
# Log file is only valid for one run
|
305 |
# Log file is only valid for one run
|
| 277 |
$logFileName = $config->{'log_file'} //= "$scriptFullPath.log";
|
306 |
$logFileName = $config->{'log_file'} //= "$scriptFullPath.log";
|
| Line 288... |
Line 317... |
| 288 |
|
317 |
|
| 289 |
#cleanup( $config, "Testing" );
|
318 |
#cleanup( $config, "Testing" );
|
| 290 |
|
319 |
|
| 291 |
# mount the transport drive, fatal error if we can not find it
|
320 |
# mount the transport drive, fatal error if we can not find it
|
| 292 |
fatalError( "Unable to mount tranport drive with label $config->{transport}->{disk_label}", $config, \&cleanup )
|
321 |
fatalError( "Unable to mount tranport drive with label $config->{transport}->{disk_label}", $config, \&cleanup )
|
| 293 |
unless $config->{transport}->{mount_point} = mountDriveByLabel( $config->{transport} );
|
322 |
unless $config->{dryrun} or $config->{transport}->{mount_point} = mountDriveByLabel( $config->{transport} );
|
| 294 |
|
323 |
|
| 295 |
# mail program logic
|
324 |
# main program logic
|
| 296 |
if ( $config->{runningAs} eq 'source' ) {
|
325 |
if ( $config->{runningAs} eq 'source' ) {
|
| 297 |
logMsg "Running as source server";
|
326 |
logMsg "Running as source server";
|
| 298 |
# remove all files from transport disk, but leave all subdirectories alone
|
327 |
# remove all files from transport disk, but leave all subdirectories alone
|
| 299 |
fatalError( "Failed to clean transport directory $config->{transport}->{mount_point}", $config, \&cleanup )
|
328 |
fatalError( "Failed to clean transport directory $config->{transport}->{mount_point}", $config, \&cleanup )
|
| 300 |
unless cleanDirectory( $config->{transport}->{mount_point} );
|
329 |
unless $config->{dryrun} or cleanDirectory( $config->{transport}->{mount_point} );
|
| 301 |
my $statusList = getStatusFile($config->{status_file});
|
330 |
my $statusList = getStatusFile($config->{status_file});
|
| 302 |
$statusList = doSourceReplication($config, $statusList);
|
331 |
$statusList = doSourceReplication($config, $statusList);
|
| 303 |
writeStatusFile($config->{status_file}, $statusList);
|
332 |
writeStatusFile($config->{status_file}, $statusList) unless $config->{dryrun};
|
| 304 |
} elsif ( $config->{runningAs} eq 'target' ) {
|
333 |
} elsif ( $config->{runningAs} eq 'target' ) {
|
| 305 |
logMsg "Running as target server";
|
334 |
logMsg "Running as target server";
|
| 306 |
mountGeli( $config->{target}->{geli} ) if ( defined $config->{target}->{geli} );
|
335 |
mountGeli( $config->{target}->{geli} ) if ( defined $config->{target}->{geli} );
|
| 307 |
updateTarget( $config );
|
336 |
updateTarget( $config );
|
| 308 |
} else {
|
337 |
} else {
|