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