Subversion Repositories zfs_utils

Rev

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

Rev Author Line No. Line
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
 
30 rodolico 71
# read the status file and return as list
72
sub getStatusFile {
73
   my $filename = shift;
74
   # read in history/status file
75
   my @lines;
76
   if ( -e $filename && open my $fh, '<', $filename ) {
77
      chomp( @lines = <$fh> );
78
      close $fh;
79
      logMsg("Read status file '$filename' with contents:\n" . join( "\n", @lines ) . "\n");
80
   } else {
81
      logMsg("Error: could not read status file '$filename': $!");
82
      die;
83
   }
84
   return \@lines;
85
}
24 rodolico 86
 
30 rodolico 87
# write the status list to file
88
sub writeStatusFile {
89
   my ( $filename, $statusList ) = @_;
90
   # backup existing status file
91
   if ( -e $filename ) {
92
      rename( $filename, "$filename.bak" ) or do {
93
         logMsg("Error: could not backup existing status file '$filename': $!");
94
         die;
95
      };
96
   }
97
   # write new status file
98
   if ( open my $fh, '>', $filename ) {
99
      foreach my $line ( @$statusList ) {
100
         print $fh "$line\n";
101
      }
102
      close $fh;
103
      logMsg("Wrote status file '$filename' with contents:\n" . join( "\n", @$statusList ) . "\n");
104
   } else {
105
      logMsg("Error: could not write status file '$filename': $!");
106
      die;
107
   }
108
}
109
 
110
# perform replication on source server
111
# $config - configuration hashref
112
# $statusList - list of last snapshots replicated for each dataset in previous replications
113
# return new status list after replication containing updated last snapshots
114
# this script will actually replicate the datasets to the sneakernet disk
115
sub doSourceReplication {
116
   my ($config, $statusList) = @_;
117
   my $newStatus = [];
118
   foreach my $dataset ( sort keys %{$config->{datasets}} ) {
119
      logMsg("Processing dataset '$dataset'\n");
120
      my $sourceList = [ runCmd( "zfs", "list", "-rt", "snap", "-H", "-o", "name", $config->{datasets}->{$dataset}->{source} ) ];
121
 
122
      # process dataset here
123
      my $commands = makeReplicateCommands($sourceList, $statusList, $newStatus );
124
      foreach my $cmd ( @$commands ) {
125
         logMsg("Running command: $cmd\n");
126
         #runCmd( split( /\s+/, $cmd ) );
127
      }
128
   }
129
   return $newStatus;
130
}
131
 
132
 
133
##################### main program starts here #####################
134
# Example to create a random key for encryption/decryption:
24 rodolico 135
# generate a random key with
136
# openssl rand 32 | xxd -p | tr -d '\n' > test.key
137
 
138
# If a YAML config file exists next to the script, load and merge it
139
$config = loadConfig($configFileName, $config );
27 rodolico 140
 
25 rodolico 141
# set some defaults
142
$config->{'status_file'} = "$0.status" unless ( defined $config->{'status_file'} );
24 rodolico 143
 
25 rodolico 144
 
24 rodolico 145
die "Invalid config file: missing source and/or target server\n"
146
    unless (defined $config->{source_server} && defined $config->{target_server});
147
 
148
my $servername = `hostname -s`;
149
chomp $servername;
150
if ( $servername eq $config->{source_server}->{hostname} ) {
25 rodolico 151
    logMsg "Running as source server\n";
30 rodolico 152
    my $statusList = getStatusFile($config->{status_file});
153
    $statusList = doSourceReplication($config, $statusList);
154
    writeStatusFile($config->{status_file}, $statusList);
24 rodolico 155
    # source server logic here
156
} elsif ( $servername eq $config->{target_server}->{hostname} ) {
25 rodolico 157
    logMsg "Running as target server\n";
30 rodolico 158
    die "GELI target server logic not yet implemented\n" if ( defined $config->{target_server}->{geli} );
24 rodolico 159
    mountGeli( $config->{target_server}->{geli} ) if ( defined $config->{target_server}->{geli} );
160
} else {
25 rodolico 161
    logMsg "This server ($servername) is neither source nor target server as per config\n";
162
    die;
24 rodolico 163
}
164
 
30 rodolico 165
die "Source and target server logic not yet implemented\n";
24 rodolico 166
 
30 rodolico 167
#my $newStatus = [];
168
#foreach my $dataset ( sort keys %{$config->{datasets}} ) {
169
#   logMsg("Processing dataset '$dataset'");
170
#   my $sourceList = [ runCmd( "zfs", "list", "-rt", "snap", "-H", "-o", "name", $config->{datasets}->{$dataset}->{source} ) ];
171
#   # process dataset here
172
#   my $commands = makeReplicateCommands($sourceList, $targetList, $newStatus );
173
#   foreach my $cmd ( @$commands ) {
174
#      $cmd .= " > $config->{transport}->{mount_point}/" . $dataset;
175
#      logMsg("Running command: $cmd");
176
#      #runCmd( split( /\s+/, $cmd ) );
177
#   }
178
#}
25 rodolico 179
 
30 rodolico 180
 
181
 
25 rodolico 182
1;
183
 
184
 
24 rodolico 185
#`cat $config->{input} | openssl enc -aes-256-cbc -K $config->{key} -iv $config->{IV} > $config->{output}`;
186
 
187
# this will decrypt $config->{output} to stdout
188
#`cat $config->{output} | openssl enc -aes-256-cbc -d -K $config->{key} -iv $config->{IV} > test.out`;