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