| Line 44... |
Line 44... |
| 44 |
our $VERSION = '0.1';
|
44 |
our $VERSION = '0.1';
|
| 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 mountGeli runCmd $logFileName $displayLogsOnConsole);
|
49 |
use ZFS_Utils qw(loadConfig shredFile logMsg makeReplicateCommands mountDriveByLabel mountGeli runCmd sendReport $logFileName $displayLogsOnConsole);
|
| - |
|
50 |
|
| - |
|
51 |
my $scriptDirectory = $FindBin::RealBin;
|
| - |
|
52 |
my $scriptFullPath = "$scriptDirectory/" . $FindBin::Script;
|
| - |
|
53 |
|
| 50 |
|
54 |
|
| 51 |
# if set, will not actually write files to disk
|
55 |
# if set, will not actually write files to disk
|
| 52 |
my $DEBUG = 0;
|
56 |
my $DEBUG = 0;
|
| 53 |
|
57 |
|
| 54 |
# set the log file to be next to this script
|
- |
|
| 55 |
$logFileName = "$FindBin::Bin/sneakernet.log";
|
- |
|
| 56 |
# log only for one run
|
- |
|
| 57 |
unlink ( $logFileName ) if -f $logFileName;
|
- |
|
| 58 |
|
- |
|
| 59 |
# display all log messages on console in addition to the log file
|
58 |
# display all log messages on console in addition to the log file
|
| 60 |
$displayLogsOnConsole = 1;
|
59 |
$displayLogsOnConsole = 1;
|
| 61 |
|
60 |
|
| 62 |
my $configFileName = "$0.conf.yaml";
|
61 |
my $configFileName = "$scriptFullPath.conf.yaml";
|
| 63 |
|
62 |
|
| 64 |
my $config = {
|
63 |
my $config = {
|
| 65 |
# file created on source server to track last copyed dataset
|
64 |
# file created on source server to track last copyed dataset
|
| 66 |
'status_file' => "$0.status",
|
65 |
'status_file' => "$scriptFullPath.status",
|
| - |
|
66 |
'log_file' => "$scriptFullPath.log",
|
| 67 |
#information about source server
|
67 |
#information about source server
|
| 68 |
'source_server' => {
|
68 |
'source_server' => {
|
| 69 |
'hostname' => '', # used to see if we are on source
|
69 |
'hostname' => '', # used to see if we are on source
|
| 70 |
'poolname' => 'pool', # name of the ZFS pool to export
|
70 |
'poolname' => 'pool', # name of the ZFS pool to export
|
| 71 |
# if set, will generate a report via email or by storing on a drive
|
71 |
# if set, will generate a report via email or by storing on a drive
|
| 72 |
'report' => {
|
72 |
'report' => {
|
| 73 |
'email' => 'tech@example.org',
|
73 |
'email' => 'tech@example.org',
|
| 74 |
'subject' => 'AG Transport Report',
|
74 |
'subject' => 'AG Transport Report',
|
| 75 |
'targetDrive' => {
|
75 |
'targetDrive' => {
|
| - |
|
76 |
'fstype' => '', # filesystem type of the report drive
|
| - |
|
77 |
# How often to check for the disk (seconds), message displayed every interval
|
| - |
|
78 |
'check_interval' => 15,
|
| 76 |
'label' => '',
|
79 |
'label' => '',
|
| 77 |
'mount_point' => '',
|
80 |
'mount_point' => '',
|
| 78 |
}
|
81 |
}
|
| 79 |
}
|
82 |
}
|
| 80 |
},
|
83 |
},
|
| Line 83... |
Line 86... |
| 83 |
'hostname' => '', # used to see if we are on target
|
86 |
'hostname' => '', # used to see if we are on target
|
| 84 |
'poolname' => 'backup', # name of the ZFS pool to import
|
87 |
'poolname' => 'backup', # name of the ZFS pool to import
|
| 85 |
# if this is set, the dataset uses GELI, so we must decrypt and
|
88 |
# if this is set, the dataset uses GELI, so we must decrypt and
|
| 86 |
# mount it first
|
89 |
# mount it first
|
| 87 |
'geli' => {
|
90 |
'geli' => {
|
| - |
|
91 |
'secureKey ' => {
|
| 88 |
'keydiskname' => 'replica', # the GPT label of the key disk
|
92 |
'label' => 'replica', # the GPT label of the key disk
|
| - |
|
93 |
'fstype' => 'ufs', # filesystem type of the key disk
|
| - |
|
94 |
'check_interval' => 15,
|
| - |
|
95 |
'wait_timeout' => 300,
|
| 89 |
'keyfile' => 'geli.key', # the name of the key file on keydiskname
|
96 |
'keyfile' => 'geli.key', # the name of the key file on the secureKey disk
|
| - |
|
97 |
},
|
| 90 |
'localKey' => 'e98c660cccdae1226550484d62caa2b72f60632ae0c607528aba1ac9e7bfbc9c', # hex representation of the local key part
|
98 |
'localKey' => 'e98c660cccdae1226550484d62caa2b72f60632ae0c607528aba1ac9e7bfbc9c', # hex representation of the local key part
|
| 91 |
'target' => '/media/geli.key', # location to create the combined keyfile
|
99 |
'target' => '/media/geli.key', # location to create the combined keyfile
|
| 92 |
'poolname' => 'backup', # name of the ZFS pool to import
|
100 |
'poolname' => 'backup', # name of the ZFS pool to import
|
| 93 |
'diskList' => [
|
101 |
'diskList' => [
|
| 94 |
'da0',
|
102 |
'da0',
|
| Line 97... |
Line 105... |
| 97 |
},
|
105 |
},
|
| 98 |
'report' => {
|
106 |
'report' => {
|
| 99 |
'email' => '',
|
107 |
'email' => '',
|
| 100 |
'subject' => '',
|
108 |
'subject' => '',
|
| 101 |
'targetDrive' => {
|
109 |
'targetDrive' => {
|
| - |
|
110 |
'fstype' => 'msdos', # filesystem type of the report drive
|
| 102 |
'label' => 'sneakernet_report',
|
111 |
'label' => 'sneakernet',
|
| 103 |
'mount_point' => '/mnt/sneakernet_report',
|
112 |
'mount_point' => '',
|
| 104 |
}
|
113 |
}
|
| 105 |
}
|
114 |
}
|
| 106 |
},
|
115 |
},
|
| 107 |
'transport' => {
|
116 |
'transport' => {
|
| 108 |
# this is the GPT label of the sneakernet disk
|
117 |
# this is the GPT label of the sneakernet disk
|
| 109 |
'disk_label' => 'sneakernet',
|
118 |
'disk_label' => 'sneakernet',
|
| - |
|
119 |
# this is the file system type. Not needed if ufs
|
| - |
|
120 |
'fstype' => 'ufs',
|
| 110 |
# where we want to mount it
|
121 |
# where we want to mount it
|
| 111 |
'mount_point' => '/mnt/sneakernet',
|
122 |
'mount_point' => '/mnt/sneakernet',
|
| 112 |
# amount of time to wait for the disk to appear
|
123 |
# amount of time to wait for the disk to appear
|
| 113 |
'timeout' => 600,
|
124 |
'timeout' => 600,
|
| - |
|
125 |
# How often to check for the disk (seconds), message displayed every interval
|
| - |
|
126 |
'check_interval' => 15,
|
| 114 |
# if set, all files will be encrypted with this key/IV during transport
|
127 |
# if set, all files will be encrypted with this key/IV during transport
|
| 115 |
'encryption' => {
|
128 |
'encryption' => {
|
| 116 |
'key' => '', # openssl rand 32 | xxd -p | tr -d '\n' > test.key
|
129 |
'key' => '', # openssl rand 32 | xxd -p | tr -d '\n' > test.key
|
| 117 |
'IV' => '00000000000000000000000000000000',
|
130 |
'IV' => '00000000000000000000000000000000',
|
| 118 |
},
|
131 |
},
|
| Line 129... |
Line 142... |
| 129 |
'filename' => 'files_share'
|
142 |
'filename' => 'files_share'
|
| 130 |
},
|
143 |
},
|
| 131 |
}
|
144 |
}
|
| 132 |
};
|
145 |
};
|
| 133 |
|
146 |
|
| 134 |
# read the status file and return as list
|
147 |
# read the status file and return as list. If the file doesn't exits, returns an empty list
|
| 135 |
sub getStatusFile {
|
148 |
sub getStatusFile {
|
| 136 |
my $filename = shift;
|
149 |
my $filename = shift;
|
| 137 |
# read in history/status file
|
150 |
# read in history/status file
|
| 138 |
my @lines = ();
|
151 |
my @lines = ();
|
| 139 |
if ( -e $filename && open my $fh, '<', $filename ) {
|
152 |
if ( -e $filename && open my $fh, '<', $filename ) {
|
| Line 238... |
Line 251... |
| 238 |
|
251 |
|
| 239 |
# If a YAML config file exists next to the script, load and merge it
|
252 |
# If a YAML config file exists next to the script, load and merge it
|
| 240 |
$config = loadConfig($configFileName, $config );
|
253 |
$config = loadConfig($configFileName, $config );
|
| 241 |
|
254 |
|
| 242 |
# set some defaults
|
255 |
# set some defaults
|
| 243 |
$config->{'status_file'} //= "$0.status";
|
256 |
$config->{'status_file'} //= "$scriptFullPath.status";
|
| - |
|
257 |
# set log file name for sub logMsg in ZFS_Utils, and remove the old log if it exists
|
| - |
|
258 |
# Log file is only valid for one run
|
| - |
|
259 |
$logFileName = $config->{'log_file'} //= "$scriptFullPath.log";
|
| - |
|
260 |
# log only for one run
|
| - |
|
261 |
unlink ( $logFileName ) if -f $logFileName;
|
| 244 |
|
262 |
|
| 245 |
fatalError( "Invalid config file: missing source and/or target server" )
|
263 |
fatalError( "Invalid config file: missing source and/or target server" )
|
| 246 |
unless (defined $config->{source_server} && defined $config->{target_server});
|
264 |
unless (defined $config->{source_server} && defined $config->{target_server});
|
| 247 |
|
265 |
|
| 248 |
# mount the transport drive, fatal error if we can not find it
|
266 |
# mount the transport drive, fatal error if we can not find it
|
| 249 |
fatalError( "Unable to mount tranport drive with label $config->{transport}->{disk_label}" )
|
267 |
fatalError( "Unable to mount tranport drive with label $config->{transport}->{disk_label}" )
|
| 250 |
unless $config->{transport}->{mount_point} = mountDriveByLabel( $config->{transport}->{disk_label}, $config->{transport}->{mount_point}, $config->{transport}->{timeout} );
|
268 |
unless $config->{transport}->{mount_point} = mountDriveByLabel( $config->{transport} );
|
| 251 |
|
269 |
|
| 252 |
my $servername = `hostname -s`;
|
270 |
my $servername = `hostname -s`;
|
| 253 |
chomp $servername;
|
271 |
chomp $servername;
|
| 254 |
if ( $servername eq $config->{source_server}->{hostname} ) {
|
272 |
my $runningAs = $servername eq $config->{source_server}->{hostname} ? 'source' :
|
| - |
|
273 |
$servername eq $config->{target_server}->{hostname} ? 'target' : 'unknown';
|
| - |
|
274 |
if ( $runningAs eq 'source' ) {
|
| 255 |
logMsg "Running as source server";
|
275 |
logMsg "Running as source server";
|
| 256 |
# remove all files from transport disk, but leave all subdirectories alone
|
276 |
# remove all files from transport disk, but leave all subdirectories alone
|
| 257 |
cleanDirectory( $config->{transport}->{mount_point} );
|
277 |
cleanDirectory( $config->{transport}->{mount_point} );
|
| 258 |
my $statusList = getStatusFile($config->{status_file});
|
278 |
my $statusList = getStatusFile($config->{status_file});
|
| 259 |
$statusList = doSourceReplication($config, $statusList);
|
279 |
$statusList = doSourceReplication($config, $statusList);
|
| 260 |
writeStatusFile($config->{status_file}, $statusList);
|
280 |
writeStatusFile($config->{status_file}, $statusList);
|
| 261 |
# source server logic here
|
- |
|
| 262 |
} elsif ( $servername eq $config->{target_server}->{hostname} ) {
|
281 |
} elsif ( $runningAs eq 'target' ) {
|
| 263 |
logMsg "Running as target server";
|
282 |
logMsg "Running as target server";
|
| 264 |
die "Target Server code not complete\n";
|
283 |
die "Target Server code not complete\n";
|
| 265 |
die "GELI target server logic not yet implemented\n" if ( defined $config->{target_server}->{geli} );
|
284 |
die "GELI target server logic not yet implemented\n" if ( defined $config->{target_server}->{geli} );
|
| 266 |
mountGeli( $config->{target_server}->{geli} ) if ( defined $config->{target_server}->{geli} );
|
285 |
mountGeli( $config->{target_server}->{geli} ) if ( defined $config->{target_server}->{geli} );
|
| 267 |
} else {
|
286 |
} else {
|
| 268 |
logMsg "This server ($servername) is neither source nor target server as per config\n";
|
287 |
logMsg "This server ($servername) is neither source nor target server as per config\n";
|
| 269 |
die;
|
288 |
die;
|
| 270 |
}
|
289 |
}
|
| 271 |
|
290 |
|
| - |
|
291 |
# add disk space utilization information on transport to the log
|
| - |
|
292 |
logMsg( "Disk space utilization on transport disk:\n" . runCmd( "df -h $config->{transport}->{mount_point}" ) . "\n" );
|
| - |
|
293 |
# add information about the server (zpools) to the log
|
| - |
|
294 |
logMsg( "Zpools on server $servername:\n" . join( "\n", runCmd( "zpool list" ) ) . "\n" );
|
| - |
|
295 |
|
| 272 |
# unmount the sneakernet drive
|
296 |
# unmount the sneakernet drive
|
| 273 |
`umount $config->{transport}->{mount_point}`;
|
297 |
`umount $config->{transport}->{mount_point}`;
|
| 274 |
# and remove the directory
|
298 |
# and remove the directory
|
| 275 |
rmdir $config->{transport}->{mount_point};
|
299 |
rmdir $config->{transport}->{mount_point};
|
| - |
|
300 |
sendReport( $config->{$runningAs}->{report}, "sneakernet replication completed on server $servername", $config->{log_file} );
|
| - |
|
301 |
|
| 276 |
|
302 |
|
| 277 |
1;
|
303 |
1;
|
| 278 |
|
304 |
|
| 279 |
|
305 |
|
| 280 |
#`cat $config->{input} | openssl enc -aes-256-cbc -K $config->{key} -iv $config->{IV} > $config->{output}`;
|
306 |
#`cat $config->{input} | openssl enc -aes-256-cbc -K $config->{key} -iv $config->{IV} > $config->{output}`;
|