]>
git.proxmox.com Git - pve-zsync.git/blob - pve-zsync
9d49730f2465041bec679dbc92bd71c09e461838
3 my $PROGNAME = "pve-zsync";
4 my $CONFIG_PATH = '/var/lib/'.$PROGNAME.'/';
5 my $CONFIG = "$PROGNAME.cfg";
6 my $CRONJOBS = '/etc/cron.d/'.$PROGNAME;
7 my $VMCONFIG = '/var/lib/'.$PROGNAME.'/';
8 my $PATH = "/usr/sbin/";
9 my $QEMU_CONF = '/etc/pve/local/qemu-server/';
14 use Data
::Dumper
qw(Dumper);
15 use Fcntl
qw(:flock SEEK_END);
19 check_bin
('cstream');
27 foreach my $p (split (/:/, $ENV{PATH
})) {
34 warn "unable to find command '$bin'\n";
38 my ($text, $max) = @_;
40 return $text if (length($text) <= $max);
41 my @spl = split('/', $text);
43 my $count = length($spl[@spl-1]);
44 return "..\/".substr($spl[@spl-1],($count-$max)+3 ,$count) if $count > $max;
46 $count += length($spl[0]) if @spl > 1;
47 return substr($spl[0], 0, $max-4-length
($spl[@spl-1]))."\/..\/".$spl[@spl-1] if $count > $max;
50 $rest = $max-$count if ($max-$count > 0);
52 return "$spl[0]".substr($text, length($spl[0]), $rest)."..\/".$spl[@spl-1];
57 flock($fh, LOCK_EX
) or die "Cannot lock config - $!\n";
59 seek($fh, 0, SEEK_END
) or die "Cannot seek - $!\n";
64 flock($fh, LOCK_UN
) or die "Cannot unlock config- $!\n";
68 my ($source, $name, $cfg) = @_;
70 my $id = $source->{vmid
} ?
$source->{vmid
} : $source->{abs_path
};
71 my $status = $cfg->{$id}->{$name}->{status
};
73 if ($cfg->{$id}->{$name}->{status
}){
81 sub check_pool_exsits
{
85 $cmd = "ssh root\@$ip " if $ip;
86 $cmd .= "zfs list $pool -H";
100 open(my $fh, ">", "$CONFIG_PATH$CONFIG")
101 or die "cannot open >$CONFIG_PATH$CONFIG: $!\n";
103 my $text = decode_config
($cfg);
110 sub read_from_config
{
112 unless(-e
"$CONFIG_PATH$CONFIG") {
116 open(my $fh, "<", "$CONFIG_PATH$CONFIG")
117 or die "cannot open > $CONFIG_PATH$CONFIG: $!\n";
127 my $cfg = encode_config
($text);
135 foreach my $source (sort keys%{$cfg}){
136 foreach my $sync_name (sort keys%{$cfg->{$source}}){
137 $raw .= "$source: $sync_name\n";
138 foreach my $parameter (sort keys%{$cfg->{$source}->{$sync_name}}){
139 $raw .= "\t$parameter: $cfg->{$source}->{$sync_name}->{$parameter}\n";
153 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
156 next if $line =~ m/^\#/;
157 next if $line =~ m/^\s*$/;
159 if ($line =~ m/^(\t| )(\w+): (.+)/){
163 if ($par eq 'source_pool') {
164 $cfg->{$source}->{$sync_name}->{$par} = $value;
165 die "error in Config: SourcePool value doubled\n" if ($check & 1);
167 } elsif ($par eq 'source_ip') {
168 $cfg->{$source}->{$sync_name}->{$par} = $value;
169 die "error in Config: SourceIP value doubled\n" if ($check & 2);
171 } elsif ($par eq 'status') {
172 $cfg->{$source}->{$sync_name}->{$par} = $value;
173 die "error in Config: Status value doubled\n" if ($check & 4);
175 } elsif ($par eq 'method') {
176 $cfg -> {$source}->{$sync_name}->{$par} = $value;
177 die "error in Config: Method value doubled\n" if ($check & 8);
179 } elsif ($par eq 'interval') {
180 $cfg -> {$source}->{$sync_name}->{$par} = $value;
181 die "error in Config: Iterval value doubled\n" if ($check & 16);
183 } elsif ($par eq 'limit') {
184 $cfg -> {$source}->{$sync_name}->{$par} = $value;
185 die "error in Config: Limit value doubled\n" if ($check & 32);
187 } elsif ($par eq 'dest_pool') {
188 $cfg -> {$source}->{$sync_name}->{$par} = $value;
189 die "error in Config: DestPool value doubled\n" if ($check & 64);
191 } elsif ($par eq 'dest_ip') {
192 $cfg -> {$source}->{$sync_name}->{$par} = $value;
193 die "error in Config: DestIp value doubled\n" if ($check & 128);
195 } elsif ($par eq 'dest_path') {
196 $cfg -> {$source}->{$sync_name}->{$par} = $value;
197 die "error in Config: DestPath value doubled\n" if ($check & 256);
199 } elsif ($par eq 'source_path') {
200 $cfg -> {$source}->{$sync_name}->{$par} = $value;
201 die "error in Config: SourcePath value doubled\n" if ($check & 512);
203 } elsif ($par eq 'vmid') {
204 $cfg -> {$source}->{$sync_name}->{$par} = $value;
205 die "error in Config: Vmid value doubled\n" if ($check & 1024);
207 } elsif ($par =~ 'lsync') {
208 $cfg->{$source}->{$sync_name}->{$par} = $value;
209 die "error in Config: lsync value doubled\n" if ($check & 2048);
211 } elsif ($par =~ 'maxsnap') {
212 $cfg->{$source}->{$sync_name}->{$par} = $value;
213 die "error in Config: maxsnap value doubled\n" if ($check & 4096);
216 die "error in Config\n";
218 } elsif ($line =~ m/^((\d+.\d+.\d+.\d+):)?([\w\-\_\/]+): (.+){0,1}/){
220 $sync_name = $4 ?
$4 : 'default' ;
221 $cfg->{$source}->{$sync_name} = undef;
222 $cfg->{$source}->{$sync_name}->{source_ip
} = $2 if $2;
232 if ($text =~ m/^((\d+.\d+.\d+.\d+):)?((\w+)\/?
)([\w\
/\-\_]*)?$/) {
234 die "Input not valid\n" if !$3;
242 if ($tmp =~ m/^(\d\d\d+)$/){
243 $target->{vmid
} = $tmp;
245 $target->{pool
} = $4;
248 $target->{path
} = "\/$5";
251 $target->{abs_path
} = $abs_path;
256 die "Input not valid\n";
261 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
263 my $list = sprintf("%-25s%-15s%-7s%-20s%-10s%-5s\n" , "SOURCE", "NAME", "ACTIVE", "LAST SYNC", "INTERVAL", "TYPE");
265 foreach my $source (sort keys%{$cfg}){
266 foreach my $sync_name (sort keys%{$cfg->{$source}}){
267 my $source_name = $source;
268 $source_name = $cfg->{$source}->{$sync_name}->{source_ip
}.":".$source if $cfg->{$source}->{$sync_name}->{source_ip
};
269 $list .= sprintf("%-25s%-15s", cut_to_width
($source_name,25), cut_to_width
($sync_name,15));
272 if($cfg->{$source}->{$sync_name}->{status
} eq 'syncing'){
278 $list .= sprintf("%-7s", $active);
279 $list .= sprintf("%-20s",$cfg->{$source}->{$sync_name}->{lsync
});
280 $list .= sprintf("%-10s",$cfg->{$source}->{$sync_name}->{interval
});
281 $list .= sprintf("%-5s\n",$cfg->{$source}->{$sync_name}->{method});
292 $cmd = "ssh root\@$target->{ip} " if ($target->{ip
});
293 $cmd .= "qm status $target->{vmid}";
295 my $res = run_cmd
($cmd);
297 return 1 if ($res =~ m/^status.*$/);
304 my $cfg = read_from_config
;
308 my $name = $param->{name
} ?
$param->{name
} : "default";
309 my $interval = $param->{interval
} ?
$param->{interval
} : 15;
311 my $source = parse_target
($param->{source
});
312 my $dest = parse_target
($param->{dest
});
314 $vm->{$name}->{dest_pool
} = $dest->{pool
};
315 $vm->{$name}->{dest_ip
} = $dest->{ip
} if $dest->{ip
};
316 $vm->{$name}->{dest_path
} = $dest->{path
} if $dest->{path
};
318 $param->{method} = "local" if !$dest->{ip
} && !$source->{ip
};
319 $vm->{$name}->{status
} = "ok";
320 $vm->{$name}->{interval
} = $interval;
321 $vm->{$name}->{method} = $param->{method} ?
$param->{method} : "ssh";
322 $vm->{$name}->{limit
} = $param->{limit
} if $param->{limit
};
323 $vm->{$name}->{maxsnap
} = $param->{maxsnap
} if $param->{maxsnap
};
325 if ( my $ip = $vm->{$name}->{dest_ip
} ) {
326 run_cmd
("ssh-copy-id -i /root/.ssh/id_rsa.pub root\@$ip");
329 if ( my $ip = $source->{ip
} ) {
330 run_cmd
("ssh-copy-id -i /root/.ssh/id_rsa.pub root\@$ip");
333 die "Pool $dest->{abs_path} does not exists\n" if check_pool_exsits
($dest->{abs_path
}, $dest->{ip
});
335 my $check = check_pool_exsits
($source->{abs_path
}, $source->{ip
}) if !$source->{vmid
} && $source->{abs_path
};
337 die "Pool $source->{abs_path} does not exists\n" if undef($check);
340 my ($vm, $name) = @_;
343 if ($vm->{$name}->{vmid
}) {
344 $source = "$vm->{$name}->{source_ip}:" if $vm->{$name}->{source_ip
};
345 $source .= $vm->{$name}->{vmid
};
347 $source = $vm->{$name}->{source_pool
};
348 $source .= $vm->{$name}->{source_path
} if $vm->{$name}->{source_path
};
350 die "Config already exists\n" if $cfg->{$source}->{$name};
354 $cfg->{$source}->{$name} = $vm->{$name};
356 write_to_config
($cfg);
359 if ($source->{vmid
}) {
360 die "VM $source->{vmid} doesn't exist\n" if !vm_exists
($source);
361 my $disks = get_disks
($source);
362 $vm->{$name}->{vmid
} = $source->{vmid
};
363 $vm->{$name}->{lsync
} = 0;
364 $vm->{$name}->{source_ip
} = $source->{ip
} if $source->{ip
};
366 &$add_job($vm, $name);
369 $vm->{$name}->{source_pool
} = $source->{pool
};
370 $vm->{$name}->{source_ip
} = $source->{ip
} if $source->{ip
};
371 $vm->{$name}->{source_path
} = $source->{path
} if $source->{path
};
372 $vm->{$name}->{lsync
} = 0;
374 &$add_job($vm, $name);
377 eval {sync
($param) if !$param->{skip
};};
387 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
388 my $name = $param->{name
} ?
$param->{name
} : "default";
390 my $source = parse_target
($param->{source
});
392 my $delete_cron = sub {
393 my ($path, $name, $cfg) = @_;
395 die "Source does not exist!\n" unless $cfg->{$path} ;
397 die "Sync Name does not exist!\n" unless $cfg->{$path}->{$name};
399 my $source .= $cfg->{$path}->{$name}->{source_ip
} ?
"$cfg->{$path}->{$name}->{source_ip}:" : '';
401 $source .= $cfg->{$path}->{$name}->{source_pool
};
402 $source .= $cfg->{$path}->{$name}->{source_path
} ?
$cfg->{$path}->{$name}->{source_path
} :'';
404 my $dest = $cfg->{$path}->{$name}->{dest_ip
} ?
$cfg->{$path}->{$name}->{dest_ip
} :"";
405 $dest .= $cfg->{$path}->{$name}->{dest_pool
};
406 $dest .= $cfg->{$path}->{$name}->{dest_path
} ?
$cfg->{$path}->{$name}->{dest_path
} :'';
408 delete $cfg->{$path}->{$name};
410 delete $cfg->{$path} if keys%{$cfg->{$path}} == 0;
412 write_to_config
($cfg);
414 cron_del
($source, $dest, $name);
418 if ($source->{vmid
}) {
419 my $path = $source->{vmid
};
421 &$delete_cron($path, $name, $cfg)
425 my $path = $source->{pool
};
426 $path .= $source->{path
} if $source->{path
};
428 &$delete_cron($path, $name, $cfg);
435 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
437 my $name = $param->{name
} ?
$param->{name
} : "default";
438 my $max_snap = $param->{maxsnap
} ?
$param->{maxsnap
} : 1;
439 my $method = $param->{method} ?
$param->{method} : "ssh";
441 my $dest = parse_target
($param->{dest
});
442 my $source = parse_target
($param->{source
});
444 my $sync_path = sub {
445 my ($source, $name, $cfg, $max_snap, $dest, $method) = @_;
447 ($source->{old_snap
},$source->{last_snap
}) = snapshot_get
($source, $dest, $max_snap, $name);
449 my $job_status = check_config
($source, $name, $cfg) if $cfg;
450 die "VM Status: $job_status syncing will not done!\n" if ($job_status && $job_status ne "ok");
453 my $conf_name = $source->{abs_path
};
454 $conf_name = $source->{vmid
} if $source->{vmid
};
455 $cfg->{$conf_name}->{$name}->{status
} = "syncing";
456 write_to_config
($cfg);
462 $date = snapshot_add
($source, $dest, $name);
464 send_image
($source, $dest, $method, $param->{verbose
}, $param->{limit
});
466 snapshot_destroy
($source, $dest, $method, $source->{old_snap
}) if ($source->{destroy
} && $source->{old_snap
});
470 my $conf_name = $source->{abs_path
};
471 $conf_name = $source->{vmid
} if $source->{vmid
};
472 $cfg->{$conf_name}->{$name}->{status
} = "error";
473 write_to_config
($cfg);
479 my $conf_name = $source->{abs_path
};
480 $conf_name = $source->{vmid
} if $source->{vmid
};
481 $cfg->{$conf_name}->{$name}->{status
} = "ok";
482 $cfg->{$conf_name}->{$name}->{lsync
} = $date;
483 write_to_config
($cfg);
487 $param->{method} = "ssh" if !$param->{method};
489 if ($source->{vmid
}) {
490 die "VM $source->{vmid} doesn't exist\n" if !vm_exists
($source);
491 my $disks = get_disks
($source);
493 foreach my $disk (sort keys %{$disks}) {
494 $source->{abs_path
} = $disks->{$disk}->{pool
};
495 $source->{abs_path
} .= "\/$disks->{$disk}->{path}" if $disks->{$disk}->{path
};
497 $source->{pool
} = $disks->{$disk}->{pool
};
498 $source->{path
} = "\/$disks->{$disk}->{path}";
500 &$sync_path($source, $name, $cfg, $max_snap, $dest, $method);
502 if ($method eq "ssh") {
503 send_config
($source, $dest,'ssh');
506 &$sync_path($source, $name, $cfg, $max_snap, $dest, $method);
511 my ($source, $dest, $max_snap, $name) = @_;
513 my $cmd = "zfs list -r -t snapshot -Ho name, -S creation ";
515 $cmd .= $source->{abs_path
};
516 $cmd = "ssh root\@$source->{ip} ".$cmd if $source->{ip
};
518 my $raw = run_cmd
($cmd);
521 my $last_snap = undef;
523 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
525 $last_snap = $line if $index == 1;
526 if ($index == $max_snap) {
527 $source->{destroy
} = 1;
533 $line =~ m/^(.+)\@(rep_$name\_.+)(\n|$)/;
534 return ($2, $last_snap) if $2;
540 my ($source, $dest, $name) = @_;
542 my $date = get_date
();
544 my $snap_name = "rep_$name\_".$date;
546 $source->{new_snap
} = $snap_name;
548 my $path = $source->{abs_path
}."\@".$snap_name;
550 my $cmd = "zfs snapshot $path";
551 $cmd = "ssh root\@$source->{ip} ".$cmd if $source->{ip
};
558 snapshot_destroy
($source, $dest, 'ssh', $snap_name);
567 open(my $fh, '>>', "$CRONJOBS")
568 or die "Could not open file: $!\n";
570 foreach my $name (keys%{$vm}){
571 my $text = "*/$vm->{$name}->{interval} * * * * root ";
572 $text .= "$PATH$PROGNAME sync";
573 $text .= " -source ";
574 if ($vm->{$name}->{vmid
}) {
575 $text .= "$vm->{$name}->{source_ip}:" if $vm->{$name}->{source_ip
};
576 $text .= "$vm->{$name}->{vmid} ";
578 $text .= "$vm->{$name}->{source_ip}:" if $vm->{$name}->{source_ip
};
579 $text .= "$vm->{$name}->{source_pool}";
580 $text .= "$vm->{$name}->{source_path}" if $vm->{$name}->{source_path
};
583 $text .= "$vm->{$name}->{dest_ip}:" if $vm->{$name}->{dest_ip
};
584 $text .= "$vm->{$name}->{dest_pool}";
585 $text .= "$vm->{$name}->{dest_path}" if $vm->{$name}->{dest_path
};
586 $text .= " -name $name ";
587 $text .= " -limit $vm->{$name}->{limit}" if $vm->{$name}->{limit
};
588 $text .= " -maxsnap $vm->{$name}->{maxsnap}" if $vm->{$name}->{maxsnap
};
596 my ($source, $dest, $name) = @_;
598 open(my $fh, '<', "$CRONJOBS")
599 or die "Could not open file: $!\n";
606 while ($text && $text =~ s/^(.*?)(\n|$)//) {
608 if ($line !~ m/^.*root $PATH$PROGNAME sync -source $source.*-dest $dest.*-name $name.*$/){
612 open($fh, '>', "$CRONJOBS")
613 or die "Could not open file: $!\n";
622 $cmd = "ssh root\@$target->{ip} " if $target->{ip
};
623 $cmd .= "qm config $target->{vmid}";
625 my $res = run_cmd
($cmd);
627 my $disks = parse_disks
($res, $target->{ip
});
634 print "Start CMD\n" if $DEBUG;
635 print Dumper
$cmd if $DEBUG;
636 my $output = `$cmd 2>&1`;
638 die $output if 0 != $?;
641 print Dumper
$output if $DEBUG;
642 print "END CMD\n" if $DEBUG;
647 my ($text, $ip) = @_;
653 $cmd .= "ssh root\@$ip " if $ip;
654 $cmd .= "pvesm zfsscan";
655 my $zfs_pools = run_cmd
($cmd);
656 while ($text && $text =~ s/^(.*?)(\n|$)//) {
660 my $is_disk = $line =~ m/^(virtio|ide|scsi|sata){1}\d+: /;
661 if($line =~ m/^(virtio\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
664 } elsif($line =~ m/^(ide\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
667 } elsif($line =~ m/^(scsi\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
670 } elsif($line =~ m/^(sata\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
675 die "disk is not on ZFS Storage\n" if $is_disk && !$disk && $line !~ m/cdrom/;
677 if($disk && $line !~ m/none/ && $line !~ m/cdrom/ ) {
679 $cmd .= "ssh root\@$ip " if $ip;
680 $cmd .= "pvesm path $stor$disk";
681 my $path = run_cmd
($cmd);
683 if ($path =~ m/^\/dev\
/zvol\/(\w
+).*(\
/$disk)$/){
685 $disks->{$num}->{pool
} = $1;
686 $disks->{$num}->{path
} = $disk;
690 die "ERROR: in path\n";
697 sub snapshot_destroy
{
698 my ($source, $dest, $method, $snap) = @_;
700 my $zfscmd = "zfs destroy ";
701 my $name = "$source->{path}\@$snap";
704 if($source->{ip
} && $method eq 'ssh'){
705 run_cmd
("ssh root\@$source->{ip} $zfscmd $source->{pool}$name");
707 run_cmd
("$zfscmd $source->{pool}$name");
714 my $ssh = $dest->{ip
} ?
"ssh root\@$dest->{ip}" : "";
717 $path ="$dest->{path}" if $dest->{path
};
719 my @dir = split(/\//, $source->{path
});
721 run_cmd
("$ssh $zfscmd $dest->{pool}$path\/$dir[@dir-1]\@$snap ");
730 my ($source ,$dest, $method) = @_;
733 $cmd = "ssh root\@$dest->{ip} " if $dest->{ip
};
734 $cmd .= "zfs list -rt snapshot -Ho name $dest->{pool}";
735 $cmd .= "$dest->{path}" if $dest->{path
};
736 my @dir = split(/\//, $source->{path
});
737 $cmd .= "\/$dir[@dir-1]\@$source->{old_snap}";
740 eval {$text =run_cmd
($cmd);};
746 while ($text && $text =~ s/^(.*?)(\n|$)//) {
748 return 1 if $line =~ m/^.*$source->{old_snap}$/;
753 my ($source, $dest, $method, $verbose, $limit) = @_;
757 $cmd .= "ssh root\@$source->{ip} " if $source->{ip
};
759 $cmd .= "-v " if $verbose;
761 if($source->{last_snap
} && snapshot_exist
($source ,$dest, $method)) {
762 $cmd .= "-i $source->{abs_path}\@$source->{old_snap} $source->{abs_path}\@$source->{new_snap} ";
764 $cmd .= "$source->{abs_path}\@$source->{new_snap} ";
768 my $bwl = $limit*1024;
769 $cmd .= "| cstream -t $bwl";
772 $cmd .= "ssh root\@$dest->{ip} " if $dest->{ip
};
773 $cmd .= "zfs recv $dest->{pool}";
774 $cmd .= "$dest->{path}" if $dest->{path
};
776 my @dir = split(/\//,$source->{path
});
777 $cmd .= "\/$dir[@dir-1]\@$source->{new_snap}";
784 snapshot_destroy
($source, undef, $method, $source->{new_snap
});
791 my ($source, $dest, $method) = @_;
793 if ($method eq 'ssh'){
794 if ($dest->{ip
} && $source->{ip
}) {
795 run_cmd
("ssh root\@$dest->{ip} mkdir $VMCONFIG -p");
796 run_cmd
("scp root\@$source->{ip}:$QEMU_CONF$source->{vmid}.conf root\@$dest->{ip}:$VMCONFIG$source->{vmid}.conf.$source->{new_snap}");
797 } elsif ($dest->{ip
}) {
798 run_cmd
("ssh root\@$dest->{ip} mkdir $VMCONFIG -p");
799 run_cmd
("scp $QEMU_CONF$source->{vmid}.conf root\@$dest->{ip}:$VMCONFIG$source->{vmid}.conf.$source->{new_snap}");
800 } elsif ($source->{ip
}) {
801 run_cmd
("mkdir $VMCONFIG -p");
802 run_cmd
("scp root\@$source->{ip}:$QEMU_CONF$source->{vmid}.conf $VMCONFIG$source->{vmid}.conf.$source->{new_snap}");
805 if ($source->{destroy
}){
807 run_cmd
("ssh root\@$dest->{ip} rm -f $VMCONFIG$source->{vmid}.conf.$source->{old_snap}");
809 run_cmd
("rm -f $VMCONFIG$source->{vmid}.conf.$source->{old_snap}");
816 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
817 my $datestamp = sprintf ( "%04d-%02d-%02d_%02d:%02d:%02d",$year+1900,$mon+1,$mday,$hour,$min,$sec);
823 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
825 my $status_list = sprintf("%-25s%-15s%-10s\n","SOURCE","NAME","STATUS");
827 foreach my $source (sort keys%{$cfg}){
828 foreach my $sync_name (sort keys%{$cfg->{$source}}){
831 my $source_name = $source;
833 $source_name = $cfg->{$source}->{$sync_name}->{source_ip
}.":".$source if $cfg->{$source}->{$sync_name}->{source_ip
};
835 $status = sprintf("%-10s",$cfg->{$source}->{$sync_name}->{status
});
837 $status_list .= sprintf("%-25s%-15s", cut_to_width
($source_name,25), cut_to_width
($sync_name,15));
838 $status_list .= "$status\n";
846 my $command = $ARGV[0];
848 my $commands = {'destroy' => 1,
855 if (!$command || !$commands->{$command}) {
869 my $help_sync = "zfs-zsync sync -dest <string> -source <string> [OPTIONS]\n
870 \twill sync one time\n
872 \t\tthe destination target is like [IP:]<Pool>[/Path]\n
874 \t\tmax sync speed in kBytes/s, default unlimited\n
875 \t-maxsnap\tinteger\n
876 \t\thow much snapshots will be kept before get erased, default 1/n
878 \t\tname of the sync job, if not set it is default.
879 \tIt is only necessary if scheduler allready contains this source.\n
881 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
883 my $help_create = "zfs-zsync create -dest <string> -source <string> [OPTIONS]/n
884 \tCreate a sync Job\n
886 \t\tthe destination target is like [IP]:<Pool>[/Path]\n
887 \t-interval\tinteger\n
888 \t\tthe interval in min in witch the zfs will sync,
889 \t\tdefault is 15 min\n
891 \t\tmax sync speed, default unlimited\n
893 \t\thow much snapshots will be kept before get erased, default 1\n
895 \t\tname of the sync job, if not set it is default\n
897 \t\tif this flag is set it will skip the first sync\n
899 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
901 my $help_destroy = "zfs-zsync destroy -source <string> [OPTIONS]\n
902 \tremove a sync Job from the scheduler\n
904 \t\tname of the sync job, if not set it is default\n
906 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
908 my $help_help = "zfs-zsync help <cmd> [OPTIONS]\n
909 \tGet help about specified command.\n
912 \t-verbose\tboolean\n
913 \t\tVerbose output format.\n";
915 my $help_list = "zfs-zsync list\n
916 \tGet a List of all scheduled Sync Jobs\n";
918 my $help_status = "zfs-zsync status\n
919 \tGet the status of all scheduled Sync Jobs\n";
935 die "$help_destroy\n";
939 die "$help_create\n";
947 die "$help_status\n";
953 my $err = GetOptions
('dest=s' => \
$dest,
954 'source=s' => \
$source,
955 'verbose' => \
$verbose,
956 'interval=i' => \
$interval,
957 'limit=i' => \
$limit,
958 'maxsnap=i' => \
$maxsnap,
963 die "can't parse options\n";
967 $param->{dest
} = $dest;
968 $param->{source
} = $source;
969 $param->{verbose
} = $verbose;
970 $param->{interval
} = $interval;
971 $param->{limit
} = $limit;
972 $param->{maxsnap
} = $maxsnap;
973 $param->{name
} = $name;
974 $param->{skip
} = $skip;
979 die "$help_destroy\n" if !$source;
980 check_target
($source);
985 die "$help_sync\n" if !$source || !$dest;
986 check_target
($source);
992 die "$help_create\n" if !$source || !$dest;
993 check_target
($source);
1007 my $help_command = $ARGV[1];
1008 if ($help_command && $commands->{$help_command}) {
1009 print help
($help_command);
1012 exec("man $PROGNAME");
1022 print("ERROR:\tno command specified\n") if !$help;
1023 print("USAGE:\t$PROGNAME <COMMAND> [ARGS] [OPTIONS]\n");
1024 print("\tpve-zsync help [<cmd>] [OPTIONS]\n\n");
1025 print("\tpve-zsync create -dest <string> -source <string> [OPTIONS]\n");
1026 print("\tpve-zsync destroy -source <string> [OPTIONS]\n");
1027 print("\tpve-zsync list\n");
1028 print("\tpve-zsync status\n");
1029 print("\tpve-zsync sync -dest <string> -source <string> [OPTIONS]\n");
1037 if($target !~ m/(\d+.\d+.\d+.\d+:)?([\w\-\_\/]+)(\
/.+)?/){
1038 print("ERROR:\t$target is not valid.\n\tUse [IP:]<ZFSPool>[/Path]!\n");
1048 pve-zsync - PVE ZFS Replication Manager
1052 zfs-zsync <COMMAND> [ARGS] [OPTIONS]
1054 zfs-zsync help <cmd> [OPTIONS]
1056 Get help about specified command.
1064 Verbose output format.
1066 zfs-zsync create -dest <string> -source <string> [OPTIONS]
1072 the destination target is like [IP]:<Pool>[/Path]
1076 the interval in min in witch the zfs will sync, default is 15 min
1080 max sync speed, default unlimited
1084 how much snapshots will be kept before get erased, default 1
1088 name of the sync job, if not set it is default
1092 if this flag is set it will skip the first sync
1096 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1098 zfs-zsync destroy -source <string> [OPTIONS]
1100 remove a sync Job from the scheduler
1104 name of the sync job, if not set it is default
1108 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1112 Get a List of all scheduled Sync Jobs
1116 Get the status of all scheduled Sync Jobs
1118 zfs-zsync sync -dest <string> -source <string> [OPTIONS]
1124 the destination target is like [IP:]<Pool>[/Path]
1128 max sync speed in kBytes/s, default unlimited
1132 how much snapshots will be kept before get erased, default 1
1136 name of the sync job, if not set it is default.
1137 It is only necessary if scheduler allready contains this source.
1141 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1145 This Tool helps you to sync your VM or directory which stored on ZFS between 2 servers.
1146 This tool also has the capability to add jobs to cron so the sync will be automatically done.
1148 =head2 PVE ZFS Storage sync Tool
1150 This Tool can get remote pool on other PVE or send Pool to others ZFS machines
1154 add sync job from local VM to remote ZFS Server
1155 zfs-zsync -source=100 -dest=192.168.1.2:zfspool
1157 =head1 IMPORTANT FILES
1159 Where the cron jobs are stored /etc/cron.d/pve-zsync
1160 Where the VM config get copied on the destination machine /var/pve-zsync
1161 Where the config is stored /var/pve-zsync
1163 Copyright (C) 2007-2015 Proxmox Server Solutions GmbH
1165 This program is free software: you can redistribute it and/or modify it
1166 under the terms of the GNU Affero General Public License as published
1167 by the Free Software Foundation, either version 3 of the License, or
1168 (at your option) any later version.
1170 This program is distributed in the hope that it will be useful, but
1171 WITHOUT ANY WARRANTY; without even the implied warranty of
1172 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1173 Affero General Public License for more details.
1175 You should have received a copy of the GNU Affero General Public
1176 License along with this program. If not, see
1177 <http://www.gnu.org/licenses/>.