]> git.proxmox.com Git - pve-zsync.git/blame - pve-zsync
improve error msg
[pve-zsync.git] / pve-zsync
CommitLineData
0bc3e510
WL
1#!/usr/bin/perl
2
0bc3e510
WL
3use strict;
4use warnings;
5use Data::Dumper qw(Dumper);
6use Fcntl qw(:flock SEEK_END);
76b2c677
WL
7use Getopt::Long qw(GetOptionsFromArray);
8use File::Copy qw(move);
21a673e6 9use File::Path qw(make_path);
0bc3e510 10use Switch;
76b2c677
WL
11use JSON;
12use IO::File;
dc54fce8 13use String::ShellQuote 'shell_quote';
0bc3e510 14
c85692fa 15my $PROGNAME = "pve-zsync";
d9e8f4ec
WL
16my $CONFIG_PATH = "/var/lib/${PROGNAME}";
17my $STATE = "${CONFIG_PATH}/sync_state";
c85692fa 18my $CRONJOBS = "/etc/cron.d/$PROGNAME";
d9e8f4ec
WL
19my $PATH = "/usr/sbin";
20my $PVE_DIR = "/etc/pve/local";
21my $QEMU_CONF = "${PVE_DIR}/qemu-server";
22my $LXC_CONF = "${PVE_DIR}/lxc";
23my $LOCKFILE = "$CONFIG_PATH/${PROGNAME}.lock";
24my $PROG_PATH = "$PATH/${PROGNAME}";
76b2c677
WL
25my $INTERVAL = 15;
26my $DEBUG = 0;
c85692fa 27
db2ce6d4
WB
28my $IPV4OCTET = "(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])";
29my $IPV4RE = "(?:(?:$IPV4OCTET\\.){3}$IPV4OCTET)";
30my $IPV6H16 = "(?:[0-9a-fA-F]{1,4})";
31my $IPV6LS32 = "(?:(?:$IPV4RE|$IPV6H16:$IPV6H16))";
32
33my $IPV6RE = "(?:" .
eee21241
WL
34 "(?:(?:" . "(?:$IPV6H16:){6})$IPV6LS32)|" .
35 "(?:(?:" . "::(?:$IPV6H16:){5})$IPV6LS32)|" .
36 "(?:(?:(?:" . "$IPV6H16)?::(?:$IPV6H16:){4})$IPV6LS32)|" .
37 "(?:(?:(?:(?:$IPV6H16:){0,1}$IPV6H16)?::(?:$IPV6H16:){3})$IPV6LS32)|" .
38 "(?:(?:(?:(?:$IPV6H16:){0,2}$IPV6H16)?::(?:$IPV6H16:){2})$IPV6LS32)|" .
39 "(?:(?:(?:(?:$IPV6H16:){0,3}$IPV6H16)?::(?:$IPV6H16:){1})$IPV6LS32)|" .
40 "(?:(?:(?:(?:$IPV6H16:){0,4}$IPV6H16)?::" . ")$IPV6LS32)|" .
41 "(?:(?:(?:(?:$IPV6H16:){0,5}$IPV6H16)?::" . ")$IPV6H16)|" .
42 "(?:(?:(?:(?:$IPV6H16:){0,6}$IPV6H16)?::" . ")))";
db2ce6d4
WB
43
44my $HOSTv4RE0 = "(?:[\\w\\.\\-_]+|$IPV4RE)"; # hostname or ipv4 address
45my $HOSTv4RE1 = "(?:$HOSTv4RE0|\\[$HOSTv4RE0\\])"; # these may be in brackets, too
46my $HOSTRE = "(?:$HOSTv4RE1|\\[$IPV6RE\\])"; # ipv6 must always be in brackets
47# targets are either a VMID, or a 'host:zpool/path' with 'host:' being optional
48my $TARGETRE = qr!^(?:($HOSTRE):)?(\d+|(?:[\w\-_]+)(/.+)?)$!;
49
0bc3e510
WL
50check_bin ('cstream');
51check_bin ('zfs');
52check_bin ('ssh');
53check_bin ('scp');
54
55sub check_bin {
56 my ($bin) = @_;
57
58 foreach my $p (split (/:/, $ENV{PATH})) {
eee21241
WL
59 my $fn = "$p/$bin";
60 if (-x $fn) {
61 return $fn;
0bc3e510
WL
62 }
63 }
64
76b2c677 65 die "unable to find command '$bin'\n";
0bc3e510
WL
66}
67
c85692fa
WL
68sub cut_target_width {
69 my ($target, $max) = @_;
0bc3e510 70
c85692fa
WL
71 return $target if (length($target) <= $max);
72 my @spl = split('/', $target);
0bc3e510
WL
73
74 my $count = length($spl[@spl-1]);
eee21241 75 return "..\/".substr($spl[@spl-1],($count-$max)+3 , $count) if $count > $max;
0bc3e510
WL
76
77 $count += length($spl[0]) if @spl > 1;
78 return substr($spl[0], 0, $max-4-length($spl[@spl-1]))."\/..\/".$spl[@spl-1] if $count > $max;
79
c85692fa 80 my $rest = 1;
0bc3e510
WL
81 $rest = $max-$count if ($max-$count > 0);
82
c85692fa 83 return "$spl[0]".substr($target, length($spl[0]), $rest)."..\/".$spl[@spl-1];
0bc3e510
WL
84}
85
86sub lock {
87 my ($fh) = @_;
76b2c677 88 flock($fh, LOCK_EX) || die "Can't lock config - $!\n";
0bc3e510
WL
89}
90
91sub unlock {
92 my ($fh) = @_;
76b2c677 93 flock($fh, LOCK_UN) || die "Can't unlock config- $!\n";
0bc3e510
WL
94}
95
76b2c677
WL
96sub get_status {
97 my ($source, $name, $status) = @_;
d3e1a943 98
76b2c677 99 if ($status->{$source->{all}}->{$name}->{status}) {
d3e1a943 100 return $status;
0bc3e510
WL
101 }
102
103 return undef;
104}
105
78d36df7 106sub check_pool_exists {
76b2c677 107 my ($target) = @_;
0bc3e510 108
271c2572
WB
109 my $cmd = [];
110 push @$cmd, 'ssh', "root\@$target->{ip}", '--', if $target->{ip};
111 push @$cmd, 'zfs', 'list', '-H', '--', $target->{all};
0bc3e510
WL
112 eval {
113 run_cmd($cmd);
114 };
115
76b2c677 116 if ($@) {
0bc3e510
WL
117 return 1;
118 }
119 return undef;
120}
121
76b2c677
WL
122sub parse_target {
123 my ($text) = @_;
0bc3e510 124
76b2c677
WL
125 my $errstr = "$text : is not a valid input! Use [IP:]<VMID> or [IP:]<ZFSPool>[/Path]";
126 my $target = {};
0bc3e510 127
db2ce6d4
WB
128 if ($text !~ $TARGETRE) {
129 die "$errstr\n";
76b2c677 130 }
db2ce6d4
WB
131 $target->{all} = $2;
132 $target->{ip} = $1 if $1;
133 my @parts = split('/', $2);
76b2c677 134
db2ce6d4 135 $target->{ip} =~ s/^\[(.*)\]$/$1/ if $target->{ip};
76b2c677 136
db2ce6d4
WB
137 my $pool = $target->{pool} = shift(@parts);
138 die "$errstr\n" if !$pool;
139
140 if ($pool =~ m/^\d+$/) {
141 $target->{vmid} = $pool;
76b2c677
WL
142 delete $target->{pool};
143 }
144
145 return $target if (@parts == 0);
146 $target->{last_part} = pop(@parts);
147
148 if ($target->{ip}) {
149 pop(@parts);
150 }
151 if (@parts > 0) {
152 $target->{path} = join('/', @parts);
153 }
154
155 return $target;
0bc3e510
WL
156}
157
76b2c677 158sub read_cron {
0bc3e510 159
76b2c677
WL
160 #This is for the first use to init file;
161 if (!-e $CRONJOBS) {
162 my $new_fh = IO::File->new("> $CRONJOBS");
163 die "Could not create $CRONJOBS: $!\n" if !$new_fh;
164 close($new_fh);
0bc3e510
WL
165 return undef;
166 }
167
76b2c677
WL
168 my $fh = IO::File->new("< $CRONJOBS");
169 die "Could not open file $CRONJOBS: $!\n" if !$fh;
0bc3e510 170
76b2c677 171 my @text = <$fh>;
0bc3e510 172
1a7871e7
WL
173 close($fh);
174
76b2c677
WL
175 return encode_cron(@text);
176}
0bc3e510 177
76b2c677
WL
178sub parse_argv {
179 my (@arg) = @_;
180
181 my $param = {};
182 $param->{dest} = undef;
183 $param->{source} = undef;
184 $param->{verbose} = undef;
185 $param->{limit} = undef;
186 $param->{maxsnap} = undef;
187 $param->{name} = undef;
188 $param->{skip} = undef;
189 $param->{method} = undef;
190
191 my ($ret, $ar) = GetOptionsFromArray(\@arg,
eee21241
WL
192 'dest=s' => \$param->{dest},
193 'source=s' => \$param->{source},
194 'verbose' => \$param->{verbose},
195 'limit=i' => \$param->{limit},
196 'maxsnap=i' => \$param->{maxsnap},
197 'name=s' => \$param->{name},
198 'skip' => \$param->{skip},
199 'method=s' => \$param->{method});
76b2c677
WL
200
201 if ($ret == 0) {
202 die "can't parse options\n";
203 }
204
205 $param->{name} = "default" if !$param->{name};
206 $param->{maxsnap} = 1 if !$param->{maxsnap};
207 $param->{method} = "ssh" if !$param->{method};
208
209 return $param;
0bc3e510
WL
210}
211
76b2c677
WL
212sub add_state_to_job {
213 my ($job) = @_;
28006d67 214
76b2c677
WL
215 my $states = read_state();
216 my $state = $states->{$job->{source}}->{$job->{name}};
217
218 $job->{state} = $state->{state};
219 $job->{lsync} = $state->{lsync};
67badfe4 220 $job->{vm_type} = $state->{vm_type};
76b2c677
WL
221
222 for (my $i = 0; $state->{"snap$i"}; $i++) {
223 $job->{"snap$i"} = $state->{"snap$i"};
0bc3e510 224 }
c85692fa 225
76b2c677 226 return $job;
0bc3e510
WL
227}
228
76b2c677
WL
229sub encode_cron {
230 my (@text) = @_;
231
0bc3e510 232 my $cfg = {};
0bc3e510 233
76b2c677
WL
234 while (my $line = shift(@text)) {
235
236 my @arg = split('\s', $line);
237 my $param = parse_argv(@arg);
238
239 if ($param->{source} && $param->{dest}) {
240 $cfg->{$param->{source}}->{$param->{name}}->{dest} = $param->{dest};
241 $cfg->{$param->{source}}->{$param->{name}}->{verbose} = $param->{verbose};
242 $cfg->{$param->{source}}->{$param->{name}}->{limit} = $param->{limit};
243 $cfg->{$param->{source}}->{$param->{name}}->{maxsnap} = $param->{maxsnap};
244 $cfg->{$param->{source}}->{$param->{name}}->{skip} = $param->{skip};
245 $cfg->{$param->{source}}->{$param->{name}}->{method} = $param->{method};
0bc3e510
WL
246 }
247 }
76b2c677 248
0bc3e510
WL
249 return $cfg;
250}
251
76b2c677
WL
252sub param_to_job {
253 my ($param) = @_;
254
255 my $job = {};
256
257 my $source = parse_target($param->{source});
258 my $dest = parse_target($param->{dest}) if $param->{dest};
259
260 $job->{name} = !$param->{name} ? "default" : $param->{name};
261 $job->{dest} = $param->{dest} if $param->{dest};
262 $job->{method} = "local" if !$dest->{ip} && !$source->{ip};
263 $job->{method} = "ssh" if !$job->{method};
264 $job->{limit} = $param->{limit};
265 $job->{maxsnap} = $param->{maxsnap} if $param->{maxsnap};
266 $job->{source} = $param->{source};
267
268 return $job;
269}
270
271sub read_state {
0bc3e510 272
76b2c677 273 if (!-e $STATE) {
21a673e6 274 make_path $CONFIG_PATH;
76b2c677
WL
275 my $new_fh = IO::File->new("> $STATE");
276 die "Could not create $STATE: $!\n" if !$new_fh;
a018f134 277 print $new_fh "{}";
76b2c677
WL
278 close($new_fh);
279 return undef;
280 }
281
282 my $fh = IO::File->new("< $STATE");
283 die "Could not open file $STATE: $!\n" if !$fh;
284
285 my $text = <$fh>;
286 my $states = decode_json($text);
0bc3e510 287
76b2c677
WL
288 close($fh);
289
290 return $states;
291}
292
293sub update_state {
294 my ($job) = @_;
295 my $text;
296 my $in_fh;
297
298 eval {
0bc3e510 299
76b2c677
WL
300 $in_fh = IO::File->new("< $STATE");
301 die "Could not open file $STATE: $!\n" if !$in_fh;
302 lock($in_fh);
303 $text = <$in_fh>;
304 };
305
306 my $out_fh = IO::File->new("> $STATE.new");
307 die "Could not open file ${STATE}.new: $!\n" if !$out_fh;
308
309 my $states = {};
310 my $state = {};
311 if ($text){
312 $states = decode_json($text);
313 $state = $states->{$job->{source}}->{$job->{name}};
314 }
315
316 if ($job->{state} ne "del") {
317 $state->{state} = $job->{state};
318 $state->{lsync} = $job->{lsync};
67badfe4 319 $state->{vm_type} = $job->{vm_type};
76b2c677
WL
320
321 for (my $i = 0; $job->{"snap$i"} ; $i++) {
322 $state->{"snap$i"} = $job->{"snap$i"};
0bc3e510 323 }
76b2c677
WL
324 $states->{$job->{source}}->{$job->{name}} = $state;
325 } else {
326
327 delete $states->{$job->{source}}->{$job->{name}};
328 delete $states->{$job->{source}} if !keys %{$states->{$job->{source}}};
329 }
330
331 $text = encode_json($states);
332 print $out_fh $text;
333
334 close($out_fh);
335 move("$STATE.new", $STATE);
336 eval {
337 close($in_fh);
338 };
339
340 return $states;
341}
342
343sub update_cron {
344 my ($job) = @_;
345
346 my $updated;
347 my $has_header;
348 my $line_no = 0;
349 my $text = "";
350 my $header = "SHELL=/bin/sh\n";
351 $header .= "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n\n";
352
353 my $fh = IO::File->new("< $CRONJOBS");
354 die "Could not open file $CRONJOBS: $!\n" if !$fh;
355 lock($fh);
0bc3e510 356
76b2c677
WL
357 my @test = <$fh>;
358
359 while (my $line = shift(@test)) {
360 chomp($line);
361 if ($line =~ m/source $job->{source} .*name $job->{name} /) {
362 $updated = 1;
363 next if $job->{state} eq "del";
364 $text .= format_job($job, $line);
0bc3e510 365 } else {
76b2c677
WL
366 if (($line_no < 3) && ($line =~ /^(PATH|SHELL)/ )) {
367 $has_header = 1;
0bc3e510 368 }
76b2c677 369 $text .= "$line\n";
0bc3e510 370 }
76b2c677
WL
371 $line_no++;
372 }
373
374 if (!$has_header) {
375 $text = "$header$text";
376 }
0bc3e510 377
76b2c677 378 if (!$updated) {
eee21241 379 $text .= format_job($job);
0bc3e510 380 }
76b2c677
WL
381 my $new_fh = IO::File->new("> ${CRONJOBS}.new");
382 die "Could not open file ${CRONJOBS}.new: $!\n" if !$new_fh;
383
384 die "can't write to $CRONJOBS.new\n" if !print($new_fh $text);
385 close ($new_fh);
386
387 die "can't move $CRONJOBS.new: $!\n" if !move("${CRONJOBS}.new", "$CRONJOBS");
388 close ($fh);
0bc3e510
WL
389}
390
76b2c677
WL
391sub format_job {
392 my ($job, $line) = @_;
393 my $text = "";
0bc3e510 394
6b4f676d 395 if ($job->{state} eq "stopped") {
76b2c677
WL
396 $text = "#";
397 }
398 if ($line) {
399 $line =~ /^#*(.+) root/;
400 $text .= $1;
401 } else {
402 $text .= "*/$INTERVAL * * * *";
403 }
404 $text .= " root";
405 $text .= " $PROGNAME sync --source $job->{source} --dest $job->{dest}";
406 $text .= " --name $job->{name} --maxsnap $job->{maxsnap}";
407 $text .= " --method $job->{method}";
408 $text .= " --verbose" if $job->{verbose};
409 $text .= "\n";
410
411 return $text;
412}
0bc3e510 413
76b2c677 414sub list {
d3e1a943 415
76b2c677 416 my $cfg = read_cron();
28006d67 417
67badfe4 418 my $list = sprintf("%-25s%-10s%-7s%-20s%-5s%-5s\n" , "SOURCE", "NAME", "STATE", "LAST SYNC", "TYPE", "CON");
28006d67 419
76b2c677
WL
420 my $states = read_state();
421 foreach my $source (sort keys%{$cfg}) {
422 foreach my $name (sort keys%{$cfg->{$source}}) {
423 $list .= sprintf("%-25s", cut_target_width($source, 25));
67badfe4 424 $list .= sprintf("%-10s", cut_target_width($name, 10));
76b2c677 425 $list .= sprintf("%-7s", $states->{$source}->{$name}->{state});
eee21241
WL
426 $list .= sprintf("%-20s", $states->{$source}->{$name}->{lsync});
427 $list .= sprintf("%-5s", $states->{$source}->{$name}->{vm_type});
428 $list .= sprintf("%-5s\n", $cfg->{$source}->{$name}->{method});
0bc3e510
WL
429 }
430 }
431
432 return $list;
433}
434
435sub vm_exists {
436 my ($target) = @_;
eee21241 437
8fc69e3c 438 my @cmd = ('ssh', "root\@$target->{ip}", '--') if $target->{ip};
0bc3e510 439
8fc69e3c
WL
440 my $res = undef;
441
d9e8f4ec 442 eval { $res = run_cmd([@cmd, 'ls', "$QEMU_CONF/$target->{vmid}.conf"]) };
8fc69e3c
WL
443
444 return "qemu" if $res;
0bc3e510 445
d9e8f4ec 446 eval { $res = run_cmd([@cmd, 'ls', "$LXC_CONF/$target->{vmid}.conf"]) };
8fc69e3c
WL
447
448 return "lxc" if $res;
0bc3e510 449
0bc3e510
WL
450 return undef;
451}
452
453sub init {
454 my ($param) = @_;
455
76b2c677 456 my $cfg = read_cron();
0bc3e510 457
76b2c677 458 my $job = param_to_job($param);
0bc3e510 459
76b2c677
WL
460 $job->{state} = "ok";
461 $job->{lsync} = 0;
0bc3e510
WL
462
463 my $source = parse_target($param->{source});
464 my $dest = parse_target($param->{dest});
465
76b2c677 466 if (my $ip = $dest->{ip}) {
271c2572 467 run_cmd(['ssh-copy-id', '-i', '/root/.ssh/id_rsa.pub', "root\@$ip"]);
0bc3e510
WL
468 }
469
c85692fa 470 if (my $ip = $source->{ip}) {
271c2572 471 run_cmd(['ssh-copy-id', '-i', '/root/.ssh/id_rsa.pub', "root\@$ip"]);
0bc3e510
WL
472 }
473
78d36df7 474 die "Pool $dest->{all} does not exists\n" if check_pool_exists($dest);
0bc3e510 475
78d36df7 476 my $check = check_pool_exists($source->{path}, $source->{ip}) if !$source->{vmid} && $source->{path};
0bc3e510 477
76b2c677 478 die "Pool $source->{path} does not exists\n" if undef($check);
8362418a 479
8fc69e3c 480 my $vm_type = vm_exists($source);
67badfe4 481 $job->{vm_type} = $vm_type;
8fc69e3c
WL
482
483 die "VM $source->{vmid} doesn't exist\n" if $param->{vmid} && !$vm_type;
0bc3e510 484
76b2c677 485 die "Config already exists\n" if $cfg->{$job->{source}}->{$job->{name}};
0bc3e510 486
76b2c677
WL
487 update_cron($job);
488 update_state($job);
c85692fa
WL
489
490 eval {
491 sync($param) if !$param->{skip};
492 };
1a7871e7 493 if(my $err = $@) {
c85692fa 494 destroy_job($param);
1a7871e7
WL
495 print $err;
496 }
0bc3e510
WL
497}
498
76b2c677
WL
499sub get_job {
500 my ($param) = @_;
501
502 my $cfg = read_cron();
503
504 if (!$cfg->{$param->{source}}->{$param->{name}}) {
505 die "Job with source $param->{source} and name $param->{name} does not exist\n" ;
506 }
507 my $job = $cfg->{$param->{source}}->{$param->{name}};
508 $job->{name} = $param->{name};
509 $job->{source} = $param->{source};
510 $job = add_state_to_job($job);
511
512 return $job;
513}
514
c85692fa 515sub destroy_job {
0bc3e510
WL
516 my ($param) = @_;
517
76b2c677
WL
518 my $job = get_job($param);
519 $job->{state} = "del";
b0842810 520
76b2c677
WL
521 update_cron($job);
522 update_state($job);
0bc3e510
WL
523}
524
525sub sync {
526 my ($param) = @_;
76b2c677
WL
527
528 my $lock_fh = IO::File->new("> $LOCKFILE");
529 die "Can't open Lock File: $LOCKFILE $!\n" if !$lock_fh;
b0842810 530 lock($lock_fh);
0bc3e510 531
76b2c677
WL
532 my $date = get_date();
533 my $job;
534 eval {
535 $job = get_job($param);
536 };
0bc3e510 537
a018f134
WL
538 if ($job && $job->{state} eq "syncing") {
539 die "Job --source $param->{source} --name $param->{name} is syncing at the moment";
76b2c677 540 }
0bc3e510
WL
541
542 my $dest = parse_target($param->{dest});
543 my $source = parse_target($param->{source});
544
545 my $sync_path = sub {
76b2c677 546 my ($source, $dest, $job, $param, $date) = @_;
0bc3e510 547
eee21241 548 ($source->{old_snap}, $source->{last_snap}) = snapshot_get($source, $dest, $param->{maxsnap}, $param->{name});
6b4f676d 549
a018f134 550 snapshot_add($source, $dest, $param->{name}, $date);
0bc3e510 551
a018f134
WL
552 send_image($source, $dest, $param);
553
554 snapshot_destroy($source, $dest, $param->{method}, $source->{old_snap}) if ($source->{destroy} && $source->{old_snap});
0bc3e510 555
0bc3e510
WL
556 };
557
8fc69e3c
WL
558 my $vm_type = vm_exists($source);
559 $source->{vm_type} = $vm_type;
560
76b2c677 561 if ($job) {
76b2c677 562 $job->{state} = "syncing";
8fc69e3c 563 $job->{vm_type} = $vm_type if !$job->{vm_type};
76b2c677
WL
564 update_state($job);
565 }
0bc3e510 566
a018f134
WL
567 eval{
568 if ($source->{vmid}) {
8fc69e3c 569 die "VM $source->{vmid} doesn't exist\n" if !$vm_type;
a018f134
WL
570 my $disks = get_disks($source);
571
572 foreach my $disk (sort keys %{$disks}) {
573 $source->{all} = $disks->{$disk}->{all};
574 $source->{pool} = $disks->{$disk}->{pool};
575 $source->{path} = $disks->{$disk}->{path} if $disks->{$disk}->{path};
576 $source->{last_part} = $disks->{$disk}->{last_part};
577 &$sync_path($source, $dest, $job, $param, $date);
578 }
c9cbba3d 579 if ($param->{method} eq "ssh" && ($source->{ip} || $dest->{ip})) {
a018f134 580 send_config($source, $dest,'ssh');
c9cbba3d
WL
581 } else {
582 send_config($source, $dest,'local');
a018f134
WL
583 }
584 } else {
76b2c677 585 &$sync_path($source, $dest, $job, $param, $date);
0bc3e510 586 }
a018f134
WL
587 };
588 if(my $err = $@) {
589 if ($job) {
590 $job->{state} = "error";
591 update_state($job);
592 unlock($lock_fh);
593 close($lock_fh);
594 print "Job --source $param->{source} --name $param->{name} got an ERROR!!!\nERROR Message:\n";
7d93705f 595 }
a018f134 596 die "$err\n";
76b2c677
WL
597 }
598
599 if ($job) {
600 $job->{state} = "ok";
601 $job->{lsync} = $date;
602 update_state($job);
0bc3e510 603 }
76b2c677 604
c85692fa 605 unlock($lock_fh);
b0842810 606 close($lock_fh);
0bc3e510
WL
607}
608
609sub snapshot_get{
610 my ($source, $dest, $max_snap, $name) = @_;
611
271c2572
WB
612 my $cmd = [];
613 push @$cmd, 'ssh', "root\@$source->{ip}", '--', if $source->{ip};
614 push @$cmd, 'zfs', 'list', '-r', '-t', 'snapshot', '-Ho', 'name', '-S', 'creation';
615 push @$cmd, $source->{all};
0bc3e510
WL
616
617 my $raw = run_cmd($cmd);
76b2c677 618 my $index = 0;
0bc3e510
WL
619 my $line = "";
620 my $last_snap = undef;
76b2c677 621 my $old_snap;
0bc3e510
WL
622
623 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
624 $line = $1;
76b2c677 625 if ($line =~ m/(rep_$name.*)$/) {
d9e8f4ec 626
76b2c677
WL
627 $last_snap = $1 if (!$last_snap);
628 $old_snap = $1;
629 $index++;
630 if ($index == $max_snap) {
631 $source->{destroy} = 1;
632 last;
633 };
634 }
0bc3e510
WL
635 }
636
76b2c677 637 return ($old_snap, $last_snap) if $last_snap;
0bc3e510
WL
638
639 return undef;
640}
641
642sub snapshot_add {
76b2c677 643 my ($source, $dest, $name, $date) = @_;
0bc3e510
WL
644
645 my $snap_name = "rep_$name\_".$date;
646
647 $source->{new_snap} = $snap_name;
648
76b2c677 649 my $path = "$source->{all}\@$snap_name";
0bc3e510 650
271c2572
WB
651 my $cmd = [];
652 push @$cmd, 'ssh', "root\@$source->{ip}", '--', if $source->{ip};
653 push @$cmd, 'zfs', 'snapshot', $path;
0bc3e510
WL
654 eval{
655 run_cmd($cmd);
656 };
657
c85692fa 658 if (my $err = $@) {
0bc3e510
WL
659 snapshot_destroy($source, $dest, 'ssh', $snap_name);
660 die "$err\n";
661 }
0bc3e510
WL
662}
663
c85692fa 664sub write_cron {
28006d67 665 my ($cfg) = @_;
0bc3e510 666
c85692fa
WL
667 my $text = "SHELL=/bin/sh\n";
668 $text .= "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n";
2e8fd72a 669
76b2c677
WL
670 my $fh = IO::File->new("> $CRONJOBS");
671 die "Could not open file: $!\n" if !$fh;
0bc3e510 672
c85692fa
WL
673 foreach my $source (sort keys%{$cfg}) {
674 foreach my $sync_name (sort keys%{$cfg->{$source}}) {
28006d67 675 next if $cfg->{$source}->{$sync_name}->{status} ne 'ok';
eee21241
WL
676 $text .= "$PROG_PATH sync";
677 $text .= " -source ";
678 if ($cfg->{$source}->{$sync_name}->{vmid}) {
679 $text .= "$cfg->{$source}->{$sync_name}->{source_ip}:" if $cfg->{$source}->{$sync_name}->{source_ip};
680 $text .= "$cfg->{$source}->{$sync_name}->{vmid} ";
681 } else {
682 $text .= "$cfg->{$source}->{$sync_name}->{source_ip}:" if $cfg->{$source}->{$sync_name}->{source_ip};
683 $text .= "$cfg->{$source}->{$sync_name}->{source_pool}";
684 $text .= "$cfg->{$source}->{$sync_name}->{source_path}" if $cfg->{$source}->{$sync_name}->{source_path};
685 }
686 $text .= " -dest ";
687 $text .= "$cfg->{$source}->{$sync_name}->{dest_ip}:" if $cfg->{$source}->{$sync_name}->{dest_ip};
688 $text .= "$cfg->{$source}->{$sync_name}->{dest_pool}";
689 $text .= "$cfg->{$source}->{$sync_name}->{dest_path}" if $cfg->{$source}->{$sync_name}->{dest_path};
690 $text .= " -name $sync_name ";
691 $text .= " -limit $cfg->{$source}->{$sync_name}->{limit}" if $cfg->{$source}->{$sync_name}->{limit};
692 $text .= " -maxsnap $cfg->{$source}->{$sync_name}->{maxsnap}" if $cfg->{$source}->{$sync_name}->{maxsnap};
693 $text .= "\n";
0bc3e510
WL
694 }
695 }
c85692fa 696 die "Can't write to cron\n" if (!print($fh $text));
0bc3e510
WL
697 close($fh);
698}
699
700sub get_disks {
701 my ($target) = @_;
702
271c2572
WB
703 my $cmd = [];
704 push @$cmd, 'ssh', "root\@$target->{ip}", '--', if $target->{ip};
9e7685c2
WL
705
706 if ($target->{vm_type} eq 'qemu') {
707 push @$cmd, 'qm', 'config', $target->{vmid};
708 } elsif ($target->{vm_type} eq 'lxc') {
709 push @$cmd, 'pct', 'config', $target->{vmid};
710 } else {
711 die "VM Type unknown\n";
712 }
0bc3e510
WL
713
714 my $res = run_cmd($cmd);
715
9e7685c2 716 my $disks = parse_disks($res, $target->{ip}, $target->{vm_type});
0bc3e510
WL
717
718 return $disks;
719}
720
721sub run_cmd {
722 my ($cmd) = @_;
723 print "Start CMD\n" if $DEBUG;
724 print Dumper $cmd if $DEBUG;
dc54fce8
WB
725 if (ref($cmd) eq 'ARRAY') {
726 $cmd = join(' ', map { ref($_) ? $$_ : shell_quote($_) } @$cmd);
727 }
0bc3e510
WL
728 my $output = `$cmd 2>&1`;
729
a018f134 730 die "COMMAND:\n\t$cmd\nGET ERROR:\n\t$output" if 0 != $?;
0bc3e510
WL
731
732 chomp($output);
733 print Dumper $output if $DEBUG;
734 print "END CMD\n" if $DEBUG;
735 return $output;
736}
737
738sub parse_disks {
c9cbba3d 739 my ($text, $ip, $vm_type) = @_;
0bc3e510
WL
740
741 my $disks;
742
743 my $num = 0;
0bc3e510
WL
744 while ($text && $text =~ s/^(.*?)(\n|$)//) {
745 my $line = $1;
78cd57dd
WB
746
747 next if $line =~ /cdrom|none/;
9e7685c2 748 next if $line !~ m/^(?:((?:virtio|ide|scsi|sata|mp)\d+)|rootfs): /;
78cd57dd 749
0bc3e510
WL
750 my $disk = undef;
751 my $stor = undef;
9e7685c2
WL
752 if($line =~ m/^(?:((?:virtio|ide|scsi|sata|mp)\d+)|rootfs): (.+:)([A-Za-z0-9\-]+),(.*)$/) {
753 $disk = $3;
754 $stor = $2;
845e36d7
WB
755 } else {
756 die "disk is not on ZFS Storage\n";
0bc3e510
WL
757 }
758
271c2572
WB
759 my $cmd = [];
760 push @$cmd, 'ssh', "root\@$ip", '--' if $ip;
761 push @$cmd, 'pvesm', 'path', "$stor$disk";
b52d13b3
WB
762 my $path = run_cmd($cmd);
763
9303e6fc
WL
764 die "Get no path from pvesm path $stor$disk\n" if !$path;
765
9e7685c2 766 if ($vm_type eq 'qemu' && $path =~ m/^\/dev\/zvol\/(\w+.*)(\/$disk)$/) {
b52d13b3
WB
767
768 my @array = split('/', $1);
3df8b97d 769 $disks->{$num}->{pool} = shift(@array);
b52d13b3
WB
770 $disks->{$num}->{all} = $disks->{$num}->{pool};
771 if (0 < @array) {
772 $disks->{$num}->{path} = join('/', @array);
773 $disks->{$num}->{all} .= "\/$disks->{$num}->{path}";
0bc3e510 774 }
b52d13b3
WB
775 $disks->{$num}->{last_part} = $disk;
776 $disks->{$num}->{all} .= "\/$disk";
777
9e7685c2 778 $num++;
aec521ca 779 } elsif ($vm_type eq 'lxc' && $path =~ m/^\/(\w+.+)(\/(\w+.*))*(\/$disk)$/) {
9e7685c2
WL
780
781 $disks->{$num}->{pool} = $1;
782 $disks->{$num}->{all} = $disks->{$num}->{pool};
783
784 if ($2) {
aec521ca 785 $disks->{$num}->{path} = $3;
9e7685c2
WL
786 $disks->{$num}->{all} .= "\/$disks->{$num}->{path}";
787 }
788
789 $disks->{$num}->{last_part} = $disk;
790 $disks->{$num}->{all} .= "\/$disk";
791
b52d13b3
WB
792 $num++;
793
794 } else {
795 die "ERROR: in path\n";
0bc3e510
WL
796 }
797 }
76b2c677 798
0bc3e510
WL
799 return $disks;
800}
801
802sub snapshot_destroy {
803 my ($source, $dest, $method, $snap) = @_;
804
271c2572 805 my @zfscmd = ('zfs', 'destroy');
76b2c677 806 my $snapshot = "$source->{all}\@$snap";
0bc3e510
WL
807
808 eval {
809 if($source->{ip} && $method eq 'ssh'){
271c2572 810 run_cmd(['ssh', "root\@$source->{ip}", '--', @zfscmd, $snapshot]);
0bc3e510 811 } else {
271c2572 812 run_cmd([@zfscmd, $snapshot]);
0bc3e510
WL
813 }
814 };
815 if (my $erro = $@) {
816 warn "WARN: $erro";
817 }
c85692fa 818 if ($dest) {
271c2572 819 my @ssh = $dest->{ip} ? ('ssh', "root\@$dest->{ip}", '--') : ();
0bc3e510 820
76b2c677 821 my $path = "$dest->{all}\/$source->{last_part}";
0bc3e510 822
0bc3e510 823 eval {
271c2572 824 run_cmd([@ssh, @zfscmd, "$path\@$snap"]);
0bc3e510
WL
825 };
826 if (my $erro = $@) {
827 warn "WARN: $erro";
828 }
829 }
830}
831
832sub snapshot_exist {
eee21241 833 my ($source , $dest, $method) = @_;
0bc3e510 834
271c2572
WB
835 my $cmd = [];
836 push @$cmd, 'ssh', "root\@$dest->{ip}", '--' if $dest->{ip};
837 push @$cmd, 'zfs', 'list', '-rt', 'snapshot', '-Ho', 'name';
838 push @$cmd, "$dest->{all}/$source->{last_part}\@$source->{old_snap}";
0bc3e510
WL
839
840 my $text = "";
841 eval {$text =run_cmd($cmd);};
eee21241 842 if (my $erro =$@) {
0bc3e510
WL
843 warn "WARN: $erro";
844 return undef;
845 }
846
847 while ($text && $text =~ s/^(.*?)(\n|$)//) {
eee21241 848 my $line =$1;
0bc3e510
WL
849 return 1 if $line =~ m/^.*$source->{old_snap}$/;
850 }
851}
852
853sub send_image {
76b2c677 854 my ($source, $dest, $param) = @_;
0bc3e510 855
271c2572 856 my $cmd = [];
0bc3e510 857
271c2572
WB
858 push @$cmd, 'ssh', "root\@$source->{ip}", '--' if $source->{ip};
859 push @$cmd, 'zfs', 'send';
860 push @$cmd, '-v' if $param->{verbose};
0bc3e510 861
eee21241 862 if($source->{last_snap} && snapshot_exist($source , $dest, $param->{method})) {
271c2572 863 push @$cmd, '-i', "$source->{all}\@$source->{last_snap}";
0bc3e510 864 }
271c2572 865 push @$cmd, '--', "$source->{all}\@$source->{new_snap}";
0bc3e510 866
76b2c677
WL
867 if ($param->{limit}){
868 my $bwl = $param->{limit}*1024;
271c2572 869 push @$cmd, \'|', 'cstream', '-t', $bwl;
0bc3e510 870 }
1193273e
WL
871 my $target = "$dest->{all}/$source->{last_part}";
872 $target =~ s!/+!/!g;
873
271c2572 874 push @$cmd, \'|';
eee21241
WL
875 push @$cmd, 'ssh', "root\@$dest->{ip}", '--' if $dest->{ip};
876 push @$cmd, 'zfs', 'recv', '-F', '--';
877 push @$cmd, "$target";
0bc3e510 878
eee21241
WL
879 eval {
880 run_cmd($cmd)
881 };
0bc3e510 882
eee21241
WL
883 if (my $erro = $@) {
884 snapshot_destroy($source, undef, $param->{method}, $source->{new_snap});
885 die $erro;
886 };
887 }
0bc3e510
WL
888
889
eee21241
WL
890 sub send_config{
891 my ($source, $dest, $method) = @_;
0bc3e510 892
d9e8f4ec 893 my $source_target = $source->{vm_type} eq 'qemu' ? "$QEMU_CONF/$source->{vmid}.conf": "$LXC_CONF/$source->{vmid}.conf";
eee21241 894 my $dest_target_new ="$source->{vmid}.conf.$source->{vm_type}.$source->{new_snap}";
76b2c677 895
d9e8f4ec 896 my $config_dir = $dest->{last_part} ? "${CONFIG_PATH}/$dest->{last_part}" : $CONFIG_PATH;
739c195a 897
d9e8f4ec 898 $dest_target_new = $config_dir.'/'.$dest_target_new;
739c195a 899
eee21241
WL
900 if ($method eq 'ssh'){
901 if ($dest->{ip} && $source->{ip}) {
902 run_cmd(['ssh', "root\@$dest->{ip}", '--', 'mkdir', '-p', '--', $config_dir]);
903 run_cmd(['scp', '--', "root\@[$source->{ip}]:$source_target", "root\@[$dest->{ip}]:$dest_target_new"]);
904 } elsif ($dest->{ip}) {
905 run_cmd(['ssh', "root\@$dest->{ip}", '--', 'mkdir', '-p', '--', $config_dir]);
906 run_cmd(['scp', '--', $source_target, "root\@[$dest->{ip}]:$dest_target_new"]);
907 } elsif ($source->{ip}) {
908 run_cmd(['mkdir', '-p', '--', $config_dir]);
909 run_cmd(['scp', '--', "root\@$source->{ip}:$source_target", $dest_target_new]);
910 }
0bc3e510 911
eee21241 912 if ($source->{destroy}){
d9e8f4ec 913 my $dest_target_old ="${config_dir}/$source->{vmid}.conf.$source->{vm_type}.$source->{old_snap}";
eee21241
WL
914 if($dest->{ip}){
915 run_cmd(['ssh', "root\@$dest->{ip}", '--', 'rm', '-f', '--', $dest_target_old]);
916 } else {
917 run_cmd(['rm', '-f', '--', $dest_target_old]);
918 }
0bc3e510 919 }
eee21241
WL
920 } elsif ($method eq 'local') {
921 run_cmd(['mkdir', '-p', '--', $config_dir]);
922 run_cmd(['cp', $source_target, $dest_target_new]);
0bc3e510
WL
923 }
924 }
0bc3e510 925
eee21241
WL
926 sub get_date {
927 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
928 my $datestamp = sprintf ("%04d-%02d-%02d_%02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
0bc3e510 929
eee21241
WL
930 return $datestamp;
931 }
0bc3e510 932
eee21241
WL
933 sub status {
934 my $cfg = read_cron();
0bc3e510 935
eee21241 936 my $status_list = sprintf("%-25s%-15s%-10s\n", "SOURCE", "NAME", "STATUS");
0bc3e510 937
eee21241 938 my $states = read_state();
76b2c677 939
eee21241
WL
940 foreach my $source (sort keys%{$cfg}) {
941 foreach my $sync_name (sort keys%{$cfg->{$source}}) {
942 $status_list .= sprintf("%-25s", cut_target_width($source, 25));
943 $status_list .= sprintf("%-15s", cut_target_width($sync_name, 25));
944 $status_list .= "$states->{$source}->{$sync_name}->{state}\n";
945 }
0bc3e510 946 }
0bc3e510 947
eee21241
WL
948 return $status_list;
949 }
0bc3e510 950
eee21241
WL
951 sub enable_job {
952 my ($param) = @_;
28006d67 953
eee21241
WL
954 my $job = get_job($param);
955 $job->{state} = "ok";
956 update_state($job);
957 update_cron($job);
958 }
28006d67 959
eee21241
WL
960 sub disable_job {
961 my ($param) = @_;
28006d67 962
eee21241
WL
963 my $job = get_job($param);
964 $job->{state} = "stopped";
965 update_state($job);
966 update_cron($job);
967 }
28006d67 968
eee21241 969 my $command = $ARGV[0];
0bc3e510 970
eee21241
WL
971 my $commands = {'destroy' => 1,
972 'create' => 1,
973 'sync' => 1,
974 'list' => 1,
975 'status' => 1,
976 'help' => 1,
977 'enable' => 1,
978 'disable' => 1};
0bc3e510 979
eee21241
WL
980 if (!$command || !$commands->{$command}) {
981 usage();
982 die "\n";
983 }
0bc3e510 984
eee21241 985 my $help_sync = "$PROGNAME sync -dest <string> -source <string> [OPTIONS]\n
0bc3e510
WL
986\twill sync one time\n
987\t-dest\tstring\n
988\t\tthe destination target is like [IP:]<Pool>[/Path]\n
989\t-limit\tinteger\n
990\t\tmax sync speed in kBytes/s, default unlimited\n
991\t-maxsnap\tinteger\n
992\t\thow much snapshots will be kept before get erased, default 1/n
993\t-name\tstring\n
994\t\tname of the sync job, if not set it is default.
995\tIt is only necessary if scheduler allready contains this source.\n
996\t-source\tstring\n
997\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
998
eee21241 999 my $help_create = "$PROGNAME create -dest <string> -source <string> [OPTIONS]/n
0bc3e510 1000\tCreate a sync Job\n
d9e8f4ec 1001\t-dest\tstring\n
0bc3e510 1002\t\tthe destination target is like [IP]:<Pool>[/Path]\n
0bc3e510 1003\t-limit\tinteger\n
28006d67 1004\t\tmax sync speed in kBytes/s, default unlimited\n
0bc3e510
WL
1005\t-maxsnap\tstring\n
1006\t\thow much snapshots will be kept before get erased, default 1\n
1007\t-name\tstring\n
1008\t\tname of the sync job, if not set it is default\n
1009\t-skip\tboolean\n
1010\t\tif this flag is set it will skip the first sync\n
1011\t-source\tstring\n
1012\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
1013
eee21241 1014 my $help_destroy = "$PROGNAME destroy -source <string> [OPTIONS]\n
0bc3e510
WL
1015\tremove a sync Job from the scheduler\n
1016\t-name\tstring\n
1017\t\tname of the sync job, if not set it is default\n
1018\t-source\tstring\n
1019\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
1020
eee21241 1021 my $help_help = "$PROGNAME help <cmd> [OPTIONS]\n
0bc3e510
WL
1022\tGet help about specified command.\n
1023\t<cmd>\tstring\n
1024\t\tCommand name\n
1025\t-verbose\tboolean\n
1026\t\tVerbose output format.\n";
1027
eee21241 1028 my $help_list = "$PROGNAME list\n
0bc3e510
WL
1029\tGet a List of all scheduled Sync Jobs\n";
1030
eee21241 1031 my $help_status = "$PROGNAME status\n
0bc3e510
WL
1032\tGet the status of all scheduled Sync Jobs\n";
1033
eee21241 1034 my $help_enable = "$PROGNAME enable -source <string> [OPTIONS]\n
28006d67
WL
1035\tenable a syncjob and reset error\n
1036\t-name\tstring\n
1037\t\tname of the sync job, if not set it is default\n
1038\t-source\tstring\n
1039\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
1040
eee21241 1041 my $help_disable = "$PROGNAME disable -source <string> [OPTIONS]\n
28006d67
WL
1042\tpause a syncjob\n
1043\t-name\tstring\n
1044\t\tname of the sync job, if not set it is default\n
1045\t-source\tstring\n
1046\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
1047
eee21241
WL
1048 sub help {
1049 my ($command) = @_;
0bc3e510 1050
eee21241
WL
1051 switch($command){
1052 case 'help'
1053 {
1054 die "$help_help\n";
1055 }
1056 case 'sync'
1057 {
1058 die "$help_sync\n";
1059 }
1060 case 'destroy'
1061 {
1062 die "$help_destroy\n";
1063 }
1064 case 'create'
1065 {
1066 die "$help_create\n";
1067 }
1068 case 'list'
1069 {
1070 die "$help_list\n";
1071 }
1072 case 'status'
1073 {
1074 die "$help_status\n";
1075 }
1076 case 'enable'
1077 {
1078 die "$help_enable\n";
1079 }
1080 case 'disable'
1081 {
1082 die "$help_enable\n";
1083 }
1084 }
1085
1086 }
1087
1088 my @arg = @ARGV;
1089 my $param = parse_argv(@arg);
1090
1091
1092 switch($command) {
1093 case "destroy"
0bc3e510 1094 {
eee21241
WL
1095 die "$help_destroy\n" if !$param->{source};
1096 check_target($param->{source});
1097 destroy_job($param);
0bc3e510 1098 }
eee21241 1099 case "sync"
0bc3e510 1100 {
eee21241
WL
1101 die "$help_sync\n" if !$param->{source} || !$param->{dest};
1102 check_target($param->{source});
1103 check_target($param->{dest});
1104 sync($param);
0bc3e510 1105 }
eee21241 1106 case "create"
0bc3e510 1107 {
eee21241
WL
1108 die "$help_create\n" if !$param->{source} || !$param->{dest};
1109 check_target($param->{source});
1110 check_target($param->{dest});
1111 init($param);
0bc3e510 1112 }
eee21241 1113 case "status"
0bc3e510 1114 {
eee21241 1115 print status();
0bc3e510 1116 }
eee21241 1117 case "list"
0bc3e510 1118 {
eee21241 1119 print list();
0bc3e510 1120 }
eee21241 1121 case "help"
0bc3e510 1122 {
eee21241
WL
1123 my $help_command = $ARGV[1];
1124 if ($help_command && $commands->{$help_command}) {
1125 print help($help_command);
1126 }
1127 if ($param->{verbose} == 1){
1128 exec("man $PROGNAME");
1129 } else {
1130 usage(1);
1131 }
0bc3e510 1132 }
eee21241 1133 case "enable"
28006d67 1134 {
eee21241
WL
1135 die "$help_enable\n" if !$param->{source};
1136 check_target($param->{source});
1137 enable_job($param);
28006d67 1138 }
eee21241 1139 case "disable"
28006d67 1140 {
eee21241
WL
1141 die "$help_disable\n" if !$param->{source};
1142 check_target($param->{source});
1143 disable_job($param);
28006d67 1144 }
0bc3e510
WL
1145 }
1146
eee21241
WL
1147 sub usage {
1148 my ($help) = @_;
1149
1150 print("ERROR:\tno command specified\n") if !$help;
1151 print("USAGE:\t$PROGNAME <COMMAND> [ARGS] [OPTIONS]\n");
1152 print("\t$PROGNAME help [<cmd>] [OPTIONS]\n\n");
1153 print("\t$PROGNAME create -dest <string> -source <string> [OPTIONS]\n");
1154 print("\t$PROGNAME destroy -source <string> [OPTIONS]\n");
1155 print("\t$PROGNAME disable -source <string> [OPTIONS]\n");
1156 print("\t$PROGNAME enable -source <string> [OPTIONS]\n");
1157 print("\t$PROGNAME list\n");
1158 print("\t$PROGNAME status\n");
1159 print("\t$PROGNAME sync -dest <string> -source <string> [OPTIONS]\n");
0bc3e510 1160 }
0bc3e510 1161
eee21241
WL
1162 sub check_target {
1163 my ($target) = @_;
1164 parse_target($target);
1165 }
0bc3e510 1166
d9e8f4ec 1167__END__
0bc3e510 1168
d9e8f4ec 1169=head1 NAME
0bc3e510 1170
d9e8f4ec 1171pve-zsync - PVE ZFS Replication Manager
0bc3e510 1172
d9e8f4ec 1173=head1 SYNOPSIS
0bc3e510 1174
d9e8f4ec 1175pve-zsync <COMMAND> [ARGS] [OPTIONS]
0bc3e510 1176
d9e8f4ec 1177pve-zsync help <cmd> [OPTIONS]
0bc3e510
WL
1178
1179 Get help about specified command.
1180
d9e8f4ec 1181 <cmd> string
0bc3e510 1182
d9e8f4ec 1183 Command name
0bc3e510
WL
1184
1185 -verbose boolean
1186
d9e8f4ec 1187 Verbose output format.
0bc3e510 1188
d9e8f4ec 1189pve-zsync create -dest <string> -source <string> [OPTIONS]
0bc3e510 1190
d9e8f4ec 1191 Create a sync Job
0bc3e510 1192
d9e8f4ec 1193 -dest string
0bc3e510 1194
d9e8f4ec 1195 the destination target is like [IP]:<Pool>[/Path]
0bc3e510 1196
d9e8f4ec 1197 -limit integer
0bc3e510 1198
28006d67 1199 max sync speed in kBytes/s, default unlimited
0bc3e510 1200
d9e8f4ec 1201 -maxsnap string
0bc3e510 1202
d9e8f4ec 1203 how much snapshots will be kept before get erased, default 1
0bc3e510 1204
d9e8f4ec 1205 -name string
0bc3e510 1206
d9e8f4ec 1207 name of the sync job, if not set it is default
0bc3e510 1208
d9e8f4ec 1209 -skip boolean
0bc3e510 1210
d9e8f4ec 1211 if this flag is set it will skip the first sync
0bc3e510 1212
d9e8f4ec 1213 -source string
0bc3e510 1214
d9e8f4ec 1215 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
0bc3e510 1216
c85692fa 1217pve-zsync destroy -source <string> [OPTIONS]
0bc3e510 1218
d9e8f4ec 1219 remove a sync Job from the scheduler
0bc3e510 1220
d9e8f4ec 1221 -name string
0bc3e510
WL
1222
1223 name of the sync job, if not set it is default
1224
d9e8f4ec 1225 -source string
0bc3e510 1226
d9e8f4ec 1227 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
0bc3e510 1228
d9e8f4ec 1229pve-zsync disable -source <string> [OPTIONS]
28006d67 1230
d9e8f4ec 1231 pause a sync job
28006d67 1232
d9e8f4ec 1233 -name string
28006d67 1234
d9e8f4ec 1235 name of the sync job, if not set it is default
28006d67 1236
d9e8f4ec 1237 -source string
28006d67 1238
d9e8f4ec 1239 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
28006d67 1240
c85692fa 1241pve-zsync enable -source <string> [OPTIONS]
28006d67 1242
d9e8f4ec 1243 enable a syncjob and reset error
28006d67 1244
d9e8f4ec 1245 -name string
28006d67
WL
1246
1247 name of the sync job, if not set it is default
1248
d9e8f4ec 1249 -source string
28006d67 1250
d9e8f4ec
WL
1251 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1252pve-zsync list
0bc3e510 1253
d9e8f4ec 1254 Get a List of all scheduled Sync Jobs
0bc3e510 1255
d9e8f4ec 1256pve-zsync status
0bc3e510 1257
d9e8f4ec 1258 Get the status of all scheduled Sync Jobs
0bc3e510 1259
d9e8f4ec 1260pve-zsync sync -dest <string> -source <string> [OPTIONS]
0bc3e510 1261
d9e8f4ec 1262 will sync one time
0bc3e510 1263
d9e8f4ec 1264 -dest string
0bc3e510 1265
d9e8f4ec 1266 the destination target is like [IP:]<Pool>[/Path]
0bc3e510
WL
1267
1268 -limit integer
1269
1270 max sync speed in kBytes/s, default unlimited
1271
d9e8f4ec 1272 -maxsnap integer
0bc3e510 1273
d9e8f4ec 1274 how much snapshots will be kept before get erased, default 1
0bc3e510 1275
d9e8f4ec 1276 -name string
0bc3e510 1277
d9e8f4ec
WL
1278 name of the sync job, if not set it is default.
1279 It is only necessary if scheduler allready contains this source.
0bc3e510 1280
d9e8f4ec 1281 -source string
0bc3e510 1282
d9e8f4ec 1283 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
0bc3e510
WL
1284
1285=head1 DESCRIPTION
1286
1287This Tool helps you to sync your VM or directory which stored on ZFS between 2 servers.
1288This tool also has the capability to add jobs to cron so the sync will be automatically done.
76b2c677 1289The default syncing interval is set to 15 min, if you want to change this value you can do this in /etc/cron.d/pve-zsync.
d9e8f4ec 1290To config cron see man crontab.
0bc3e510 1291
d9e8f4ec 1292=head2 PVE ZFS Storage sync Tool
0bc3e510 1293
d9e8f4ec 1294This Tool can get remote pool on other PVE or send Pool to others ZFS machines
0bc3e510 1295
d9e8f4ec 1296=head1 EXAMPLES
0bc3e510 1297
d9e8f4ec
WL
1298add sync job from local VM to remote ZFS Server
1299pve-zsync create -source=100 -dest=192.168.1.2:zfspool
0bc3e510 1300
d9e8f4ec 1301=head1 IMPORTANT FILES
a018f134 1302
d9e8f4ec 1303Cron jobs and config are stored at /etc/cron.d/pve-zsync
6b4f676d 1304
d9e8f4ec 1305The VM config get copied on the destination machine to /var/lib/pve-zsync/
0bc3e510 1306
d9e8f4ec 1307=head1 COPYRIGHT AND DISCLAIMER
0bc3e510 1308
d9e8f4ec 1309Copyright (C) 2007-2015 Proxmox Server Solutions GmbH
0bc3e510 1310
d9e8f4ec 1311This program is free software: you can redistribute it and/or modify it
0bc3e510
WL
1312under the terms of the GNU Affero General Public License as published
1313by the Free Software Foundation, either version 3 of the License, or
1314(at your option) any later version.
1315
d9e8f4ec
WL
1316This program is distributed in the hope that it will be useful, but
1317WITHOUT ANY WARRANTY; without even the implied warranty of
1318MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1319Affero General Public License for more details.
0bc3e510 1320
d9e8f4ec
WL
1321You should have received a copy of the GNU Affero General Public
1322License along with this program. If not, see
1323<http://www.gnu.org/licenses/>.