]>
git.proxmox.com Git - pve-zsync.git/blob - pve-zsync
47e7b590f5a63e0ea7761fb80b7b2b853fa33387
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 if ($source->{vmid
} && $cfg->{$source->{vmid
}}->{$name}->{locked
}){
71 return "active" if $cfg->{$source->{vmid
}}->{$name}->{locked
} eq 'yes';
72 return "exist" if $cfg->{$source->{vmid
}}->{$name}->{locked
} eq 'no';
73 } elsif ($cfg->{$source->{abs_path
}}->{$name}->{locked
}) {
74 return "active" if $cfg->{$source->{abs_path
}}->{$name}->{locked
} eq 'yes';
75 return "exist" if $cfg->{$source->{abs_path
}}->{$name}->{locked
} eq 'no';
82 sub check_pool_exsits
{
86 $cmd = "ssh root\@$ip " if $ip;
87 $cmd .= "zfs list $pool -H";
101 open(my $fh, ">", "$CONFIG_PATH$CONFIG")
102 or die "cannot open >$CONFIG_PATH$CONFIG: $!\n";
104 my $text = decode_config
($cfg);
111 sub read_from_config
{
113 unless(-e
"$CONFIG_PATH$CONFIG") {
117 open(my $fh, "<", "$CONFIG_PATH$CONFIG")
118 or die "cannot open > $CONFIG_PATH$CONFIG: $!\n";
128 my $cfg = encode_config
($text);
136 foreach my $source (sort keys%{$cfg}){
137 foreach my $sync_name (sort keys%{$cfg->{$source}}){
138 $raw .= "$source: $sync_name\n";
139 foreach my $parameter (sort keys%{$cfg->{$source}->{$sync_name}}){
140 $raw .= "\t$parameter: $cfg->{$source}->{$sync_name}->{$parameter}\n";
154 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
157 next if $line =~ m/^\#/;
158 next if $line =~ m/^\s*$/;
160 if ($line =~ m/^(\t| )(\w+): (.+)/){
164 if ($par eq 'source_pool') {
165 $cfg->{$source}->{$sync_name}->{$par} = $value;
166 die "error in Config: SourcePool value doubled\n" if ($check & 1);
168 } elsif ($par eq 'source_ip') {
169 $cfg->{$source}->{$sync_name}->{$par} = $value;
170 die "error in Config: SourceIP value doubled\n" if ($check & 2);
172 } elsif ($par eq 'locked') {
173 $cfg->{$source}->{$sync_name}->{$par} = $value;
174 die "error in Config: Locked value doubled\n" if ($check & 4);
176 } elsif ($par eq 'method') {
177 $cfg -> {$source}->{$sync_name}->{$par} = $value;
178 die "error in Config: Method value doubled\n" if ($check & 8);
180 } elsif ($par eq 'interval') {
181 $cfg -> {$source}->{$sync_name}->{$par} = $value;
182 die "error in Config: Iterval value doubled\n" if ($check & 16);
184 } elsif ($par eq 'limit') {
185 $cfg -> {$source}->{$sync_name}->{$par} = $value;
186 die "error in Config: Limit value doubled\n" if ($check & 32);
188 } elsif ($par eq 'dest_pool') {
189 $cfg -> {$source}->{$sync_name}->{$par} = $value;
190 die "error in Config: DestPool value doubled\n" if ($check & 64);
192 } elsif ($par eq 'dest_ip') {
193 $cfg -> {$source}->{$sync_name}->{$par} = $value;
194 die "error in Config: DestIp value doubled\n" if ($check & 128);
196 } elsif ($par eq 'dest_path') {
197 $cfg -> {$source}->{$sync_name}->{$par} = $value;
198 die "error in Config: DestPath value doubled\n" if ($check & 256);
200 } elsif ($par eq 'source_path') {
201 $cfg -> {$source}->{$sync_name}->{$par} = $value;
202 die "error in Config: SourcePath value doubled\n" if ($check & 512);
204 } elsif ($par eq 'vmid') {
205 $cfg -> {$source}->{$sync_name}->{$par} = $value;
206 die "error in Config: Vmid value doubled\n" if ($check & 1024);
208 } elsif ($par =~ 'lsync') {
209 $cfg->{$source}->{$sync_name}->{$par} = $value;
210 die "error in Config: lsync value doubled\n" if ($check & 2048);
212 } elsif ($par =~ 'maxsnap') {
213 $cfg->{$source}->{$sync_name}->{$par} = $value;
214 die "error in Config: maxsnap value doubled\n" if ($check & 4096);
217 die "error in Config\n";
219 } elsif ($line =~ m/^((\d+.\d+.\d+.\d+):)?([\w\-\_\/]+): (.+){0,1}/){
221 $sync_name = $4 ?
$4 : 'default' ;
222 $cfg->{$source}->{$sync_name} = undef;
223 $cfg->{$source}->{$sync_name}->{source_ip
} = $2 if $2;
233 if ($text =~ m/^((\d+.\d+.\d+.\d+):)?((\w+)\/?
)([\w\
/\-\_]*)?$/) {
235 die "Input not valid\n" if !$3;
243 if ($tmp =~ m/^(\d\d\d+)$/){
244 $target->{vmid
} = $tmp;
246 $target->{pool
} = $4;
249 $target->{path
} = "\/$5";
252 $target->{abs_path
} = $abs_path;
257 die "Input not valid\n";
262 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
264 my $list = sprintf("%-25s%-15s%-7s%-20s%-10s%-5s\n" , "SOURCE", "NAME", "ACTIVE", "LAST SYNC", "INTERVAL", "TYPE");
266 foreach my $source (sort keys%{$cfg}){
267 foreach my $sync_name (sort keys%{$cfg->{$source}}){
268 my $source_name = $source;
269 $source_name = $cfg->{$source}->{$sync_name}->{source_ip
}.":".$source if $cfg->{$source}->{$sync_name}->{source_ip
};
270 $list .= sprintf("%-25s%-15s", cut_to_width
($source_name,25), cut_to_width
($sync_name,15));
271 $list .= sprintf("%-7s",$cfg->{$source}->{$sync_name}->{locked
});
272 $list .= sprintf("%-20s",$cfg->{$source}->{$sync_name}->{lsync
});
273 $list .= sprintf("%-10s",$cfg->{$source}->{$sync_name}->{interval
});
274 $list .= sprintf("%-5s\n",$cfg->{$source}->{$sync_name}->{method});
285 $cmd = "ssh root\@$target->{ip} " if ($target->{ip
});
286 $cmd .= "qm status $target->{vmid}";
288 my $res = run_cmd
($cmd);
290 return 1 if ($res =~ m/^status.*$/);
297 my $cfg = read_from_config
;
301 my $name = $param->{name
} ?
$param->{name
} : "default";
302 my $interval = $param->{interval
} ?
$param->{interval
} : 15;
304 my $source = parse_target
($param->{source
});
305 my $dest = parse_target
($param->{dest
});
307 $vm->{$name}->{dest_pool
} = $dest->{pool
};
308 $vm->{$name}->{dest_ip
} = $dest->{ip
} if $dest->{ip
};
309 $vm->{$name}->{dest_path
} = $dest->{path
} if $dest->{path
};
311 $param->{method} = "local" if !$dest->{ip
} && !$source->{ip
};
312 $vm->{$name}->{locked
} = "no";
313 $vm->{$name}->{interval
} = $interval;
314 $vm->{$name}->{method} = $param->{method} ?
$param->{method} : "ssh";
315 $vm->{$name}->{limit
} = $param->{limit
} if $param->{limit
};
316 $vm->{$name}->{maxsnap
} = $param->{maxsnap
} if $param->{maxsnap
};
318 if ( my $ip = $vm->{$name}->{dest_ip
} ) {
319 run_cmd
("ssh-copy-id -i /root/.ssh/id_rsa.pub root\@$ip");
322 if ( my $ip = $source->{ip
} ) {
323 run_cmd
("ssh-copy-id -i /root/.ssh/id_rsa.pub root\@$ip");
326 die "Pool $dest->{abs_path} does not exists\n" if check_pool_exsits
($dest->{abs_path
}, $dest->{ip
});
328 my $check = check_pool_exsits
($source->{abs_path
}, $source->{ip
}) if !$source->{vmid
} && $source->{abs_path
};
330 die "Pool $source->{abs_path} does not exists\n" if undef($check);
333 my ($vm, $name) = @_;
336 if ($vm->{$name}->{vmid
}) {
337 $source = "$vm->{$name}->{source_ip}:" if $vm->{$name}->{source_ip
};
338 $source .= $vm->{$name}->{vmid
};
340 $source = $vm->{$name}->{source_pool
};
341 $source .= $vm->{$name}->{source_path
} if $vm->{$name}->{source_path
};
343 die "Config already exists\n" if $cfg->{$source}->{$name};
347 $cfg->{$source}->{$name} = $vm->{$name};
349 write_to_config
($cfg);
352 if ($source->{vmid
}) {
353 die "VM $source->{vmid} doesn't exist\n" if !vm_exists
($source);
354 my $disks = get_disks
($source);
355 $vm->{$name}->{vmid
} = $source->{vmid
};
356 $vm->{$name}->{lsync
} = 0;
357 $vm->{$name}->{source_ip
} = $source->{ip
} if $source->{ip
};
359 &$add_job($vm, $name);
362 $vm->{$name}->{source_pool
} = $source->{pool
};
363 $vm->{$name}->{source_ip
} = $source->{ip
} if $source->{ip
};
364 $vm->{$name}->{source_path
} = $source->{path
} if $source->{path
};
365 $vm->{$name}->{lsync
} = 0;
367 &$add_job($vm, $name);
370 eval {sync
($param) if !$param->{skip
};};
380 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
381 my $name = $param->{name
} ?
$param->{name
} : "default";
383 my $source = parse_target
($param->{source
});
385 my $delete_cron = sub {
386 my ($path, $name, $cfg) = @_;
388 die "Source does not exist!\n" unless $cfg->{$path} ;
390 die "Sync Name does not exist!\n" unless $cfg->{$path}->{$name};
392 delete $cfg->{$path}->{$name};
394 delete $cfg->{$path} if keys%{$cfg->{$path}} == 0;
396 write_to_config
($cfg);
398 cron_del
($path, $name);
402 if ($source->{vmid
}) {
403 my $path = $source->{vmid
};
405 &$delete_cron($path, $name, $cfg)
409 my $path = $source->{pool
};
410 $path .= $source->{path
} if $source->{path
};
412 &$delete_cron($path, $name, $cfg);
419 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
421 my $name = $param->{name
} ?
$param->{name
} : "default";
422 my $max_snap = $param->{maxsnap
} ?
$param->{maxsnap
} : 1;
423 my $method = $param->{method} ?
$param->{method} : "ssh";
425 my $dest = parse_target
($param->{dest
});
426 my $source = parse_target
($param->{source
});
428 my $sync_path = sub {
429 my ($source, $name, $cfg, $max_snap, $dest, $method) = @_;
431 ($source->{old_snap
},$source->{last_snap
}) = snapshot_get
($source, $dest, $max_snap, $name);
433 my $job_status = check_config
($source, $name, $cfg) if $cfg;
434 die "VM syncing at the moment!\n" if ($job_status && $job_status eq "active");
436 if ($job_status && $job_status eq "exist") {
437 my $conf_name = $source->{abs_path
};
438 $conf_name = $source->{vmid
} if $source->{vmid
};
439 $cfg->{$conf_name}->{$name}->{locked
} = "yes";
440 write_to_config
($cfg);
443 my $date = snapshot_add
($source, $dest, $name);
445 send_image
($source, $dest, $method, $param->{verbose
}, $param->{limit
});
447 snapshot_destroy
($source, $dest, $method, $source->{old_snap
}) if ($source->{destroy
} && $source->{old_snap
});
450 if ($job_status && $job_status eq "exist") {
451 my $conf_name = $source->{abs_path
};
452 $conf_name = $source->{vmid
} if $source->{vmid
};
453 $cfg->{$conf_name}->{$name}->{locked
} = "no";
454 $cfg->{$conf_name}->{$name}->{lsync
} = $date;
455 write_to_config
($cfg);
459 $param->{method} = "ssh" if !$param->{method};
461 if ($source->{vmid
}) {
462 die "VM $source->{vmid} doesn't exist\n" if !vm_exists
($source);
463 my $disks = get_disks
($source);
465 foreach my $disk (keys %{$disks}) {
466 $source->{abs_path
} = $disks->{$disk}->{pool
};
467 $source->{abs_path
} .= "\/$disks->{$disk}->{path}" if $disks->{$disk}->{path
};
469 $source->{pool
} = $disks->{$disk}->{pool
};
470 $source->{path
} = "\/$disks->{$disk}->{path}";
472 &$sync_path($source, $name, $cfg, $max_snap, $dest, $method);
475 &$sync_path($source, $name, $cfg, $max_snap, $dest, $method);
480 my ($source, $dest, $max_snap, $name) = @_;
482 my $cmd = "zfs list -r -t snapshot -Ho name, -S creation ";
484 $cmd .= $source->{abs_path
};
485 $cmd = "ssh root\@$source->{ip} ".$cmd if $source->{ip
};
487 my $raw = run_cmd
($cmd);
490 my $last_snap = undef;
492 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
494 $last_snap = $line if $index == 1;
495 if ($index == $max_snap) {
496 $source->{destroy
} = 1;
502 $line =~ m/^(.+)\@(rep_$name\_.+)(\n|$)/;
503 return ($2, $last_snap) if $2;
509 my ($source, $dest, $name) = @_;
511 my $date = get_date
();
513 my $snap_name = "rep_$name\_".$date;
515 $source->{new_snap
} = $snap_name;
517 my $path = $source->{abs_path
}."\@".$snap_name;
519 my $cmd = "zfs snapshot $path";
520 $cmd = "ssh root\@$source->{ip} ".$cmd if $source->{ip
};
527 snapshot_destroy
($source, $dest, 'ssh', $snap_name);
536 open(my $fh, '>>', "$CRONJOBS")
537 or die "Could not open file: $!\n";
539 foreach my $name (keys%{$vm}){
540 my $text = "*/$vm->{$name}->{interval} * * * * root ";
541 $text .= "$PATH$PROGNAME sync";
542 $text .= " -source ";
543 if ($vm->{$name}->{vmid
}) {
544 $text .= "$vm->{$name}->{source_ip}:" if $vm->{$name}->{source_ip
};
545 $text .= "$vm->{$name}->{vmid} ";
547 $text .= "$vm->{$name}->{source_ip}:" if $vm->{$name}->{source_ip
};
548 $text .= "$vm->{$name}->{source_pool}";
549 $text .= "$vm->{$name}->{source_path}" if $vm->{$name}->{source_path
};
552 $text .= "$vm->{$name}->{dest_ip}:" if $vm->{$name}->{dest_ip
};
553 $text .= "$vm->{$name}->{dest_pool}";
554 $text .= "$vm->{$name}->{dest_path}" if $vm->{$name}->{dest_path
};
555 $text .= " -name $name ";
556 $text .= " -limit $vm->{$name}->{limit}" if $vm->{$name}->{limit
};
557 $text .= " -maxsnap $vm->{$name}->{maxsnap}" if $vm->{$name}->{maxsnap
};
565 my ($source, $name) = @_;
567 open(my $fh, '<', "$CRONJOBS")
568 or die "Could not open file: $!\n";
575 while ($text && $text =~ s/^(.*?)(\n|$)//) {
577 if ($line !~ m/.*$PROGNAME.*$source.*$name.*/){
581 open($fh, '>', "$CRONJOBS")
582 or die "Could not open file: $!\n";
591 $cmd = "ssh root\@$target->{ip} " if $target->{ip
};
592 $cmd .= "qm config $target->{vmid}";
594 my $res = run_cmd
($cmd);
596 my $disks = parse_disks
($res, $target->{ip
});
603 print "Start CMD\n" if $DEBUG;
604 print Dumper
$cmd if $DEBUG;
605 my $output = `$cmd 2>&1`;
607 die $output if 0 != $?;
610 print Dumper
$output if $DEBUG;
611 print "END CMD\n" if $DEBUG;
616 my ($text, $ip) = @_;
622 $cmd .= "ssh root\@$ip " if $ip;
623 $cmd .= "pvesm zfsscan";
624 my $zfs_pools = run_cmd
($cmd);
625 while ($text && $text =~ s/^(.*?)(\n|$)//) {
629 if($line =~ m/^(virtio\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
632 } elsif($line =~ m/^(ide\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
635 } elsif($line =~ m/^(scsi\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
638 } elsif($line =~ m/^(sata\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
643 if($disk && $disk ne "none" && $disk !~ m/cdrom/ ) {
645 $cmd .= "ssh root\@$ip " if $ip;
646 $cmd .= "pvesm path $stor$disk";
647 my $path = run_cmd
($cmd);
649 if ($path =~ m/^\/dev\
/zvol\/(\w
+).*(\
/$disk)$/){
651 $disks->{$num}->{pool
} = $1;
652 $disks->{$num}->{path
} = $disk;
656 die "ERROR: in path\n";
660 die "disk is not on ZFS Storage\n" if $num == 0;
664 sub snapshot_destroy
{
665 my ($source, $dest, $method, $snap) = @_;
667 my $zfscmd = "zfs destroy ";
668 my $name = "$source->{path}\@$snap";
671 if($source->{ip
} && $method eq 'ssh'){
672 run_cmd
("ssh root\@$source->{ip} $zfscmd $source->{pool}$name");
674 run_cmd
("$zfscmd $source->{pool}$name");
681 my $ssh = $dest->{ip
} ?
"ssh root\@$dest->{ip}" : "";
684 $path ="$dest->{path}" if $dest->{path
};
686 my @dir = split(/\//, $source->{path
});
688 run_cmd
("$ssh $zfscmd $dest->{pool}$path\/$dir[@dir-1]\@$snap ");
697 my ($source ,$dest, $method) = @_;
700 $cmd = "ssh root\@$dest->{ip} " if $dest->{ip
};
701 $cmd .= "zfs list -rt snapshot -Ho name $dest->{pool}";
702 $cmd .= "$dest->{path}" if $dest->{path
};
703 my @dir = split(/\//, $source->{path
});
704 $cmd .= "\/$dir[@dir-1]\@$source->{old_snap}";
707 eval {$text =run_cmd
($cmd);};
713 while ($text && $text =~ s/^(.*?)(\n|$)//) {
715 return 1 if $line =~ m/^.*$source->{old_snap}$/;
720 my ($source, $dest, $method, $verbose, $limit) = @_;
724 $cmd .= "ssh root\@$source->{ip} " if $source->{ip
};
726 $cmd .= "-v " if $verbose;
728 if($source->{last_snap
} && snapshot_exist
($source ,$dest, $method)) {
729 $cmd .= "-i $source->{abs_path}\@$source->{old_snap} $source->{abs_path}\@$source->{new_snap} ";
731 $cmd .= "$source->{abs_path}\@$source->{new_snap} ";
735 my $bwl = $limit*1024;
736 $cmd .= "| cstream -t $bwl";
739 $cmd .= "ssh root\@$dest->{ip} " if $dest->{ip
};
740 $cmd .= "zfs recv $dest->{pool}";
741 $cmd .= "$dest->{path}" if $dest->{path
};
743 my @dir = split(/\//,$source->{path
});
744 $cmd .= "\/$dir[@dir-1]\@$source->{new_snap}";
751 snapshot_destroy
($source, undef, $method, $source->{new_snap
});
755 if ($source->{vmid
}) {
756 if ($method eq "ssh") {
757 send_config
($source, $dest,'ssh');
764 my ($source, $dest, $method) = @_;
766 if ($method eq 'ssh'){
767 if ($dest->{ip
} && $source->{ip
}) {
768 run_cmd
("ssh root\@$dest->{ip} mkdir $VMCONFIG -p");
769 run_cmd
("scp root\@$source->{ip}:$QEMU_CONF$source->{vmid}.conf root\@$dest->{ip}:$VMCONFIG$source->{vmid}.conf.$source->{new_snap}");
770 } elsif ($dest->{ip
}) {
771 run_cmd
("ssh root\@$dest->{ip} mkdir $VMCONFIG -p");
772 run_cmd
("scp $QEMU_CONF$source->{vmid}.conf root\@$dest->{ip}:$VMCONFIG$source->{vmid}.conf.$source->{new_snap}");
773 } elsif ($source->{ip
}) {
774 run_cmd
("mkdir $VMCONFIG -p");
775 run_cmd
("scp root\@$source->{ip}:$QEMU_CONF$source->{vmid}.conf $VMCONFIG$source->{vmid}.conf.$source->{new_snap}");
778 if ($source->{destroy
}){
780 run_cmd
("ssh root\@$dest->{ip} rm -f $VMCONFIG$source->{vmid}.conf.$source->{old_snap}");
782 run_cmd
("rm -f $VMCONFIG$source->{vmid}.conf.$source->{old_snap}");
789 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
790 my $datestamp = sprintf ( "%04d-%02d-%02d_%02d:%02d:%02d",$year+1900,$mon+1,$mday,$hour,$min,$sec);
796 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
798 my $status_list = sprintf("%-25s%-15s%-10s\n","SOURCE","NAME","STATUS");
800 foreach my $source (sort keys%{$cfg}){
801 foreach my $sync_name (sort keys%{$cfg->{$source}}){
804 my $source_name = $source;
806 $source_name = $cfg->{$source}->{$sync_name}->{source_ip
}.":".$source if $cfg->{$source}->{$sync_name}->{source_ip
};
808 if ($cfg->{$source}->{$sync_name}->{locked
} eq 'no'){
809 $status = sprintf("%-10s","OK");
810 } elsif ($cfg->{$source}->{$sync_name}->{locked
} eq 'yes' &&
811 $cfg->{$source}->{$sync_name}->{failure
}) {
812 $status = sprintf("%-10s","sync error");
814 $status = sprintf("%-10s","syncing");
817 $status_list .= sprintf("%-25s%-15s", cut_to_width
($source_name,25), cut_to_width
($sync_name,15));
818 $status_list .= "$status\n";
826 my $command = $ARGV[0];
828 my $commands = {'destroy' => 1,
835 if (!$command || !$commands->{$command}) {
849 my $help_sync = "zfs-zsync sync -dest <string> -source <string> [OPTIONS]\n
850 \twill sync one time\n
852 \t\tthe destination target is like [IP:]<Pool>[/Path]\n
854 \t\tmax sync speed in kBytes/s, default unlimited\n
855 \t-maxsnap\tinteger\n
856 \t\thow much snapshots will be kept before get erased, default 1/n
858 \t\tname of the sync job, if not set it is default.
859 \tIt is only necessary if scheduler allready contains this source.\n
861 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
863 my $help_create = "zfs-zsync create -dest <string> -source <string> [OPTIONS]/n
864 \tCreate a sync Job\n
866 \t\tthe destination target is like [IP]:<Pool>[/Path]\n
867 \t-interval\tinteger\n
868 \t\tthe interval in min in witch the zfs will sync,
869 \t\tdefault is 15 min\n
871 \t\tmax sync speed, default unlimited\n
873 \t\thow much snapshots will be kept before get erased, default 1\n
875 \t\tname of the sync job, if not set it is default\n
877 \t\tif this flag is set it will skip the first sync\n
879 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
881 my $help_destroy = "zfs-zsync destroy -source <string> [OPTIONS]\n
882 \tremove a sync Job from the scheduler\n
884 \t\tname of the sync job, if not set it is default\n
886 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
888 my $help_help = "zfs-zsync help <cmd> [OPTIONS]\n
889 \tGet help about specified command.\n
892 \t-verbose\tboolean\n
893 \t\tVerbose output format.\n";
895 my $help_list = "zfs-zsync list\n
896 \tGet a List of all scheduled Sync Jobs\n";
898 my $help_status = "zfs-zsync status\n
899 \tGet the status of all scheduled Sync Jobs\n";
915 die "$help_destroy\n";
919 die "$help_create\n";
927 die "$help_status\n";
933 my $err = GetOptions
('dest=s' => \
$dest,
934 'source=s' => \
$source,
935 'verbose' => \
$verbose,
936 'interval=i' => \
$interval,
937 'limit=i' => \
$limit,
938 'maxsnap=i' => \
$maxsnap,
943 die "can't parse options\n";
947 $param->{dest
} = $dest;
948 $param->{source
} = $source;
949 $param->{verbose
} = $verbose;
950 $param->{interval
} = $interval;
951 $param->{limit
} = $limit;
952 $param->{maxsnap
} = $maxsnap;
953 $param->{name
} = $name;
954 $param->{skip
} = $skip;
959 die "$help_destroy\n" if !$source;
960 check_target
($source);
965 die "$help_sync\n" if !$source || !$dest;
966 check_target
($source);
972 die "$help_create\n" if !$source || !$dest;
973 check_target
($source);
987 my $help_command = $ARGV[1];
988 if ($help_command && $commands->{$help_command}) {
989 print help
($help_command);
992 exec("man $PROGNAME");
1002 print("ERROR:\tno command specified\n") if !$help;
1003 print("USAGE:\t$PROGNAME <COMMAND> [ARGS] [OPTIONS]\n");
1004 print("\tpve-zsync help [<cmd>] [OPTIONS]\n\n");
1005 print("\tpve-zsync create -dest <string> -source <string> [OPTIONS]\n");
1006 print("\tpve-zsync destroy -source <string> [OPTIONS]\n");
1007 print("\tpve-zsync list\n");
1008 print("\tpve-zsync status\n");
1009 print("\tpve-zsync sync -dest <string> -source <string> [OPTIONS]\n");
1017 if($target !~ m/(\d+.\d+.\d+.\d+:)?([\w\-\_\/]+)(\
/.+)?/){
1018 print("ERROR:\t$target is not valid.\n\tUse [IP:]<ZFSPool>[/Path]!\n");
1028 pve-zsync - PVE ZFS Replication Manager
1032 zfs-zsync <COMMAND> [ARGS] [OPTIONS]
1034 zfs-zsync help <cmd> [OPTIONS]
1036 Get help about specified command.
1044 Verbose output format.
1046 zfs-zsync create -dest <string> -source <string> [OPTIONS]
1052 the destination target is like [IP]:<Pool>[/Path]
1056 the interval in min in witch the zfs will sync, default is 15 min
1060 max sync speed, default unlimited
1064 how much snapshots will be kept before get erased, default 1
1068 name of the sync job, if not set it is default
1072 if this flag is set it will skip the first sync
1076 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1078 zfs-zsync destroy -source <string> [OPTIONS]
1080 remove a sync Job from the scheduler
1084 name of the sync job, if not set it is default
1088 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1092 Get a List of all scheduled Sync Jobs
1096 Get the status of all scheduled Sync Jobs
1098 zfs-zsync sync -dest <string> -source <string> [OPTIONS]
1104 the destination target is like [IP:]<Pool>[/Path]
1108 max sync speed in kBytes/s, default unlimited
1112 how much snapshots will be kept before get erased, default 1
1116 name of the sync job, if not set it is default.
1117 It is only necessary if scheduler allready contains this source.
1121 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1125 This Tool helps you to sync your VM or directory which stored on ZFS between 2 servers.
1126 This tool also has the capability to add jobs to cron so the sync will be automatically done.
1128 =head2 PVE ZFS Storage sync Tool
1130 This Tool can get remote pool on other PVE or send Pool to others ZFS machines
1134 add sync job from local VM to remote ZFS Server
1135 zfs-zsync -source=100 -dest=192.168.1.2:zfspool
1137 =head1 IMPORTANT FILES
1139 Where the cron jobs are stored /etc/cron.d/pve-zsync
1140 Where the VM config get copied on the destination machine /var/pve-zsync
1141 Where the config is stored /var/pve-zsync
1143 Copyright (C) 2007-2015 Proxmox Server Solutions GmbH
1145 This program is free software: you can redistribute it and/or modify it
1146 under the terms of the GNU Affero General Public License as published
1147 by the Free Software Foundation, either version 3 of the License, or
1148 (at your option) any later version.
1150 This program is distributed in the hope that it will be useful, but
1151 WITHOUT ANY WARRANTY; without even the implied warranty of
1152 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1153 Affero General Public License for more details.
1155 You should have received a copy of the GNU Affero General Public
1156 License along with this program. If not, see
1157 <http://www.gnu.org/licenses/>.