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