]>
git.proxmox.com Git - pve-zsync.git/blob - pve-zsync
b187e6cdb6db11cffbeeeeb96ec30ce8cad25486
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";
126 my $cfg = encode_config
($text);
134 foreach my $source (keys%{$cfg}){
135 foreach my $sync_name (keys%{$cfg->{$source}}){
136 $raw .= "$source: $sync_name\n";
137 foreach my $parameter (keys%{$cfg->{$source}->{$sync_name}}){
138 $raw .= "\t$parameter: $cfg->{$source}->{$sync_name}->{$parameter}\n";
152 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
155 next if $line =~ m/^\#/;
156 next if $line =~ m/^\s*$/;
158 if ($line =~ m/^(\t| )(\w+): (.+)/){
162 if ($par eq 'source_pool') {
163 $cfg->{$source}->{$sync_name}->{$par} = $value;
164 die "error in Config: SourcePool value doubled\n" if ($check & 1);
166 } elsif ($par eq 'source_ip') {
167 $cfg->{$source}->{$sync_name}->{$par} = $value;
168 die "error in Config: SourceIP value doubled\n" if ($check & 2);
170 } elsif ($par eq 'locked') {
171 $cfg->{$source}->{$sync_name}->{$par} = $value;
172 die "error in Config: Locked value doubled\n" if ($check & 4);
174 } elsif ($par eq 'method') {
175 $cfg -> {$source}->{$sync_name}->{$par} = $value;
176 die "error in Config: Method value doubled\n" if ($check & 8);
178 } elsif ($par eq 'interval') {
179 $cfg -> {$source}->{$sync_name}->{$par} = $value;
180 die "error in Config: Iterval value doubled\n" if ($check & 16);
182 } elsif ($par eq 'limit') {
183 $cfg -> {$source}->{$sync_name}->{$par} = $value;
184 die "error in Config: Limit value doubled\n" if ($check & 32);
186 } elsif ($par eq 'dest_pool') {
187 $cfg -> {$source}->{$sync_name}->{$par} = $value;
188 die "error in Config: DestPool value doubled\n" if ($check & 64);
190 } elsif ($par eq 'dest_ip') {
191 $cfg -> {$source}->{$sync_name}->{$par} = $value;
192 die "error in Config: DestIp value doubled\n" if ($check & 128);
194 } elsif ($par eq 'dest_path') {
195 $cfg -> {$source}->{$sync_name}->{$par} = $value;
196 die "error in Config: DestPath value doubled\n" if ($check & 256);
198 } elsif ($par eq 'source_path') {
199 $cfg -> {$source}->{$sync_name}->{$par} = $value;
200 die "error in Config: SourcePath value doubled\n" if ($check & 512);
202 } elsif ($par eq 'vmid') {
203 $cfg -> {$source}->{$sync_name}->{$par} = $value;
204 die "error in Config: Vmid value doubled\n" if ($check & 1024);
206 } elsif ($par =~ 'lsync') {
207 $cfg->{$source}->{$sync_name}->{$par} = $value;
208 die "error in Config: lsync value doubled\n" if ($check & 2048);
210 } elsif ($par =~ 'maxsnap') {
211 $cfg->{$source}->{$sync_name}->{$par} = $value;
212 die "error in Config: maxsnap value doubled\n" if ($check & 4096);
215 die "error in Config\n";
217 } elsif ($line =~ m/^((\d+.\d+.\d+.\d+):)?([\w\-\_\/]+): (.+){0,1}/){
219 $sync_name = $4 ?
$4 : 'default' ;
220 $cfg->{$source}->{$sync_name} = undef;
221 $cfg->{$source}->{$sync_name}->{source_ip
} = $2 if $2;
231 if ($text =~ m/^((\d+.\d+.\d+.\d+):)?((\w+)\/?
)([\w\
/\-\_]*)?$/) {
233 die "Input not valid\n" if !$3;
241 if ($tmp =~ m/^(\d\d\d+)$/){
242 $target->{vmid
} = $tmp;
244 $target->{pool
} = $4;
247 $target->{path
} = "\/$5";
250 $target->{abs_path
} = $abs_path;
255 die "Input not valid\n";
260 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
262 my $list = sprintf("%-25s%-15s%-7s%-20s%-10s%-5s\n" , "SOURCE", "NAME", "ACTIVE", "LAST SYNC", "INTERVAL", "TYPE");
264 foreach my $source (keys%{$cfg}){
265 foreach my $sync_name (keys%{$cfg->{$source}}){
266 my $source_name = $source;
267 $source_name = $cfg->{$source}->{$sync_name}->{source_ip
}.":".$source if $cfg->{$source}->{$sync_name}->{source_ip
};
268 $list .= sprintf("%-25s%-15s", cut_to_width
($source_name,25), cut_to_width
($sync_name,15));
269 $list .= sprintf("%-7s",$cfg->{$source}->{$sync_name}->{locked
});
270 $list .= sprintf("%-20s",$cfg->{$source}->{$sync_name}->{lsync
});
271 $list .= sprintf("%-10s",$cfg->{$source}->{$sync_name}->{interval
});
272 $list .= sprintf("%-5s\n",$cfg->{$source}->{$sync_name}->{method});
283 $cmd = "ssh root\@$target->{ip} " if ($target->{ip
});
284 $cmd .= "qm status $target->{vmid}";
286 my $res = run_cmd
($cmd);
288 return 1 if ($res =~ m/^status.*$/);
295 my $cfg = read_from_config
;
299 my $name = $param->{name
} ?
$param->{name
} : "default";
300 my $interval = $param->{interval
} ?
$param->{interval
} : 15;
302 my $source = parse_target
($param->{source
});
303 my $dest = parse_target
($param->{dest
});
305 $vm->{$name}->{dest_pool
} = $dest->{pool
};
306 $vm->{$name}->{dest_ip
} = $dest->{ip
} if $dest->{ip
};
307 $vm->{$name}->{dest_path
} = $dest->{path
} if $dest->{path
};
309 $param->{method} = "local" if !$dest->{ip
} && !$source->{ip
};
310 $vm->{$name}->{locked
} = "no";
311 $vm->{$name}->{interval
} = $interval;
312 $vm->{$name}->{method} = $param->{method} ?
$param->{method} : "ssh";
313 $vm->{$name}->{limit
} = $param->{limit
} if $param->{limit
};
314 $vm->{$name}->{maxsnap
} = $param->{maxsnap
} if $param->{maxsnap
};
316 if ( my $ip = $vm->{$name}->{dest_ip
} ) {
317 run_cmd
("ssh-copy-id -i /root/.ssh/id_rsa.pub root\@$ip");
320 if ( my $ip = $source->{ip
} ) {
321 run_cmd
("ssh-copy-id -i /root/.ssh/id_rsa.pub root\@$ip");
324 die "Pool $dest->{abs_path} does not exists\n" if check_pool_exsits
($dest->{abs_path
}, $dest->{ip
});
326 my $check = check_pool_exsits
($source->{abs_path
}, $source->{ip
}) if !$source->{vmid
} && $source->{abs_path
};
328 die "Pool $source->{abs_path} does not exists\n" if undef($check);
331 my ($vm, $name) = @_;
334 if ($vm->{$name}->{vmid
}) {
335 $source = "$vm->{$name}->{source_ip}:" if $vm->{$name}->{source_ip
};
336 $source .= $vm->{$name}->{vmid
};
338 $source = $vm->{$name}->{source_pool
};
339 $source .= $vm->{$name}->{source_path
} if $vm->{$name}->{source_path
};
341 die "Config already exists\n" if $cfg->{$source}->{$name};
345 $cfg->{$source}->{$name} = $vm->{$name};
347 write_to_config
($cfg);
350 if ($source->{vmid
}) {
351 die "VM $source->{vmid} doesn't exist\n" if !vm_exists
($source);
352 my $disks = get_disks
($source);
353 $vm->{$name}->{vmid
} = $source->{vmid
};
354 $vm->{$name}->{lsync
} = 0;
355 $vm->{$name}->{source_ip
} = $source->{ip
} if $source->{ip
};
357 &$add_job($vm, $name);
360 $vm->{$name}->{source_pool
} = $source->{pool
};
361 $vm->{$name}->{source_ip
} = $source->{ip
} if $source->{ip
};
362 $vm->{$name}->{source_path
} = $source->{path
} if $source->{path
};
363 $vm->{$name}->{lsync
} = 0;
365 &$add_job($vm, $name);
368 sync
($param) if !$param->{skip
};
374 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
375 my $name = $param->{name
} ?
$param->{name
} : "default";
377 my $source = parse_target
($param->{source
});
379 my $delete_cron = sub {
380 my ($path, $name, $cfg) = @_;
382 die "Source does not exist!\n" unless $cfg->{$path} ;
384 die "Sync Name does not exist!\n" unless $cfg->{$path}->{$name};
386 delete $cfg->{$path}->{$name};
388 delete $cfg->{$path} if keys%{$cfg->{$path}} == 0;
390 write_to_config
($cfg);
392 cron_del
($path, $name);
396 if ($source->{vmid
}) {
397 my $path = $source->{vmid
};
399 &$delete_cron($path, $name, $cfg)
403 my $path = $source->{pool
};
404 $path .= $source->{path
} if $source->{path
};
406 &$delete_cron($path, $name, $cfg);
413 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
415 my $name = $param->{name
} ?
$param->{name
} : "default";
416 my $max_snap = $param->{maxsnap
} ?
$param->{maxsnap
} : 1;
417 my $method = $param->{method} ?
$param->{method} : "ssh";
419 my $dest = parse_target
($param->{dest
});
420 my $source = parse_target
($param->{source
});
422 my $sync_path = sub {
423 my ($source, $name, $cfg, $max_snap, $dest, $method) = @_;
425 ($source->{old_snap
},$source->{last_snap
}) = snapshot_get
($source, $dest, $max_snap, $name);
427 my $job_status = check_config
($source, $name, $cfg) if $cfg;
428 die "VM syncing at the moment!\n" if ($job_status && $job_status eq "active");
430 if ($job_status && $job_status eq "exist") {
431 my $conf_name = $source->{abs_path
};
432 $conf_name = $source->{vmid
} if $source->{vmid
};
433 $cfg->{$conf_name}->{$name}->{locked
} = "yes";
434 write_to_config
($cfg);
437 my $date = snapshot_add
($source, $dest, $name);
439 send_image
($source, $dest, $method, $param->{verbose
}, $param->{limit
});
441 snapshot_destroy
($source, $dest, $method, $source->{old_snap
}) if ($source->{destroy
} && $source->{old_snap
});
443 if ($job_status && $job_status eq "exist") {
444 my $conf_name = $source->{abs_path
};
445 $conf_name = $source->{vmid
} if $source->{vmid
};
446 $cfg->{$conf_name}->{$name}->{locked
} = "no";
447 $cfg->{$conf_name}->{$name}->{lsync
} = $date;
448 write_to_config
($cfg);
452 $param->{method} = "ssh" if !$param->{method};
454 if ($source->{vmid
}) {
455 die "VM $source->{vmid} doesn't exist\n" if !vm_exists
($source);
456 my $disks = get_disks
($source);
458 foreach my $disk (keys %{$disks}) {
459 $source->{abs_path
} = $disks->{$disk}->{pool
};
460 $source->{abs_path
} .= "\/$disks->{$disk}->{path}" if $disks->{$disk}->{path
};
462 $source->{pool
} = $disks->{$disk}->{pool
};
463 $source->{path
} = "\/$disks->{$disk}->{path}";
465 &$sync_path($source, $name, $cfg, $max_snap, $dest, $method);
468 &$sync_path($source, $name, $cfg, $max_snap, $dest, $method);
473 my ($source, $dest, $max_snap, $name) = @_;
475 my $cmd = "zfs list -r -t snapshot -Ho name, -S creation ";
477 $cmd .= $source->{abs_path
};
478 $cmd = "ssh root\@$source->{ip} ".$cmd if $source->{ip
};
480 my $raw = run_cmd
($cmd);
483 my $last_snap = undef;
485 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
487 $last_snap = $line if $index == 1;
488 if ($index == $max_snap) {
489 $source->{destroy
} = 1;
495 $line =~ m/^(.+)\@(rep_$name\_.+)(\n|$)/;
496 return ($2, $last_snap) if $2;
502 my ($source, $dest, $name) = @_;
504 my $date = get_date
();
506 my $snap_name = "rep_$name\_".$date;
508 $source->{new_snap
} = $snap_name;
510 my $path = $source->{abs_path
}."\@".$snap_name;
512 my $cmd = "zfs snapshot $path";
513 $cmd = "ssh root\@$source->{ip} ".$cmd if $source->{ip
};
520 snapshot_destroy
($source, $dest, 'ssh', $snap_name);
529 open(my $fh, '>>', "$CRONJOBS")
530 or die "Could not open file: $!\n";
532 foreach my $name (keys%{$vm}){
533 my $text = "*/$vm->{$name}->{interval} * * * * root ";
534 $text .= "$PATH$PROGNAME sync";
535 $text .= " -source ";
536 if ($vm->{$name}->{vmid
}) {
537 $text .= "$vm->{$name}->{source_ip}:" if $vm->{$name}->{source_ip
};
538 $text .= "$vm->{$name}->{vmid} ";
540 $text .= "$vm->{$name}->{source_ip}:" if $vm->{$name}->{source_ip
};
541 $text .= "$vm->{$name}->{source_pool}";
542 $text .= "$vm->{$name}->{source_path}" if $vm->{$name}->{source_path
};
545 $text .= "$vm->{$name}->{dest_ip}:" if $vm->{$name}->{dest_ip
};
546 $text .= "$vm->{$name}->{dest_pool}";
547 $text .= "$vm->{$name}->{dest_path}" if $vm->{$name}->{dest_path
};
548 $text .= " -name $name ";
549 $text .= " -limit $vm->{$name}->{limit}" if $vm->{$name}->{limit
};
550 $text .= " -maxsnap $vm->{$name}->{maxsnap}" if $vm->{$name}->{maxsnap
};
558 my ($source, $name) = @_;
560 open(my $fh, '<', "$CRONJOBS")
561 or die "Could not open file: $!\n";
568 while ($text && $text =~ s/^(.*?)(\n|$)//) {
570 if ($line !~ m/.*$PROGNAME.*$source.*$name.*/){
574 open($fh, '>', "$CRONJOBS")
575 or die "Could not open file: $!\n";
584 $cmd = "ssh root\@$target->{ip} " if $target->{ip
};
585 $cmd .= "qm config $target->{vmid}";
587 my $res = run_cmd
($cmd);
589 my $disks = parse_disks
($res, $target->{ip
});
596 print "Start CMD\n" if $DEBUG;
597 print Dumper
$cmd if $DEBUG;
598 my $output = `$cmd 2>&1`;
600 die $output if 0 != $?;
603 print Dumper
$output if $DEBUG;
604 print "END CMD\n" if $DEBUG;
609 my ($text, $ip) = @_;
615 $cmd .= "ssh root\@$ip " if $ip;
616 $cmd .= "pvesm zfsscan";
617 my $zfs_pools = run_cmd
($cmd);
618 while ($text && $text =~ s/^(.*?)(\n|$)//) {
622 if($line =~ m/^(virtio\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
625 } elsif($line =~ m/^(ide\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
628 } elsif($line =~ m/^(scsi\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
631 } elsif($line =~ m/^(sata\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
636 if($disk && $disk ne "none" && $disk !~ m/cdrom/ ) {
638 $cmd .= "ssh root\@$ip " if $ip;
639 $cmd .= "pvesm path $stor$disk";
640 my $path = run_cmd
($cmd);
642 if ($path =~ m/^\/dev\
/zvol\/(\w
+).*(\
/$disk)$/){
644 $disks->{$num}->{pool
} = $1;
645 $disks->{$num}->{path
} = $disk;
649 die "ERROR: in path\n";
653 die "disk is not on ZFS Storage\n" if $num == 0;
657 sub snapshot_destroy
{
658 my ($source, $dest, $method, $snap) = @_;
660 my $zfscmd = "zfs destroy ";
661 my $name = "$source->{path}\@$snap";
664 if($source->{ip
} && $method eq 'ssh'){
665 run_cmd
("ssh root\@$source->{ip} $zfscmd $source->{pool}$name");
667 run_cmd
("$zfscmd $source->{pool}$name");
674 my $ssh = $dest->{ip
} ?
"ssh root\@$dest->{ip}" : "";
677 $path ="$dest->{path}" if $dest->{path
};
679 my @dir = split(/\//, $source->{path
});
681 run_cmd
("$ssh $zfscmd $dest->{pool}$path\/$dir[@dir-1]\@$snap ");
690 my ($source ,$dest, $method) = @_;
693 $cmd = "ssh root\@$dest->{ip} " if $dest->{ip
};
694 $cmd .= "zfs list -rt snapshot -Ho name $dest->{pool}";
695 $cmd .= "$dest->{path}" if $dest->{path
};
696 my @dir = split(/\//, $source->{path
});
697 $cmd .= "\/$dir[@dir-1]\@$source->{old_snap}";
700 eval {$text =run_cmd
($cmd);};
706 while ($text && $text =~ s/^(.*?)(\n|$)//) {
708 return 1 if $line =~ m/^.*$source->{old_snap}$/;
713 my ($source, $dest, $method, $verbose, $limit) = @_;
717 $cmd .= "ssh root\@$source->{ip} " if $source->{ip
};
719 $cmd .= "-v " if $verbose;
721 if($source->{last_snap
} && snapshot_exist
($source ,$dest, $method)) {
722 $cmd .= "-i $source->{abs_path}\@$source->{old_snap} $source->{abs_path}\@$source->{new_snap} ";
724 $cmd .= "$source->{abs_path}\@$source->{new_snap} ";
728 my $bwl = $limit*1024;
729 $cmd .= "| cstream -t $bwl";
732 $cmd .= "ssh root\@$dest->{ip} " if $dest->{ip
};
733 $cmd .= "zfs recv $dest->{pool}";
734 $cmd .= "$dest->{path}" if $dest->{path
};
736 my @dir = split(/\//,$source->{path
});
737 $cmd .= "\/$dir[@dir-1]\@$source->{new_snap}";
744 snapshot_destroy
($source, undef, $method, $source->{new_snap
});
748 if ($source->{vmid
}) {
749 if ($method eq "ssh") {
750 send_config
($source, $dest,'ssh');
757 my ($source, $dest, $method) = @_;
759 if ($method eq 'ssh'){
760 if ($dest->{ip
} && $source->{ip
}) {
761 run_cmd
("ssh root\@$dest->{ip} mkdir $VMCONFIG -p");
762 run_cmd
("scp root\@$source->{ip}:$QEMU_CONF$source->{vmid}.conf root\@$dest->{ip}:$VMCONFIG$source->{vmid}.conf.$source->{new_snap}");
763 } elsif ($dest->{ip
}) {
764 run_cmd
("ssh root\@$dest->{ip} mkdir $VMCONFIG -p");
765 run_cmd
("scp $QEMU_CONF$source->{vmid}.conf root\@$dest->{ip}:$VMCONFIG$source->{vmid}.conf.$source->{new_snap}");
766 } elsif ($source->{ip
}) {
767 run_cmd
("mkdir $VMCONFIG -p");
768 run_cmd
("scp root\@$source->{ip}:$QEMU_CONF$source->{vmid}.conf $VMCONFIG$source->{vmid}.conf.$source->{new_snap}");
771 if ($source->{destroy
}){
773 run_cmd
("ssh root\@$dest->{ip} rm -f $VMCONFIG$source->{vmid}.conf.$source->{old_snap}");
775 run_cmd
("rm -f $VMCONFIG$source->{vmid}.conf.$source->{old_snap}");
782 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
783 my $datestamp = sprintf ( "%04d-%02d-%02d_%02d:%02d:%02d",$year+1900,$mon+1,$mday,$hour,$min,$sec);
789 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
791 my $status_list = sprintf("%-25s%-15s%-10s\n","SOURCE","NAME","STATUS");
793 foreach my $source (keys%{$cfg}){
794 foreach my $sync_name (keys%{$cfg->{$source}}){
797 my $source_name = $source;
799 $source_name = $cfg->{$source}->{$sync_name}->{source_ip
}.":".$source if $cfg->{$source}->{$sync_name}->{source_ip
};
801 if ($cfg->{$source}->{$sync_name}->{locked
} eq 'no'){
802 $status = sprintf("%-10s","OK");
803 } elsif ($cfg->{$source}->{$sync_name}->{locked
} eq 'yes' &&
804 $cfg->{$source}->{$sync_name}->{failure
}) {
805 $status = sprintf("%-10s","sync error");
807 $status = sprintf("%-10s","syncing");
810 $status_list .= sprintf("%-25s%-15s", cut_to_width
($source_name,25), cut_to_width
($sync_name,15));
811 $status_list .= "$status\n";
819 my $command = $ARGV[0];
821 my $commands = {'destroy' => 1,
828 if (!$command || !$commands->{$command}) {
842 my $help_sync = "zfs-zsync sync -dest <string> -source <string> [OPTIONS]\n
843 \twill sync one time\n
845 \t\tthe destination target is like [IP:]<Pool>[/Path]\n
847 \t\tmax sync speed in kBytes/s, default unlimited\n
848 \t-maxsnap\tinteger\n
849 \t\thow much snapshots will be kept before get erased, default 1/n
851 \t\tname of the sync job, if not set it is default.
852 \tIt is only necessary if scheduler allready contains this source.\n
854 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
856 my $help_create = "zfs-zsync create -dest <string> -source <string> [OPTIONS]/n
857 \tCreate a sync Job\n
859 \t\tthe destination target is like [IP]:<Pool>[/Path]\n
860 \t-interval\tinteger\n
861 \t\tthe interval in min in witch the zfs will sync,
862 \t\tdefault is 15 min\n
864 \t\tmax sync speed, default unlimited\n
866 \t\thow much snapshots will be kept before get erased, default 1\n
868 \t\tname of the sync job, if not set it is default\n
870 \t\tif this flag is set it will skip the first sync\n
872 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
874 my $help_destroy = "zfs-zsync destroy -source <string> [OPTIONS]\n
875 \tremove a sync Job from the scheduler\n
877 \t\tname of the sync job, if not set it is default\n
879 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
881 my $help_help = "zfs-zsync help <cmd> [OPTIONS]\n
882 \tGet help about specified command.\n
885 \t-verbose\tboolean\n
886 \t\tVerbose output format.\n";
888 my $help_list = "zfs-zsync list\n
889 \tGet a List of all scheduled Sync Jobs\n";
891 my $help_status = "zfs-zsync status\n
892 \tGet the status of all scheduled Sync Jobs\n";
908 die "$help_destroy\n";
912 die "$help_create\n";
920 die "$help_status\n";
926 my $err = GetOptions
('dest=s' => \
$dest,
927 'source=s' => \
$source,
928 'verbose' => \
$verbose,
929 'interval=i' => \
$interval,
930 'limit=i' => \
$limit,
931 'maxsnap=i' => \
$maxsnap,
936 die "can't parse options\n";
940 $param->{dest
} = $dest;
941 $param->{source
} = $source;
942 $param->{verbose
} = $verbose;
943 $param->{interval
} = $interval;
944 $param->{limit
} = $limit;
945 $param->{maxsnap
} = $maxsnap;
946 $param->{name
} = $name;
947 $param->{skip
} = $skip;
952 die "$help_destroy\n" if !$source;
953 check_target
($source);
958 die "$help_sync\n" if !$source || !$dest;
959 check_target
($source);
965 die "$help_create\n" if !$source || !$dest;
966 check_target
($source);
980 my $help_command = $ARGV[1];
981 if ($help_command && $commands->{$help_command}) {
982 print help
($help_command);
985 exec("man $PROGNAME");
995 print("ERROR:\tno command specified\n") if !$help;
996 print("USAGE:\t$PROGNAME <COMMAND> [ARGS] [OPTIONS]\n");
997 print("\tpve-zsync help [<cmd>] [OPTIONS]\n\n");
998 print("\tpve-zsync create -dest <string> -source <string> [OPTIONS]\n");
999 print("\tpve-zsync destroy -source <string> [OPTIONS]\n");
1000 print("\tpve-zsync list\n");
1001 print("\tpve-zsync status\n");
1002 print("\tpve-zsync sync -dest <string> -source <string> [OPTIONS]\n");
1010 if($target !~ m/(\d+.\d+.\d+.\d+:)?([\w\-\_\/]+)(\
/.+)?/){
1011 print("ERROR:\t$target is not valid.\n\tUse [IP:]<ZFSPool>[/Path]!\n");
1021 pve-zsync - PVE ZFS Replication Manager
1025 zfs-zsync <COMMAND> [ARGS] [OPTIONS]
1027 zfs-zsync help <cmd> [OPTIONS]
1029 Get help about specified command.
1037 Verbose output format.
1039 zfs-zsync create -dest <string> -source <string> [OPTIONS]
1045 the destination target is like [IP]:<Pool>[/Path]
1049 the interval in min in witch the zfs will sync, default is 15 min
1053 max sync speed, default unlimited
1057 how much snapshots will be kept before get erased, default 1
1061 name of the sync job, if not set it is default
1065 if this flag is set it will skip the first sync
1069 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1071 zfs-zsync destroy -source <string> [OPTIONS]
1073 remove a sync Job from the scheduler
1077 name of the sync job, if not set it is default
1081 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1085 Get a List of all scheduled Sync Jobs
1089 Get the status of all scheduled Sync Jobs
1091 zfs-zsync sync -dest <string> -source <string> [OPTIONS]
1097 the destination target is like [IP:]<Pool>[/Path]
1101 max sync speed in kBytes/s, default unlimited
1105 how much snapshots will be kept before get erased, default 1
1109 name of the sync job, if not set it is default.
1110 It is only necessary if scheduler allready contains this source.
1114 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1118 This Tool helps you to sync your VM or directory which stored on ZFS between 2 servers.
1119 This tool also has the capability to add jobs to cron so the sync will be automatically done.
1121 =head2 PVE ZFS Storage sync Tool
1123 This Tool can get remote pool on other PVE or send Pool to others ZFS machines
1127 add sync job from local VM to remote ZFS Server
1128 zfs-zsync -source=100 -dest=192.168.1.2:zfspool
1130 =head1 IMPORTANT FILES
1132 Where the cron jobs are stored /etc/cron.d/pve-zsync
1133 Where the VM config get copied on the destination machine /var/pve-zsync
1134 Where the config is stored /var/pve-zsync
1136 Copyright (C) 2007-2015 Proxmox Server Solutions GmbH
1138 This program is free software: you can redistribute it and/or modify it
1139 under the terms of the GNU Affero General Public License as published
1140 by the Free Software Foundation, either version 3 of the License, or
1141 (at your option) any later version.
1143 This program is distributed in the hope that it will be useful, but
1144 WITHOUT ANY WARRANTY; without even the implied warranty of
1145 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1146 Affero General Public License for more details.
1148 You should have received a copy of the GNU Affero General Public
1149 License along with this program. If not, see
1150 <http://www.gnu.org/licenses/>.