]> git.proxmox.com Git - pve-zsync.git/blame - pve-zsync
Make return value logical correct
[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 ($@) {
148408c3 117 return 0;
0bc3e510 118 }
148408c3 119 return 1;
0bc3e510
WL
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}";
48186847 407 $text .= " --limit $job->{limit}" if $job->{limit};
76b2c677
WL
408 $text .= " --method $job->{method}";
409 $text .= " --verbose" if $job->{verbose};
410 $text .= "\n";
411
412 return $text;
413}
0bc3e510 414
76b2c677 415sub list {
d3e1a943 416
76b2c677 417 my $cfg = read_cron();
28006d67 418
3daaef60 419 my $list = sprintf("%-25s%-25s%-10s%-20s%-6s%-5s\n" , "SOURCE", "NAME", "STATE", "LAST SYNC", "TYPE", "CON");
28006d67 420
76b2c677
WL
421 my $states = read_state();
422 foreach my $source (sort keys%{$cfg}) {
423 foreach my $name (sort keys%{$cfg->{$source}}) {
424 $list .= sprintf("%-25s", cut_target_width($source, 25));
3daaef60
WL
425 $list .= sprintf("%-25s", cut_target_width($name, 25));
426 $list .= sprintf("%-10s", $states->{$source}->{$name}->{state});
eee21241 427 $list .= sprintf("%-20s", $states->{$source}->{$name}->{lsync});
947c04dc 428 $list .= sprintf("%-6s", defined($states->{$source}->{$name}->{vm_type}) ? $states->{$source}->{$name}->{vm_type} : "undef");
eee21241 429 $list .= sprintf("%-5s\n", $cfg->{$source}->{$name}->{method});
0bc3e510
WL
430 }
431 }
432
433 return $list;
434}
435
436sub vm_exists {
437 my ($target) = @_;
eee21241 438
8fc69e3c 439 my @cmd = ('ssh', "root\@$target->{ip}", '--') if $target->{ip};
0bc3e510 440
8fc69e3c
WL
441 my $res = undef;
442
947c04dc
FG
443 return undef if !defined($target->{vmid});
444
d9e8f4ec 445 eval { $res = run_cmd([@cmd, 'ls', "$QEMU_CONF/$target->{vmid}.conf"]) };
8fc69e3c
WL
446
447 return "qemu" if $res;
0bc3e510 448
d9e8f4ec 449 eval { $res = run_cmd([@cmd, 'ls', "$LXC_CONF/$target->{vmid}.conf"]) };
8fc69e3c
WL
450
451 return "lxc" if $res;
0bc3e510 452
0bc3e510
WL
453 return undef;
454}
455
456sub init {
457 my ($param) = @_;
458
76b2c677 459 my $cfg = read_cron();
0bc3e510 460
76b2c677 461 my $job = param_to_job($param);
0bc3e510 462
76b2c677
WL
463 $job->{state} = "ok";
464 $job->{lsync} = 0;
0bc3e510
WL
465
466 my $source = parse_target($param->{source});
467 my $dest = parse_target($param->{dest});
468
76b2c677 469 if (my $ip = $dest->{ip}) {
271c2572 470 run_cmd(['ssh-copy-id', '-i', '/root/.ssh/id_rsa.pub', "root\@$ip"]);
0bc3e510
WL
471 }
472
c85692fa 473 if (my $ip = $source->{ip}) {
271c2572 474 run_cmd(['ssh-copy-id', '-i', '/root/.ssh/id_rsa.pub', "root\@$ip"]);
0bc3e510
WL
475 }
476
78d36df7 477 die "Pool $dest->{all} does not exists\n" if check_pool_exists($dest);
0bc3e510 478
78d36df7 479 my $check = check_pool_exists($source->{path}, $source->{ip}) if !$source->{vmid} && $source->{path};
0bc3e510 480
76b2c677 481 die "Pool $source->{path} does not exists\n" if undef($check);
8362418a 482
8fc69e3c 483 my $vm_type = vm_exists($source);
67badfe4 484 $job->{vm_type} = $vm_type;
3db33e1f 485 $source->{vm_type} = $vm_type;
8fc69e3c 486
6cc5bf8e 487 die "VM $source->{vmid} doesn't exist\n" if $source->{vmid} && !$vm_type;
0bc3e510 488
76b2c677 489 die "Config already exists\n" if $cfg->{$job->{source}}->{$job->{name}};
0bc3e510 490
3db33e1f
WL
491 #check if vm has zfs disks if not die;
492 get_disks($source, 1) if $source->{vmid};
493
76b2c677
WL
494 update_cron($job);
495 update_state($job);
c85692fa
WL
496
497 eval {
498 sync($param) if !$param->{skip};
499 };
1a7871e7 500 if(my $err = $@) {
c85692fa 501 destroy_job($param);
1a7871e7
WL
502 print $err;
503 }
0bc3e510
WL
504}
505
76b2c677
WL
506sub get_job {
507 my ($param) = @_;
508
509 my $cfg = read_cron();
510
511 if (!$cfg->{$param->{source}}->{$param->{name}}) {
512 die "Job with source $param->{source} and name $param->{name} does not exist\n" ;
513 }
514 my $job = $cfg->{$param->{source}}->{$param->{name}};
515 $job->{name} = $param->{name};
516 $job->{source} = $param->{source};
517 $job = add_state_to_job($job);
518
519 return $job;
520}
521
c85692fa 522sub destroy_job {
0bc3e510
WL
523 my ($param) = @_;
524
76b2c677
WL
525 my $job = get_job($param);
526 $job->{state} = "del";
b0842810 527
76b2c677
WL
528 update_cron($job);
529 update_state($job);
0bc3e510
WL
530}
531
532sub sync {
533 my ($param) = @_;
76b2c677
WL
534
535 my $lock_fh = IO::File->new("> $LOCKFILE");
536 die "Can't open Lock File: $LOCKFILE $!\n" if !$lock_fh;
b0842810 537 lock($lock_fh);
0bc3e510 538
76b2c677
WL
539 my $date = get_date();
540 my $job;
541 eval {
542 $job = get_job($param);
543 };
0bc3e510 544
a018f134
WL
545 if ($job && $job->{state} eq "syncing") {
546 die "Job --source $param->{source} --name $param->{name} is syncing at the moment";
76b2c677 547 }
0bc3e510
WL
548
549 my $dest = parse_target($param->{dest});
550 my $source = parse_target($param->{source});
551
552 my $sync_path = sub {
76b2c677 553 my ($source, $dest, $job, $param, $date) = @_;
0bc3e510 554
eee21241 555 ($source->{old_snap}, $source->{last_snap}) = snapshot_get($source, $dest, $param->{maxsnap}, $param->{name});
6b4f676d 556
a018f134 557 snapshot_add($source, $dest, $param->{name}, $date);
0bc3e510 558
a018f134
WL
559 send_image($source, $dest, $param);
560
561 snapshot_destroy($source, $dest, $param->{method}, $source->{old_snap}) if ($source->{destroy} && $source->{old_snap});
0bc3e510 562
0bc3e510
WL
563 };
564
8fc69e3c
WL
565 my $vm_type = vm_exists($source);
566 $source->{vm_type} = $vm_type;
567
76b2c677 568 if ($job) {
76b2c677 569 $job->{state} = "syncing";
8fc69e3c 570 $job->{vm_type} = $vm_type if !$job->{vm_type};
76b2c677
WL
571 update_state($job);
572 }
0bc3e510 573
a018f134
WL
574 eval{
575 if ($source->{vmid}) {
8fc69e3c 576 die "VM $source->{vmid} doesn't exist\n" if !$vm_type;
a018f134
WL
577 my $disks = get_disks($source);
578
579 foreach my $disk (sort keys %{$disks}) {
580 $source->{all} = $disks->{$disk}->{all};
581 $source->{pool} = $disks->{$disk}->{pool};
582 $source->{path} = $disks->{$disk}->{path} if $disks->{$disk}->{path};
583 $source->{last_part} = $disks->{$disk}->{last_part};
584 &$sync_path($source, $dest, $job, $param, $date);
585 }
c9cbba3d 586 if ($param->{method} eq "ssh" && ($source->{ip} || $dest->{ip})) {
a018f134 587 send_config($source, $dest,'ssh');
c9cbba3d
WL
588 } else {
589 send_config($source, $dest,'local');
a018f134
WL
590 }
591 } else {
76b2c677 592 &$sync_path($source, $dest, $job, $param, $date);
0bc3e510 593 }
a018f134
WL
594 };
595 if(my $err = $@) {
596 if ($job) {
597 $job->{state} = "error";
598 update_state($job);
599 unlock($lock_fh);
600 close($lock_fh);
601 print "Job --source $param->{source} --name $param->{name} got an ERROR!!!\nERROR Message:\n";
7d93705f 602 }
a018f134 603 die "$err\n";
76b2c677
WL
604 }
605
606 if ($job) {
607 $job->{state} = "ok";
608 $job->{lsync} = $date;
609 update_state($job);
0bc3e510 610 }
76b2c677 611
c85692fa 612 unlock($lock_fh);
b0842810 613 close($lock_fh);
0bc3e510
WL
614}
615
616sub snapshot_get{
617 my ($source, $dest, $max_snap, $name) = @_;
618
271c2572
WB
619 my $cmd = [];
620 push @$cmd, 'ssh', "root\@$source->{ip}", '--', if $source->{ip};
621 push @$cmd, 'zfs', 'list', '-r', '-t', 'snapshot', '-Ho', 'name', '-S', 'creation';
622 push @$cmd, $source->{all};
0bc3e510
WL
623
624 my $raw = run_cmd($cmd);
76b2c677 625 my $index = 0;
0bc3e510
WL
626 my $line = "";
627 my $last_snap = undef;
76b2c677 628 my $old_snap;
0bc3e510
WL
629
630 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
631 $line = $1;
76b2c677 632 if ($line =~ m/(rep_$name.*)$/) {
d9e8f4ec 633
76b2c677
WL
634 $last_snap = $1 if (!$last_snap);
635 $old_snap = $1;
636 $index++;
637 if ($index == $max_snap) {
638 $source->{destroy} = 1;
639 last;
640 };
641 }
0bc3e510
WL
642 }
643
76b2c677 644 return ($old_snap, $last_snap) if $last_snap;
0bc3e510
WL
645
646 return undef;
647}
648
649sub snapshot_add {
76b2c677 650 my ($source, $dest, $name, $date) = @_;
0bc3e510
WL
651
652 my $snap_name = "rep_$name\_".$date;
653
654 $source->{new_snap} = $snap_name;
655
76b2c677 656 my $path = "$source->{all}\@$snap_name";
0bc3e510 657
271c2572
WB
658 my $cmd = [];
659 push @$cmd, 'ssh', "root\@$source->{ip}", '--', if $source->{ip};
660 push @$cmd, 'zfs', 'snapshot', $path;
0bc3e510
WL
661 eval{
662 run_cmd($cmd);
663 };
664
c85692fa 665 if (my $err = $@) {
0bc3e510
WL
666 snapshot_destroy($source, $dest, 'ssh', $snap_name);
667 die "$err\n";
668 }
0bc3e510
WL
669}
670
c85692fa 671sub write_cron {
28006d67 672 my ($cfg) = @_;
0bc3e510 673
c85692fa
WL
674 my $text = "SHELL=/bin/sh\n";
675 $text .= "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n";
2e8fd72a 676
76b2c677
WL
677 my $fh = IO::File->new("> $CRONJOBS");
678 die "Could not open file: $!\n" if !$fh;
0bc3e510 679
c85692fa
WL
680 foreach my $source (sort keys%{$cfg}) {
681 foreach my $sync_name (sort keys%{$cfg->{$source}}) {
28006d67 682 next if $cfg->{$source}->{$sync_name}->{status} ne 'ok';
eee21241
WL
683 $text .= "$PROG_PATH sync";
684 $text .= " -source ";
685 if ($cfg->{$source}->{$sync_name}->{vmid}) {
686 $text .= "$cfg->{$source}->{$sync_name}->{source_ip}:" if $cfg->{$source}->{$sync_name}->{source_ip};
687 $text .= "$cfg->{$source}->{$sync_name}->{vmid} ";
688 } else {
689 $text .= "$cfg->{$source}->{$sync_name}->{source_ip}:" if $cfg->{$source}->{$sync_name}->{source_ip};
690 $text .= "$cfg->{$source}->{$sync_name}->{source_pool}";
691 $text .= "$cfg->{$source}->{$sync_name}->{source_path}" if $cfg->{$source}->{$sync_name}->{source_path};
692 }
693 $text .= " -dest ";
694 $text .= "$cfg->{$source}->{$sync_name}->{dest_ip}:" if $cfg->{$source}->{$sync_name}->{dest_ip};
695 $text .= "$cfg->{$source}->{$sync_name}->{dest_pool}";
696 $text .= "$cfg->{$source}->{$sync_name}->{dest_path}" if $cfg->{$source}->{$sync_name}->{dest_path};
697 $text .= " -name $sync_name ";
698 $text .= " -limit $cfg->{$source}->{$sync_name}->{limit}" if $cfg->{$source}->{$sync_name}->{limit};
699 $text .= " -maxsnap $cfg->{$source}->{$sync_name}->{maxsnap}" if $cfg->{$source}->{$sync_name}->{maxsnap};
700 $text .= "\n";
0bc3e510
WL
701 }
702 }
c85692fa 703 die "Can't write to cron\n" if (!print($fh $text));
0bc3e510
WL
704 close($fh);
705}
706
707sub get_disks {
3db33e1f 708 my ($target, $get_err) = @_;
0bc3e510 709
271c2572
WB
710 my $cmd = [];
711 push @$cmd, 'ssh', "root\@$target->{ip}", '--', if $target->{ip};
9e7685c2
WL
712
713 if ($target->{vm_type} eq 'qemu') {
714 push @$cmd, 'qm', 'config', $target->{vmid};
715 } elsif ($target->{vm_type} eq 'lxc') {
716 push @$cmd, 'pct', 'config', $target->{vmid};
717 } else {
718 die "VM Type unknown\n";
719 }
0bc3e510
WL
720
721 my $res = run_cmd($cmd);
722
3db33e1f 723 my $disks = parse_disks($res, $target->{ip}, $target->{vm_type}, $get_err);
0bc3e510
WL
724
725 return $disks;
726}
727
728sub run_cmd {
729 my ($cmd) = @_;
730 print "Start CMD\n" if $DEBUG;
731 print Dumper $cmd if $DEBUG;
dc54fce8
WB
732 if (ref($cmd) eq 'ARRAY') {
733 $cmd = join(' ', map { ref($_) ? $$_ : shell_quote($_) } @$cmd);
734 }
0bc3e510
WL
735 my $output = `$cmd 2>&1`;
736
a018f134 737 die "COMMAND:\n\t$cmd\nGET ERROR:\n\t$output" if 0 != $?;
0bc3e510
WL
738
739 chomp($output);
740 print Dumper $output if $DEBUG;
741 print "END CMD\n" if $DEBUG;
742 return $output;
743}
744
745sub parse_disks {
3db33e1f 746 my ($text, $ip, $vm_type, $get_err) = @_;
0bc3e510
WL
747
748 my $disks;
749
750 my $num = 0;
0bc3e510
WL
751 while ($text && $text =~ s/^(.*?)(\n|$)//) {
752 my $line = $1;
3db33e1f 753 my $error = $vm_type eq 'qemu' ? 1 : 0 ;
78cd57dd
WB
754
755 next if $line =~ /cdrom|none/;
9e7685c2 756 next if $line !~ m/^(?:((?:virtio|ide|scsi|sata|mp)\d+)|rootfs): /;
78cd57dd 757
3db33e1f
WL
758 #QEMU if backup is not set include in sync
759 next if $vm_type eq 'qemu && ($line =~ m/backup=(?i:0|no|off|false)/)';
760
761 #LXC if backup is not set do no in sync
762 $error = ($line =~ m/backup=(?i:1|yes|on|true)/) if $vm_type eq 'lxc';
763
0bc3e510
WL
764 my $disk = undef;
765 my $stor = undef;
e5f52a63
WL
766 if($line =~ m/^(?:(?:(?:virtio|ide|scsi|sata|mp)\d+)|rootfs): (.*)$/) {
767 my @parameter = split(/,/,$1);
768
769 foreach my $opt (@parameter) {
770 if ($opt =~ m/^(?:file=|volume=)?([^:]+:)([A-Za-z0-9\-]+)$/){
771 $disk = $2;
772 $stor = $1;
773 last;
774 }
775 }
776
845e36d7 777 } else {
3db33e1f
WL
778 print "Disk: \"$line\" will not include in pve-sync\n" if $get_err || $error;
779 next;
0bc3e510
WL
780 }
781
271c2572
WB
782 my $cmd = [];
783 push @$cmd, 'ssh', "root\@$ip", '--' if $ip;
784 push @$cmd, 'pvesm', 'path', "$stor$disk";
b52d13b3
WB
785 my $path = run_cmd($cmd);
786
9303e6fc
WL
787 die "Get no path from pvesm path $stor$disk\n" if !$path;
788
9e7685c2 789 if ($vm_type eq 'qemu' && $path =~ m/^\/dev\/zvol\/(\w+.*)(\/$disk)$/) {
b52d13b3
WB
790
791 my @array = split('/', $1);
3df8b97d 792 $disks->{$num}->{pool} = shift(@array);
b52d13b3
WB
793 $disks->{$num}->{all} = $disks->{$num}->{pool};
794 if (0 < @array) {
795 $disks->{$num}->{path} = join('/', @array);
796 $disks->{$num}->{all} .= "\/$disks->{$num}->{path}";
0bc3e510 797 }
b52d13b3
WB
798 $disks->{$num}->{last_part} = $disk;
799 $disks->{$num}->{all} .= "\/$disk";
800
9e7685c2 801 $num++;
aec521ca 802 } elsif ($vm_type eq 'lxc' && $path =~ m/^\/(\w+.+)(\/(\w+.*))*(\/$disk)$/) {
9e7685c2
WL
803
804 $disks->{$num}->{pool} = $1;
805 $disks->{$num}->{all} = $disks->{$num}->{pool};
806
807 if ($2) {
aec521ca 808 $disks->{$num}->{path} = $3;
9e7685c2
WL
809 $disks->{$num}->{all} .= "\/$disks->{$num}->{path}";
810 }
811
812 $disks->{$num}->{last_part} = $disk;
813 $disks->{$num}->{all} .= "\/$disk";
814
b52d13b3
WB
815 $num++;
816
817 } else {
818 die "ERROR: in path\n";
0bc3e510
WL
819 }
820 }
76b2c677 821
3db33e1f 822 die "Vm include no disk on zfs.\n" if !$disks->{0};
0bc3e510
WL
823 return $disks;
824}
825
826sub snapshot_destroy {
827 my ($source, $dest, $method, $snap) = @_;
828
271c2572 829 my @zfscmd = ('zfs', 'destroy');
76b2c677 830 my $snapshot = "$source->{all}\@$snap";
0bc3e510
WL
831
832 eval {
833 if($source->{ip} && $method eq 'ssh'){
271c2572 834 run_cmd(['ssh', "root\@$source->{ip}", '--', @zfscmd, $snapshot]);
0bc3e510 835 } else {
271c2572 836 run_cmd([@zfscmd, $snapshot]);
0bc3e510
WL
837 }
838 };
839 if (my $erro = $@) {
840 warn "WARN: $erro";
841 }
c85692fa 842 if ($dest) {
271c2572 843 my @ssh = $dest->{ip} ? ('ssh', "root\@$dest->{ip}", '--') : ();
0bc3e510 844
76b2c677 845 my $path = "$dest->{all}\/$source->{last_part}";
0bc3e510 846
0bc3e510 847 eval {
271c2572 848 run_cmd([@ssh, @zfscmd, "$path\@$snap"]);
0bc3e510
WL
849 };
850 if (my $erro = $@) {
851 warn "WARN: $erro";
852 }
853 }
854}
855
856sub snapshot_exist {
eee21241 857 my ($source , $dest, $method) = @_;
0bc3e510 858
271c2572
WB
859 my $cmd = [];
860 push @$cmd, 'ssh', "root\@$dest->{ip}", '--' if $dest->{ip};
861 push @$cmd, 'zfs', 'list', '-rt', 'snapshot', '-Ho', 'name';
862 push @$cmd, "$dest->{all}/$source->{last_part}\@$source->{old_snap}";
0bc3e510
WL
863
864 my $text = "";
865 eval {$text =run_cmd($cmd);};
eee21241 866 if (my $erro =$@) {
0bc3e510
WL
867 warn "WARN: $erro";
868 return undef;
869 }
870
871 while ($text && $text =~ s/^(.*?)(\n|$)//) {
eee21241 872 my $line =$1;
0bc3e510
WL
873 return 1 if $line =~ m/^.*$source->{old_snap}$/;
874 }
875}
876
877sub send_image {
76b2c677 878 my ($source, $dest, $param) = @_;
0bc3e510 879
271c2572 880 my $cmd = [];
0bc3e510 881
271c2572
WB
882 push @$cmd, 'ssh', "root\@$source->{ip}", '--' if $source->{ip};
883 push @$cmd, 'zfs', 'send';
884 push @$cmd, '-v' if $param->{verbose};
0bc3e510 885
eee21241 886 if($source->{last_snap} && snapshot_exist($source , $dest, $param->{method})) {
271c2572 887 push @$cmd, '-i', "$source->{all}\@$source->{last_snap}";
0bc3e510 888 }
271c2572 889 push @$cmd, '--', "$source->{all}\@$source->{new_snap}";
0bc3e510 890
76b2c677
WL
891 if ($param->{limit}){
892 my $bwl = $param->{limit}*1024;
271c2572 893 push @$cmd, \'|', 'cstream', '-t', $bwl;
0bc3e510 894 }
1193273e
WL
895 my $target = "$dest->{all}/$source->{last_part}";
896 $target =~ s!/+!/!g;
897
271c2572 898 push @$cmd, \'|';
eee21241
WL
899 push @$cmd, 'ssh', "root\@$dest->{ip}", '--' if $dest->{ip};
900 push @$cmd, 'zfs', 'recv', '-F', '--';
901 push @$cmd, "$target";
0bc3e510 902
eee21241
WL
903 eval {
904 run_cmd($cmd)
905 };
0bc3e510 906
eee21241
WL
907 if (my $erro = $@) {
908 snapshot_destroy($source, undef, $param->{method}, $source->{new_snap});
909 die $erro;
910 };
911 }
0bc3e510
WL
912
913
eee21241
WL
914 sub send_config{
915 my ($source, $dest, $method) = @_;
0bc3e510 916
d9e8f4ec 917 my $source_target = $source->{vm_type} eq 'qemu' ? "$QEMU_CONF/$source->{vmid}.conf": "$LXC_CONF/$source->{vmid}.conf";
eee21241 918 my $dest_target_new ="$source->{vmid}.conf.$source->{vm_type}.$source->{new_snap}";
76b2c677 919
d9e8f4ec 920 my $config_dir = $dest->{last_part} ? "${CONFIG_PATH}/$dest->{last_part}" : $CONFIG_PATH;
739c195a 921
d9e8f4ec 922 $dest_target_new = $config_dir.'/'.$dest_target_new;
739c195a 923
eee21241
WL
924 if ($method eq 'ssh'){
925 if ($dest->{ip} && $source->{ip}) {
926 run_cmd(['ssh', "root\@$dest->{ip}", '--', 'mkdir', '-p', '--', $config_dir]);
927 run_cmd(['scp', '--', "root\@[$source->{ip}]:$source_target", "root\@[$dest->{ip}]:$dest_target_new"]);
928 } elsif ($dest->{ip}) {
929 run_cmd(['ssh', "root\@$dest->{ip}", '--', 'mkdir', '-p', '--', $config_dir]);
930 run_cmd(['scp', '--', $source_target, "root\@[$dest->{ip}]:$dest_target_new"]);
931 } elsif ($source->{ip}) {
932 run_cmd(['mkdir', '-p', '--', $config_dir]);
933 run_cmd(['scp', '--', "root\@$source->{ip}:$source_target", $dest_target_new]);
934 }
0bc3e510 935
eee21241 936 if ($source->{destroy}){
d9e8f4ec 937 my $dest_target_old ="${config_dir}/$source->{vmid}.conf.$source->{vm_type}.$source->{old_snap}";
eee21241
WL
938 if($dest->{ip}){
939 run_cmd(['ssh', "root\@$dest->{ip}", '--', 'rm', '-f', '--', $dest_target_old]);
940 } else {
941 run_cmd(['rm', '-f', '--', $dest_target_old]);
942 }
0bc3e510 943 }
eee21241
WL
944 } elsif ($method eq 'local') {
945 run_cmd(['mkdir', '-p', '--', $config_dir]);
946 run_cmd(['cp', $source_target, $dest_target_new]);
0bc3e510
WL
947 }
948 }
0bc3e510 949
eee21241
WL
950 sub get_date {
951 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
952 my $datestamp = sprintf ("%04d-%02d-%02d_%02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
0bc3e510 953
eee21241
WL
954 return $datestamp;
955 }
0bc3e510 956
eee21241
WL
957 sub status {
958 my $cfg = read_cron();
0bc3e510 959
3daaef60 960 my $status_list = sprintf("%-25s%-25s%-10s\n", "SOURCE", "NAME", "STATUS");
0bc3e510 961
eee21241 962 my $states = read_state();
76b2c677 963
eee21241
WL
964 foreach my $source (sort keys%{$cfg}) {
965 foreach my $sync_name (sort keys%{$cfg->{$source}}) {
966 $status_list .= sprintf("%-25s", cut_target_width($source, 25));
3daaef60 967 $status_list .= sprintf("%-25s", cut_target_width($sync_name, 25));
eee21241
WL
968 $status_list .= "$states->{$source}->{$sync_name}->{state}\n";
969 }
0bc3e510 970 }
0bc3e510 971
eee21241
WL
972 return $status_list;
973 }
0bc3e510 974
eee21241
WL
975 sub enable_job {
976 my ($param) = @_;
28006d67 977
eee21241
WL
978 my $job = get_job($param);
979 $job->{state} = "ok";
980 update_state($job);
981 update_cron($job);
982 }
28006d67 983
eee21241
WL
984 sub disable_job {
985 my ($param) = @_;
28006d67 986
eee21241
WL
987 my $job = get_job($param);
988 $job->{state} = "stopped";
989 update_state($job);
990 update_cron($job);
991 }
28006d67 992
eee21241 993 my $command = $ARGV[0];
0bc3e510 994
eee21241
WL
995 my $commands = {'destroy' => 1,
996 'create' => 1,
997 'sync' => 1,
998 'list' => 1,
999 'status' => 1,
1000 'help' => 1,
1001 'enable' => 1,
1002 'disable' => 1};
0bc3e510 1003
eee21241
WL
1004 if (!$command || !$commands->{$command}) {
1005 usage();
1006 die "\n";
1007 }
0bc3e510 1008
eee21241 1009 my $help_sync = "$PROGNAME sync -dest <string> -source <string> [OPTIONS]\n
0bc3e510
WL
1010\twill sync one time\n
1011\t-dest\tstring\n
1012\t\tthe destination target is like [IP:]<Pool>[/Path]\n
1013\t-limit\tinteger\n
1014\t\tmax sync speed in kBytes/s, default unlimited\n
1015\t-maxsnap\tinteger\n
1016\t\thow much snapshots will be kept before get erased, default 1/n
1017\t-name\tstring\n
1018\t\tname of the sync job, if not set it is default.
1019\tIt is only necessary if scheduler allready contains this source.\n
1020\t-source\tstring\n
1021\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
1022
eee21241 1023 my $help_create = "$PROGNAME create -dest <string> -source <string> [OPTIONS]/n
0bc3e510 1024\tCreate a sync Job\n
d9e8f4ec 1025\t-dest\tstring\n
0bc3e510 1026\t\tthe destination target is like [IP]:<Pool>[/Path]\n
0bc3e510 1027\t-limit\tinteger\n
28006d67 1028\t\tmax sync speed in kBytes/s, default unlimited\n
0bc3e510
WL
1029\t-maxsnap\tstring\n
1030\t\thow much snapshots will be kept before get erased, default 1\n
1031\t-name\tstring\n
1032\t\tname of the sync job, if not set it is default\n
1033\t-skip\tboolean\n
1034\t\tif this flag is set it will skip the first sync\n
1035\t-source\tstring\n
1036\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
1037
eee21241 1038 my $help_destroy = "$PROGNAME destroy -source <string> [OPTIONS]\n
0bc3e510
WL
1039\tremove a sync Job from the scheduler\n
1040\t-name\tstring\n
1041\t\tname of the sync job, if not set it is default\n
1042\t-source\tstring\n
1043\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
1044
eee21241 1045 my $help_help = "$PROGNAME help <cmd> [OPTIONS]\n
0bc3e510
WL
1046\tGet help about specified command.\n
1047\t<cmd>\tstring\n
1048\t\tCommand name\n
1049\t-verbose\tboolean\n
1050\t\tVerbose output format.\n";
1051
eee21241 1052 my $help_list = "$PROGNAME list\n
0bc3e510
WL
1053\tGet a List of all scheduled Sync Jobs\n";
1054
eee21241 1055 my $help_status = "$PROGNAME status\n
0bc3e510
WL
1056\tGet the status of all scheduled Sync Jobs\n";
1057
eee21241 1058 my $help_enable = "$PROGNAME enable -source <string> [OPTIONS]\n
28006d67
WL
1059\tenable a syncjob and reset error\n
1060\t-name\tstring\n
1061\t\tname of the sync job, if not set it is default\n
1062\t-source\tstring\n
1063\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
1064
eee21241 1065 my $help_disable = "$PROGNAME disable -source <string> [OPTIONS]\n
28006d67
WL
1066\tpause a syncjob\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
WL
1072 sub help {
1073 my ($command) = @_;
0bc3e510 1074
eee21241
WL
1075 switch($command){
1076 case 'help'
1077 {
1078 die "$help_help\n";
1079 }
1080 case 'sync'
1081 {
1082 die "$help_sync\n";
1083 }
1084 case 'destroy'
1085 {
1086 die "$help_destroy\n";
1087 }
1088 case 'create'
1089 {
1090 die "$help_create\n";
1091 }
1092 case 'list'
1093 {
1094 die "$help_list\n";
1095 }
1096 case 'status'
1097 {
1098 die "$help_status\n";
1099 }
1100 case 'enable'
1101 {
1102 die "$help_enable\n";
1103 }
1104 case 'disable'
1105 {
1106 die "$help_enable\n";
1107 }
1108 }
1109
1110 }
1111
1112 my @arg = @ARGV;
1113 my $param = parse_argv(@arg);
1114
1115
1116 switch($command) {
1117 case "destroy"
0bc3e510 1118 {
eee21241
WL
1119 die "$help_destroy\n" if !$param->{source};
1120 check_target($param->{source});
1121 destroy_job($param);
0bc3e510 1122 }
eee21241 1123 case "sync"
0bc3e510 1124 {
eee21241
WL
1125 die "$help_sync\n" if !$param->{source} || !$param->{dest};
1126 check_target($param->{source});
1127 check_target($param->{dest});
1128 sync($param);
0bc3e510 1129 }
eee21241 1130 case "create"
0bc3e510 1131 {
eee21241
WL
1132 die "$help_create\n" if !$param->{source} || !$param->{dest};
1133 check_target($param->{source});
1134 check_target($param->{dest});
1135 init($param);
0bc3e510 1136 }
eee21241 1137 case "status"
0bc3e510 1138 {
eee21241 1139 print status();
0bc3e510 1140 }
eee21241 1141 case "list"
0bc3e510 1142 {
eee21241 1143 print list();
0bc3e510 1144 }
eee21241 1145 case "help"
0bc3e510 1146 {
eee21241
WL
1147 my $help_command = $ARGV[1];
1148 if ($help_command && $commands->{$help_command}) {
1149 print help($help_command);
1150 }
1151 if ($param->{verbose} == 1){
1152 exec("man $PROGNAME");
1153 } else {
1154 usage(1);
1155 }
0bc3e510 1156 }
eee21241 1157 case "enable"
28006d67 1158 {
eee21241
WL
1159 die "$help_enable\n" if !$param->{source};
1160 check_target($param->{source});
1161 enable_job($param);
28006d67 1162 }
eee21241 1163 case "disable"
28006d67 1164 {
eee21241
WL
1165 die "$help_disable\n" if !$param->{source};
1166 check_target($param->{source});
1167 disable_job($param);
28006d67 1168 }
0bc3e510
WL
1169 }
1170
eee21241
WL
1171 sub usage {
1172 my ($help) = @_;
1173
1174 print("ERROR:\tno command specified\n") if !$help;
1175 print("USAGE:\t$PROGNAME <COMMAND> [ARGS] [OPTIONS]\n");
1176 print("\t$PROGNAME help [<cmd>] [OPTIONS]\n\n");
1177 print("\t$PROGNAME create -dest <string> -source <string> [OPTIONS]\n");
1178 print("\t$PROGNAME destroy -source <string> [OPTIONS]\n");
1179 print("\t$PROGNAME disable -source <string> [OPTIONS]\n");
1180 print("\t$PROGNAME enable -source <string> [OPTIONS]\n");
1181 print("\t$PROGNAME list\n");
1182 print("\t$PROGNAME status\n");
1183 print("\t$PROGNAME sync -dest <string> -source <string> [OPTIONS]\n");
0bc3e510 1184 }
0bc3e510 1185
eee21241
WL
1186 sub check_target {
1187 my ($target) = @_;
1188 parse_target($target);
1189 }
0bc3e510 1190
d9e8f4ec 1191__END__
0bc3e510 1192
d9e8f4ec 1193=head1 NAME
0bc3e510 1194
d9e8f4ec 1195pve-zsync - PVE ZFS Replication Manager
0bc3e510 1196
d9e8f4ec 1197=head1 SYNOPSIS
0bc3e510 1198
d9e8f4ec 1199pve-zsync <COMMAND> [ARGS] [OPTIONS]
0bc3e510 1200
d9e8f4ec 1201pve-zsync help <cmd> [OPTIONS]
0bc3e510
WL
1202
1203 Get help about specified command.
1204
d9e8f4ec 1205 <cmd> string
0bc3e510 1206
d9e8f4ec 1207 Command name
0bc3e510
WL
1208
1209 -verbose boolean
1210
d9e8f4ec 1211 Verbose output format.
0bc3e510 1212
d9e8f4ec 1213pve-zsync create -dest <string> -source <string> [OPTIONS]
0bc3e510 1214
d9e8f4ec 1215 Create a sync Job
0bc3e510 1216
d9e8f4ec 1217 -dest string
0bc3e510 1218
d9e8f4ec 1219 the destination target is like [IP]:<Pool>[/Path]
0bc3e510 1220
d9e8f4ec 1221 -limit integer
0bc3e510 1222
28006d67 1223 max sync speed in kBytes/s, default unlimited
0bc3e510 1224
d9e8f4ec 1225 -maxsnap string
0bc3e510 1226
d9e8f4ec 1227 how much snapshots will be kept before get erased, default 1
0bc3e510 1228
d9e8f4ec 1229 -name string
0bc3e510 1230
d9e8f4ec 1231 name of the sync job, if not set it is default
0bc3e510 1232
d9e8f4ec 1233 -skip boolean
0bc3e510 1234
d9e8f4ec 1235 if this flag is set it will skip the first sync
0bc3e510 1236
d9e8f4ec 1237 -source string
0bc3e510 1238
d9e8f4ec 1239 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
0bc3e510 1240
c85692fa 1241pve-zsync destroy -source <string> [OPTIONS]
0bc3e510 1242
d9e8f4ec 1243 remove a sync Job from the scheduler
0bc3e510 1244
d9e8f4ec 1245 -name string
0bc3e510
WL
1246
1247 name of the sync job, if not set it is default
1248
d9e8f4ec 1249 -source string
0bc3e510 1250
d9e8f4ec 1251 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
0bc3e510 1252
d9e8f4ec 1253pve-zsync disable -source <string> [OPTIONS]
28006d67 1254
d9e8f4ec 1255 pause a sync job
28006d67 1256
d9e8f4ec 1257 -name string
28006d67 1258
d9e8f4ec 1259 name of the sync job, if not set it is default
28006d67 1260
d9e8f4ec 1261 -source string
28006d67 1262
d9e8f4ec 1263 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
28006d67 1264
c85692fa 1265pve-zsync enable -source <string> [OPTIONS]
28006d67 1266
d9e8f4ec 1267 enable a syncjob and reset error
28006d67 1268
d9e8f4ec 1269 -name string
28006d67
WL
1270
1271 name of the sync job, if not set it is default
1272
d9e8f4ec 1273 -source string
28006d67 1274
d9e8f4ec
WL
1275 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1276pve-zsync list
0bc3e510 1277
d9e8f4ec 1278 Get a List of all scheduled Sync Jobs
0bc3e510 1279
d9e8f4ec 1280pve-zsync status
0bc3e510 1281
d9e8f4ec 1282 Get the status of all scheduled Sync Jobs
0bc3e510 1283
d9e8f4ec 1284pve-zsync sync -dest <string> -source <string> [OPTIONS]
0bc3e510 1285
d9e8f4ec 1286 will sync one time
0bc3e510 1287
d9e8f4ec 1288 -dest string
0bc3e510 1289
d9e8f4ec 1290 the destination target is like [IP:]<Pool>[/Path]
0bc3e510
WL
1291
1292 -limit integer
1293
1294 max sync speed in kBytes/s, default unlimited
1295
d9e8f4ec 1296 -maxsnap integer
0bc3e510 1297
d9e8f4ec 1298 how much snapshots will be kept before get erased, default 1
0bc3e510 1299
d9e8f4ec 1300 -name string
0bc3e510 1301
d9e8f4ec
WL
1302 name of the sync job, if not set it is default.
1303 It is only necessary if scheduler allready contains this source.
0bc3e510 1304
d9e8f4ec 1305 -source string
0bc3e510 1306
d9e8f4ec 1307 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
0bc3e510
WL
1308
1309=head1 DESCRIPTION
1310
1311This Tool helps you to sync your VM or directory which stored on ZFS between 2 servers.
1312This tool also has the capability to add jobs to cron so the sync will be automatically done.
76b2c677 1313The 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 1314To config cron see man crontab.
0bc3e510 1315
d9e8f4ec 1316=head2 PVE ZFS Storage sync Tool
0bc3e510 1317
d9e8f4ec 1318This Tool can get remote pool on other PVE or send Pool to others ZFS machines
0bc3e510 1319
d9e8f4ec 1320=head1 EXAMPLES
0bc3e510 1321
d9e8f4ec
WL
1322add sync job from local VM to remote ZFS Server
1323pve-zsync create -source=100 -dest=192.168.1.2:zfspool
0bc3e510 1324
d9e8f4ec 1325=head1 IMPORTANT FILES
a018f134 1326
d9e8f4ec 1327Cron jobs and config are stored at /etc/cron.d/pve-zsync
6b4f676d 1328
d9e8f4ec 1329The VM config get copied on the destination machine to /var/lib/pve-zsync/
0bc3e510 1330
d9e8f4ec 1331=head1 COPYRIGHT AND DISCLAIMER
0bc3e510 1332
d9e8f4ec 1333Copyright (C) 2007-2015 Proxmox Server Solutions GmbH
0bc3e510 1334
d9e8f4ec 1335This program is free software: you can redistribute it and/or modify it
0bc3e510
WL
1336under the terms of the GNU Affero General Public License as published
1337by the Free Software Foundation, either version 3 of the License, or
1338(at your option) any later version.
1339
d9e8f4ec
WL
1340This program is distributed in the hope that it will be useful, but
1341WITHOUT ANY WARRANTY; without even the implied warranty of
1342MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1343Affero General Public License for more details.
0bc3e510 1344
d9e8f4ec
WL
1345You should have received a copy of the GNU Affero General Public
1346License along with this program. If not, see
1347<http://www.gnu.org/licenses/>.