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