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