| 24 |
rodolico |
1 |
#! /usr/bin/env perl
|
|
|
2 |
|
|
|
3 |
use strict;
|
|
|
4 |
use warnings;
|
|
|
5 |
|
|
|
6 |
use FindBin;
|
|
|
7 |
use lib "$FindBin::Bin/..";
|
| 27 |
rodolico |
8 |
use Data::Dumper;
|
|
|
9 |
use ZFS_Utils qw(loadConfig shredFile logMsg makeReplicateCommands mountDriveByLabel mountGeli runCmd $logFileName $displayLogsOnConsole);
|
| 24 |
rodolico |
10 |
|
|
|
11 |
# set the log file to be next to this script
|
|
|
12 |
$logFileName = "$FindBin::Bin/sneakernet.log";
|
|
|
13 |
# display all log messages on console in addition to the log file
|
|
|
14 |
$displayLogsOnConsole = 1;
|
|
|
15 |
|
|
|
16 |
my $configFileName = "$0.conf.yaml";
|
|
|
17 |
|
|
|
18 |
my $config = {
|
|
|
19 |
# file created on source server to track last copyed dataset
|
|
|
20 |
'status_file' => "$0.status",
|
|
|
21 |
#information about source server
|
|
|
22 |
'source_server' => {
|
|
|
23 |
'hostname' => '', # used to see if we are on source
|
|
|
24 |
'poolname' => '', # name of the ZFS pool to export
|
|
|
25 |
},
|
|
|
26 |
#information about target server
|
|
|
27 |
'target_server' => {
|
|
|
28 |
'hostname' => '', # used to see if we are on target
|
|
|
29 |
'poolname' => '', # name of the ZFS pool to import
|
|
|
30 |
# if this is set, the dataset uses GELI, so we must decrypt and
|
|
|
31 |
# mount it first
|
|
|
32 |
'geli' => {
|
|
|
33 |
'keydiskname' => 'replica', # the GPT label of the key disk
|
|
|
34 |
'keyfile' => 'geli.key', # the name of the key file on keydiskname
|
|
|
35 |
'localKey' => 'e98c660cccdae1226550484d62caa2b72f60632ae0c607528aba1ac9e7bfbc9c', # hex representation of the local key part
|
|
|
36 |
'target' => '/media/geli.key', # location to create the combined keyfile
|
|
|
37 |
'poolname' => '', # name of the ZFS pool to import
|
|
|
38 |
'diskList' => [
|
|
|
39 |
'/dev/gpt/sneakernet_disk'
|
|
|
40 |
], # list of disks to try to mount the dataset from
|
|
|
41 |
}
|
|
|
42 |
},
|
| 27 |
rodolico |
43 |
|
| 24 |
rodolico |
44 |
'transport' => {
|
|
|
45 |
# this is the GPT label of the sneakernet disk
|
|
|
46 |
'disk_label' => 'sneakernet',
|
|
|
47 |
# where we want to mount it
|
|
|
48 |
'mount_point' => '/mnt/sneakernet',
|
|
|
49 |
# amount of time to wait for the disk to appear
|
|
|
50 |
'timeout' => 600,
|
|
|
51 |
# if set, all files will be encrypted with this key/IV during transport
|
|
|
52 |
'encryption' => {
|
|
|
53 |
'key' => '', # openssl rand 32 | xxd -p | tr -d '\n' > test.key
|
|
|
54 |
'IV' => '00000000000000000000000000000000',
|
|
|
55 |
},
|
|
|
56 |
},
|
|
|
57 |
'datasets' => {
|
|
|
58 |
'iscsi' => {
|
|
|
59 |
'source' => 'storage/backup/iscsi',
|
|
|
60 |
'target' => 'storage/backup/iscsi',
|
|
|
61 |
'filename' => 'iscsi'
|
|
|
62 |
},
|
|
|
63 |
'files_share' => {
|
|
|
64 |
'source' => 'storage/backup/files_share',
|
|
|
65 |
'target' => 'storage/backup/files_share',
|
|
|
66 |
'filename' => 'files_share'
|
|
|
67 |
},
|
|
|
68 |
}
|
|
|
69 |
};
|
|
|
70 |
|
|
|
71 |
|
|
|
72 |
# generate a random key with
|
|
|
73 |
# openssl rand 32 | xxd -p | tr -d '\n' > test.key
|
|
|
74 |
|
|
|
75 |
# If a YAML config file exists next to the script, load and merge it
|
|
|
76 |
$config = loadConfig($configFileName, $config );
|
| 27 |
rodolico |
77 |
|
| 25 |
rodolico |
78 |
# set some defaults
|
|
|
79 |
$config->{'status_file'} = "$0.status" unless ( defined $config->{'status_file'} );
|
| 24 |
rodolico |
80 |
|
| 25 |
rodolico |
81 |
|
| 24 |
rodolico |
82 |
die "Invalid config file: missing source and/or target server\n"
|
|
|
83 |
unless (defined $config->{source_server} && defined $config->{target_server});
|
|
|
84 |
|
|
|
85 |
my $servername = `hostname -s`;
|
|
|
86 |
chomp $servername;
|
|
|
87 |
if ( $servername eq $config->{source_server}->{hostname} ) {
|
| 25 |
rodolico |
88 |
logMsg "Running as source server\n";
|
| 24 |
rodolico |
89 |
# source server logic here
|
|
|
90 |
} elsif ( $servername eq $config->{target_server}->{hostname} ) {
|
| 25 |
rodolico |
91 |
logMsg "Running as target server\n";
|
| 24 |
rodolico |
92 |
mountGeli( $config->{target_server}->{geli} ) if ( defined $config->{target_server}->{geli} );
|
|
|
93 |
} else {
|
| 25 |
rodolico |
94 |
logMsg "This server ($servername) is neither source nor target server as per config\n";
|
|
|
95 |
die;
|
| 24 |
rodolico |
96 |
}
|
|
|
97 |
|
| 25 |
rodolico |
98 |
# read in history/status file
|
|
|
99 |
my $targetList = [];
|
|
|
100 |
if ( -e $config->{status_file} && open my $fh, '<', $config->{status_file} ) {
|
|
|
101 |
chomp( my @lines = <$fh> );
|
|
|
102 |
$targetList = \@lines;
|
|
|
103 |
close $fh;
|
|
|
104 |
} else {
|
|
|
105 |
logMsg("Error: could not read status file '$config->{status_file}': $!");
|
|
|
106 |
die;
|
|
|
107 |
}
|
| 24 |
rodolico |
108 |
|
| 25 |
rodolico |
109 |
my $newStatus = [];
|
|
|
110 |
foreach my $dataset ( sort keys %{$config->{datasets}} ) {
|
| 27 |
rodolico |
111 |
logMsg("Processing dataset '$dataset'");
|
| 25 |
rodolico |
112 |
my $sourceList = [ runCmd( "zfs", "list", "-rt", "snap", "-H", "-o", "name", $config->{datasets}->{$dataset}->{source} ) ];
|
|
|
113 |
# process dataset here
|
|
|
114 |
my $commands = makeReplicateCommands($sourceList, $targetList, $newStatus );
|
|
|
115 |
foreach my $cmd ( @$commands ) {
|
| 27 |
rodolico |
116 |
$cmd .= " > $config->{transport}->{mount_point}/" . $dataset;
|
|
|
117 |
logMsg("Running command: $cmd");
|
| 25 |
rodolico |
118 |
#runCmd( split( /\s+/, $cmd ) );
|
|
|
119 |
}
|
|
|
120 |
}
|
|
|
121 |
|
|
|
122 |
1;
|
|
|
123 |
|
|
|
124 |
|
| 24 |
rodolico |
125 |
#`cat $config->{input} | openssl enc -aes-256-cbc -K $config->{key} -iv $config->{IV} > $config->{output}`;
|
|
|
126 |
|
|
|
127 |
# this will decrypt $config->{output} to stdout
|
|
|
128 |
#`cat $config->{output} | openssl enc -aes-256-cbc -d -K $config->{key} -iv $config->{IV} > test.out`;
|