]> git.proxmox.com Git - pve-zsync.git/blame - pve-zsync
Fix variable name
[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});
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;
fa07b081 766 if($line =~ m/^(?:((?:virtio|ide|scsi|sata|mp)\d+)|rootfs): ([^:]+:)([A-Za-z0-9\-]+),(.*)$/) {
9e7685c2
WL
767 $disk = $3;
768 $stor = $2;
845e36d7 769 } else {
3db33e1f
WL
770 print "Disk: \"$line\" will not include in pve-sync\n" if $get_err || $error;
771 next;
0bc3e510
WL
772 }
773
271c2572
WB
774 my $cmd = [];
775 push @$cmd, 'ssh', "root\@$ip", '--' if $ip;
776 push @$cmd, 'pvesm', 'path', "$stor$disk";
b52d13b3
WB
777 my $path = run_cmd($cmd);
778
9303e6fc
WL
779 die "Get no path from pvesm path $stor$disk\n" if !$path;
780
9e7685c2 781 if ($vm_type eq 'qemu' && $path =~ m/^\/dev\/zvol\/(\w+.*)(\/$disk)$/) {
b52d13b3
WB
782
783 my @array = split('/', $1);
3df8b97d 784 $disks->{$num}->{pool} = shift(@array);
b52d13b3
WB
785 $disks->{$num}->{all} = $disks->{$num}->{pool};
786 if (0 < @array) {
787 $disks->{$num}->{path} = join('/', @array);
788 $disks->{$num}->{all} .= "\/$disks->{$num}->{path}";
0bc3e510 789 }
b52d13b3
WB
790 $disks->{$num}->{last_part} = $disk;
791 $disks->{$num}->{all} .= "\/$disk";
792
9e7685c2 793 $num++;
aec521ca 794 } elsif ($vm_type eq 'lxc' && $path =~ m/^\/(\w+.+)(\/(\w+.*))*(\/$disk)$/) {
9e7685c2
WL
795
796 $disks->{$num}->{pool} = $1;
797 $disks->{$num}->{all} = $disks->{$num}->{pool};
798
799 if ($2) {
aec521ca 800 $disks->{$num}->{path} = $3;
9e7685c2
WL
801 $disks->{$num}->{all} .= "\/$disks->{$num}->{path}";
802 }
803
804 $disks->{$num}->{last_part} = $disk;
805 $disks->{$num}->{all} .= "\/$disk";
806
b52d13b3
WB
807 $num++;
808
809 } else {
810 die "ERROR: in path\n";
0bc3e510
WL
811 }
812 }
76b2c677 813
3db33e1f 814 die "Vm include no disk on zfs.\n" if !$disks->{0};
0bc3e510
WL
815 return $disks;
816}
817
818sub snapshot_destroy {
819 my ($source, $dest, $method, $snap) = @_;
820
271c2572 821 my @zfscmd = ('zfs', 'destroy');
76b2c677 822 my $snapshot = "$source->{all}\@$snap";
0bc3e510
WL
823
824 eval {
825 if($source->{ip} && $method eq 'ssh'){
271c2572 826 run_cmd(['ssh', "root\@$source->{ip}", '--', @zfscmd, $snapshot]);
0bc3e510 827 } else {
271c2572 828 run_cmd([@zfscmd, $snapshot]);
0bc3e510
WL
829 }
830 };
831 if (my $erro = $@) {
832 warn "WARN: $erro";
833 }
c85692fa 834 if ($dest) {
271c2572 835 my @ssh = $dest->{ip} ? ('ssh', "root\@$dest->{ip}", '--') : ();
0bc3e510 836
76b2c677 837 my $path = "$dest->{all}\/$source->{last_part}";
0bc3e510 838
0bc3e510 839 eval {
271c2572 840 run_cmd([@ssh, @zfscmd, "$path\@$snap"]);
0bc3e510
WL
841 };
842 if (my $erro = $@) {
843 warn "WARN: $erro";
844 }
845 }
846}
847
848sub snapshot_exist {
eee21241 849 my ($source , $dest, $method) = @_;
0bc3e510 850
271c2572
WB
851 my $cmd = [];
852 push @$cmd, 'ssh', "root\@$dest->{ip}", '--' if $dest->{ip};
853 push @$cmd, 'zfs', 'list', '-rt', 'snapshot', '-Ho', 'name';
854 push @$cmd, "$dest->{all}/$source->{last_part}\@$source->{old_snap}";
0bc3e510
WL
855
856 my $text = "";
857 eval {$text =run_cmd($cmd);};
eee21241 858 if (my $erro =$@) {
0bc3e510
WL
859 warn "WARN: $erro";
860 return undef;
861 }
862
863 while ($text && $text =~ s/^(.*?)(\n|$)//) {
eee21241 864 my $line =$1;
0bc3e510
WL
865 return 1 if $line =~ m/^.*$source->{old_snap}$/;
866 }
867}
868
869sub send_image {
76b2c677 870 my ($source, $dest, $param) = @_;
0bc3e510 871
271c2572 872 my $cmd = [];
0bc3e510 873
271c2572
WB
874 push @$cmd, 'ssh', "root\@$source->{ip}", '--' if $source->{ip};
875 push @$cmd, 'zfs', 'send';
876 push @$cmd, '-v' if $param->{verbose};
0bc3e510 877
eee21241 878 if($source->{last_snap} && snapshot_exist($source , $dest, $param->{method})) {
271c2572 879 push @$cmd, '-i', "$source->{all}\@$source->{last_snap}";
0bc3e510 880 }
271c2572 881 push @$cmd, '--', "$source->{all}\@$source->{new_snap}";
0bc3e510 882
76b2c677
WL
883 if ($param->{limit}){
884 my $bwl = $param->{limit}*1024;
271c2572 885 push @$cmd, \'|', 'cstream', '-t', $bwl;
0bc3e510 886 }
1193273e
WL
887 my $target = "$dest->{all}/$source->{last_part}";
888 $target =~ s!/+!/!g;
889
271c2572 890 push @$cmd, \'|';
eee21241
WL
891 push @$cmd, 'ssh', "root\@$dest->{ip}", '--' if $dest->{ip};
892 push @$cmd, 'zfs', 'recv', '-F', '--';
893 push @$cmd, "$target";
0bc3e510 894
eee21241
WL
895 eval {
896 run_cmd($cmd)
897 };
0bc3e510 898
eee21241
WL
899 if (my $erro = $@) {
900 snapshot_destroy($source, undef, $param->{method}, $source->{new_snap});
901 die $erro;
902 };
903 }
0bc3e510
WL
904
905
eee21241
WL
906 sub send_config{
907 my ($source, $dest, $method) = @_;
0bc3e510 908
d9e8f4ec 909 my $source_target = $source->{vm_type} eq 'qemu' ? "$QEMU_CONF/$source->{vmid}.conf": "$LXC_CONF/$source->{vmid}.conf";
eee21241 910 my $dest_target_new ="$source->{vmid}.conf.$source->{vm_type}.$source->{new_snap}";
76b2c677 911
d9e8f4ec 912 my $config_dir = $dest->{last_part} ? "${CONFIG_PATH}/$dest->{last_part}" : $CONFIG_PATH;
739c195a 913
d9e8f4ec 914 $dest_target_new = $config_dir.'/'.$dest_target_new;
739c195a 915
eee21241
WL
916 if ($method eq 'ssh'){
917 if ($dest->{ip} && $source->{ip}) {
918 run_cmd(['ssh', "root\@$dest->{ip}", '--', 'mkdir', '-p', '--', $config_dir]);
919 run_cmd(['scp', '--', "root\@[$source->{ip}]:$source_target", "root\@[$dest->{ip}]:$dest_target_new"]);
920 } elsif ($dest->{ip}) {
921 run_cmd(['ssh', "root\@$dest->{ip}", '--', 'mkdir', '-p', '--', $config_dir]);
922 run_cmd(['scp', '--', $source_target, "root\@[$dest->{ip}]:$dest_target_new"]);
923 } elsif ($source->{ip}) {
924 run_cmd(['mkdir', '-p', '--', $config_dir]);
925 run_cmd(['scp', '--', "root\@$source->{ip}:$source_target", $dest_target_new]);
926 }
0bc3e510 927
eee21241 928 if ($source->{destroy}){
d9e8f4ec 929 my $dest_target_old ="${config_dir}/$source->{vmid}.conf.$source->{vm_type}.$source->{old_snap}";
eee21241
WL
930 if($dest->{ip}){
931 run_cmd(['ssh', "root\@$dest->{ip}", '--', 'rm', '-f', '--', $dest_target_old]);
932 } else {
933 run_cmd(['rm', '-f', '--', $dest_target_old]);
934 }
0bc3e510 935 }
eee21241
WL
936 } elsif ($method eq 'local') {
937 run_cmd(['mkdir', '-p', '--', $config_dir]);
938 run_cmd(['cp', $source_target, $dest_target_new]);
0bc3e510
WL
939 }
940 }
0bc3e510 941
eee21241
WL
942 sub get_date {
943 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
944 my $datestamp = sprintf ("%04d-%02d-%02d_%02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
0bc3e510 945
eee21241
WL
946 return $datestamp;
947 }
0bc3e510 948
eee21241
WL
949 sub status {
950 my $cfg = read_cron();
0bc3e510 951
3daaef60 952 my $status_list = sprintf("%-25s%-25s%-10s\n", "SOURCE", "NAME", "STATUS");
0bc3e510 953
eee21241 954 my $states = read_state();
76b2c677 955
eee21241
WL
956 foreach my $source (sort keys%{$cfg}) {
957 foreach my $sync_name (sort keys%{$cfg->{$source}}) {
958 $status_list .= sprintf("%-25s", cut_target_width($source, 25));
3daaef60 959 $status_list .= sprintf("%-25s", cut_target_width($sync_name, 25));
eee21241
WL
960 $status_list .= "$states->{$source}->{$sync_name}->{state}\n";
961 }
0bc3e510 962 }
0bc3e510 963
eee21241
WL
964 return $status_list;
965 }
0bc3e510 966
eee21241
WL
967 sub enable_job {
968 my ($param) = @_;
28006d67 969
eee21241
WL
970 my $job = get_job($param);
971 $job->{state} = "ok";
972 update_state($job);
973 update_cron($job);
974 }
28006d67 975
eee21241
WL
976 sub disable_job {
977 my ($param) = @_;
28006d67 978
eee21241
WL
979 my $job = get_job($param);
980 $job->{state} = "stopped";
981 update_state($job);
982 update_cron($job);
983 }
28006d67 984
eee21241 985 my $command = $ARGV[0];
0bc3e510 986
eee21241
WL
987 my $commands = {'destroy' => 1,
988 'create' => 1,
989 'sync' => 1,
990 'list' => 1,
991 'status' => 1,
992 'help' => 1,
993 'enable' => 1,
994 'disable' => 1};
0bc3e510 995
eee21241
WL
996 if (!$command || !$commands->{$command}) {
997 usage();
998 die "\n";
999 }
0bc3e510 1000
eee21241 1001 my $help_sync = "$PROGNAME sync -dest <string> -source <string> [OPTIONS]\n
0bc3e510
WL
1002\twill sync one time\n
1003\t-dest\tstring\n
1004\t\tthe destination target is like [IP:]<Pool>[/Path]\n
1005\t-limit\tinteger\n
1006\t\tmax sync speed in kBytes/s, default unlimited\n
1007\t-maxsnap\tinteger\n
1008\t\thow much snapshots will be kept before get erased, default 1/n
1009\t-name\tstring\n
1010\t\tname of the sync job, if not set it is default.
1011\tIt is only necessary if scheduler allready contains this source.\n
1012\t-source\tstring\n
1013\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
1014
eee21241 1015 my $help_create = "$PROGNAME create -dest <string> -source <string> [OPTIONS]/n
0bc3e510 1016\tCreate a sync Job\n
d9e8f4ec 1017\t-dest\tstring\n
0bc3e510 1018\t\tthe destination target is like [IP]:<Pool>[/Path]\n
0bc3e510 1019\t-limit\tinteger\n
28006d67 1020\t\tmax sync speed in kBytes/s, default unlimited\n
0bc3e510
WL
1021\t-maxsnap\tstring\n
1022\t\thow much snapshots will be kept before get erased, default 1\n
1023\t-name\tstring\n
1024\t\tname of the sync job, if not set it is default\n
1025\t-skip\tboolean\n
1026\t\tif this flag is set it will skip the first sync\n
1027\t-source\tstring\n
1028\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
1029
eee21241 1030 my $help_destroy = "$PROGNAME destroy -source <string> [OPTIONS]\n
0bc3e510
WL
1031\tremove a sync Job from the scheduler\n
1032\t-name\tstring\n
1033\t\tname of the sync job, if not set it is default\n
1034\t-source\tstring\n
1035\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
1036
eee21241 1037 my $help_help = "$PROGNAME help <cmd> [OPTIONS]\n
0bc3e510
WL
1038\tGet help about specified command.\n
1039\t<cmd>\tstring\n
1040\t\tCommand name\n
1041\t-verbose\tboolean\n
1042\t\tVerbose output format.\n";
1043
eee21241 1044 my $help_list = "$PROGNAME list\n
0bc3e510
WL
1045\tGet a List of all scheduled Sync Jobs\n";
1046
eee21241 1047 my $help_status = "$PROGNAME status\n
0bc3e510
WL
1048\tGet the status of all scheduled Sync Jobs\n";
1049
eee21241 1050 my $help_enable = "$PROGNAME enable -source <string> [OPTIONS]\n
28006d67
WL
1051\tenable a syncjob and reset error\n
1052\t-name\tstring\n
1053\t\tname of the sync job, if not set it is default\n
1054\t-source\tstring\n
1055\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
1056
eee21241 1057 my $help_disable = "$PROGNAME disable -source <string> [OPTIONS]\n
28006d67
WL
1058\tpause a syncjob\n
1059\t-name\tstring\n
1060\t\tname of the sync job, if not set it is default\n
1061\t-source\tstring\n
1062\t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
1063
eee21241
WL
1064 sub help {
1065 my ($command) = @_;
0bc3e510 1066
eee21241
WL
1067 switch($command){
1068 case 'help'
1069 {
1070 die "$help_help\n";
1071 }
1072 case 'sync'
1073 {
1074 die "$help_sync\n";
1075 }
1076 case 'destroy'
1077 {
1078 die "$help_destroy\n";
1079 }
1080 case 'create'
1081 {
1082 die "$help_create\n";
1083 }
1084 case 'list'
1085 {
1086 die "$help_list\n";
1087 }
1088 case 'status'
1089 {
1090 die "$help_status\n";
1091 }
1092 case 'enable'
1093 {
1094 die "$help_enable\n";
1095 }
1096 case 'disable'
1097 {
1098 die "$help_enable\n";
1099 }
1100 }
1101
1102 }
1103
1104 my @arg = @ARGV;
1105 my $param = parse_argv(@arg);
1106
1107
1108 switch($command) {
1109 case "destroy"
0bc3e510 1110 {
eee21241
WL
1111 die "$help_destroy\n" if !$param->{source};
1112 check_target($param->{source});
1113 destroy_job($param);
0bc3e510 1114 }
eee21241 1115 case "sync"
0bc3e510 1116 {
eee21241
WL
1117 die "$help_sync\n" if !$param->{source} || !$param->{dest};
1118 check_target($param->{source});
1119 check_target($param->{dest});
1120 sync($param);
0bc3e510 1121 }
eee21241 1122 case "create"
0bc3e510 1123 {
eee21241
WL
1124 die "$help_create\n" if !$param->{source} || !$param->{dest};
1125 check_target($param->{source});
1126 check_target($param->{dest});
1127 init($param);
0bc3e510 1128 }
eee21241 1129 case "status"
0bc3e510 1130 {
eee21241 1131 print status();
0bc3e510 1132 }
eee21241 1133 case "list"
0bc3e510 1134 {
eee21241 1135 print list();
0bc3e510 1136 }
eee21241 1137 case "help"
0bc3e510 1138 {
eee21241
WL
1139 my $help_command = $ARGV[1];
1140 if ($help_command && $commands->{$help_command}) {
1141 print help($help_command);
1142 }
1143 if ($param->{verbose} == 1){
1144 exec("man $PROGNAME");
1145 } else {
1146 usage(1);
1147 }
0bc3e510 1148 }
eee21241 1149 case "enable"
28006d67 1150 {
eee21241
WL
1151 die "$help_enable\n" if !$param->{source};
1152 check_target($param->{source});
1153 enable_job($param);
28006d67 1154 }
eee21241 1155 case "disable"
28006d67 1156 {
eee21241
WL
1157 die "$help_disable\n" if !$param->{source};
1158 check_target($param->{source});
1159 disable_job($param);
28006d67 1160 }
0bc3e510
WL
1161 }
1162
eee21241
WL
1163 sub usage {
1164 my ($help) = @_;
1165
1166 print("ERROR:\tno command specified\n") if !$help;
1167 print("USAGE:\t$PROGNAME <COMMAND> [ARGS] [OPTIONS]\n");
1168 print("\t$PROGNAME help [<cmd>] [OPTIONS]\n\n");
1169 print("\t$PROGNAME create -dest <string> -source <string> [OPTIONS]\n");
1170 print("\t$PROGNAME destroy -source <string> [OPTIONS]\n");
1171 print("\t$PROGNAME disable -source <string> [OPTIONS]\n");
1172 print("\t$PROGNAME enable -source <string> [OPTIONS]\n");
1173 print("\t$PROGNAME list\n");
1174 print("\t$PROGNAME status\n");
1175 print("\t$PROGNAME sync -dest <string> -source <string> [OPTIONS]\n");
0bc3e510 1176 }
0bc3e510 1177
eee21241
WL
1178 sub check_target {
1179 my ($target) = @_;
1180 parse_target($target);
1181 }
0bc3e510 1182
d9e8f4ec 1183__END__
0bc3e510 1184
d9e8f4ec 1185=head1 NAME
0bc3e510 1186
d9e8f4ec 1187pve-zsync - PVE ZFS Replication Manager
0bc3e510 1188
d9e8f4ec 1189=head1 SYNOPSIS
0bc3e510 1190
d9e8f4ec 1191pve-zsync <COMMAND> [ARGS] [OPTIONS]
0bc3e510 1192
d9e8f4ec 1193pve-zsync help <cmd> [OPTIONS]
0bc3e510
WL
1194
1195 Get help about specified command.
1196
d9e8f4ec 1197 <cmd> string
0bc3e510 1198
d9e8f4ec 1199 Command name
0bc3e510
WL
1200
1201 -verbose boolean
1202
d9e8f4ec 1203 Verbose output format.
0bc3e510 1204
d9e8f4ec 1205pve-zsync create -dest <string> -source <string> [OPTIONS]
0bc3e510 1206
d9e8f4ec 1207 Create a sync Job
0bc3e510 1208
d9e8f4ec 1209 -dest string
0bc3e510 1210
d9e8f4ec 1211 the destination target is like [IP]:<Pool>[/Path]
0bc3e510 1212
d9e8f4ec 1213 -limit integer
0bc3e510 1214
28006d67 1215 max sync speed in kBytes/s, default unlimited
0bc3e510 1216
d9e8f4ec 1217 -maxsnap string
0bc3e510 1218
d9e8f4ec 1219 how much snapshots will be kept before get erased, default 1
0bc3e510 1220
d9e8f4ec 1221 -name string
0bc3e510 1222
d9e8f4ec 1223 name of the sync job, if not set it is default
0bc3e510 1224
d9e8f4ec 1225 -skip boolean
0bc3e510 1226
d9e8f4ec 1227 if this flag is set it will skip the first sync
0bc3e510 1228
d9e8f4ec 1229 -source string
0bc3e510 1230
d9e8f4ec 1231 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
0bc3e510 1232
c85692fa 1233pve-zsync destroy -source <string> [OPTIONS]
0bc3e510 1234
d9e8f4ec 1235 remove a sync Job from the scheduler
0bc3e510 1236
d9e8f4ec 1237 -name string
0bc3e510
WL
1238
1239 name of the sync job, if not set it is default
1240
d9e8f4ec 1241 -source string
0bc3e510 1242
d9e8f4ec 1243 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
0bc3e510 1244
d9e8f4ec 1245pve-zsync disable -source <string> [OPTIONS]
28006d67 1246
d9e8f4ec 1247 pause a sync job
28006d67 1248
d9e8f4ec 1249 -name string
28006d67 1250
d9e8f4ec 1251 name of the sync job, if not set it is default
28006d67 1252
d9e8f4ec 1253 -source string
28006d67 1254
d9e8f4ec 1255 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
28006d67 1256
c85692fa 1257pve-zsync enable -source <string> [OPTIONS]
28006d67 1258
d9e8f4ec 1259 enable a syncjob and reset error
28006d67 1260
d9e8f4ec 1261 -name string
28006d67
WL
1262
1263 name of the sync job, if not set it is default
1264
d9e8f4ec 1265 -source string
28006d67 1266
d9e8f4ec
WL
1267 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1268pve-zsync list
0bc3e510 1269
d9e8f4ec 1270 Get a List of all scheduled Sync Jobs
0bc3e510 1271
d9e8f4ec 1272pve-zsync status
0bc3e510 1273
d9e8f4ec 1274 Get the status of all scheduled Sync Jobs
0bc3e510 1275
d9e8f4ec 1276pve-zsync sync -dest <string> -source <string> [OPTIONS]
0bc3e510 1277
d9e8f4ec 1278 will sync one time
0bc3e510 1279
d9e8f4ec 1280 -dest string
0bc3e510 1281
d9e8f4ec 1282 the destination target is like [IP:]<Pool>[/Path]
0bc3e510
WL
1283
1284 -limit integer
1285
1286 max sync speed in kBytes/s, default unlimited
1287
d9e8f4ec 1288 -maxsnap integer
0bc3e510 1289
d9e8f4ec 1290 how much snapshots will be kept before get erased, default 1
0bc3e510 1291
d9e8f4ec 1292 -name string
0bc3e510 1293
d9e8f4ec
WL
1294 name of the sync job, if not set it is default.
1295 It is only necessary if scheduler allready contains this source.
0bc3e510 1296
d9e8f4ec 1297 -source string
0bc3e510 1298
d9e8f4ec 1299 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
0bc3e510
WL
1300
1301=head1 DESCRIPTION
1302
1303This Tool helps you to sync your VM or directory which stored on ZFS between 2 servers.
1304This tool also has the capability to add jobs to cron so the sync will be automatically done.
76b2c677 1305The 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 1306To config cron see man crontab.
0bc3e510 1307
d9e8f4ec 1308=head2 PVE ZFS Storage sync Tool
0bc3e510 1309
d9e8f4ec 1310This Tool can get remote pool on other PVE or send Pool to others ZFS machines
0bc3e510 1311
d9e8f4ec 1312=head1 EXAMPLES
0bc3e510 1313
d9e8f4ec
WL
1314add sync job from local VM to remote ZFS Server
1315pve-zsync create -source=100 -dest=192.168.1.2:zfspool
0bc3e510 1316
d9e8f4ec 1317=head1 IMPORTANT FILES
a018f134 1318
d9e8f4ec 1319Cron jobs and config are stored at /etc/cron.d/pve-zsync
6b4f676d 1320
d9e8f4ec 1321The VM config get copied on the destination machine to /var/lib/pve-zsync/
0bc3e510 1322
d9e8f4ec 1323=head1 COPYRIGHT AND DISCLAIMER
0bc3e510 1324
d9e8f4ec 1325Copyright (C) 2007-2015 Proxmox Server Solutions GmbH
0bc3e510 1326
d9e8f4ec 1327This program is free software: you can redistribute it and/or modify it
0bc3e510
WL
1328under the terms of the GNU Affero General Public License as published
1329by the Free Software Foundation, either version 3 of the License, or
1330(at your option) any later version.
1331
d9e8f4ec
WL
1332This program is distributed in the hope that it will be useful, but
1333WITHOUT ANY WARRANTY; without even the implied warranty of
1334MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1335Affero General Public License for more details.
0bc3e510 1336
d9e8f4ec
WL
1337You should have received a copy of the GNU Affero General Public
1338License along with this program. If not, see
1339<http://www.gnu.org/licenses/>.