]>
git.proxmox.com Git - pve-zsync.git/blob - pve-zsync
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/';
11 my $LOCKFILE = $VMCONFIG.$PROGNAME.'lock';
15 use Data
::Dumper
qw(Dumper);
16 use Fcntl
qw(:flock SEEK_END);
20 check_bin
('cstream');
28 foreach my $p (split (/:/, $ENV{PATH
})) {
35 warn "unable to find command '$bin'\n";
39 my ($text, $max) = @_;
41 return $text if (length($text) <= $max);
42 my @spl = split('/', $text);
44 my $count = length($spl[@spl-1]);
45 return "..\/".substr($spl[@spl-1],($count-$max)+3 ,$count) if $count > $max;
47 $count += length($spl[0]) if @spl > 1;
48 return substr($spl[0], 0, $max-4-length
($spl[@spl-1]))."\/..\/".$spl[@spl-1] if $count > $max;
51 $rest = $max-$count if ($max-$count > 0);
53 return "$spl[0]".substr($text, length($spl[0]), $rest)."..\/".$spl[@spl-1];
58 flock($fh, LOCK_EX
) or die "Cannot lock config - $!\n";
60 seek($fh, 0, SEEK_END
) or die "Cannot seek - $!\n";
65 flock($fh, LOCK_UN
) or die "Cannot unlock config- $!\n";
69 my ($source, $name, $cfg) = @_;
71 my $id = $source->{vmid
} ?
$source->{vmid
} : $source->{abs_path
};
72 my $status = $cfg->{$id}->{$name}->{status
};
74 if ($cfg->{$id}->{$name}->{status
}){
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);
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";
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 'status') {
171 $cfg->{$source}->{$sync_name}->{$par} = $value;
172 die "error in Config: Status 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 (sort keys%{$cfg}){
265 foreach my $sync_name (sort 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));
271 if($cfg->{$source}->{$sync_name}->{status
} eq 'syncing'){
277 $list .= sprintf("%-7s", $active);
278 $list .= sprintf("%-20s",$cfg->{$source}->{$sync_name}->{lsync
});
279 $list .= sprintf("%-10s",$cfg->{$source}->{$sync_name}->{interval
});
280 $list .= sprintf("%-5s\n",$cfg->{$source}->{$sync_name}->{method});
291 $cmd = "ssh root\@$target->{ip} " if ($target->{ip
});
292 $cmd .= "qm status $target->{vmid}";
294 my $res = run_cmd
($cmd);
296 return 1 if ($res =~ m/^status.*$/);
303 open(my $lock_fh, ">", $LOCKFILE) || die "cannot open Lock File: $LOCKFILE\n";
305 my $cfg = read_from_config
;
309 my $name = $param->{name
} ?
$param->{name
} : "default";
310 my $interval = $param->{interval
} ?
$param->{interval
} : 15;
312 my $source = parse_target
($param->{source
});
313 my $dest = parse_target
($param->{dest
});
315 $vm->{$name}->{dest_pool
} = $dest->{pool
};
316 $vm->{$name}->{dest_ip
} = $dest->{ip
} if $dest->{ip
};
317 $vm->{$name}->{dest_path
} = $dest->{path
} if $dest->{path
};
319 $param->{method} = "local" if !$dest->{ip
} && !$source->{ip
};
320 $vm->{$name}->{status
} = "ok";
321 $vm->{$name}->{interval
} = $interval;
322 $vm->{$name}->{method} = $param->{method} ?
$param->{method} : "ssh";
323 $vm->{$name}->{limit
} = $param->{limit
} if $param->{limit
};
324 $vm->{$name}->{maxsnap
} = $param->{maxsnap
} if $param->{maxsnap
};
326 if ( my $ip = $vm->{$name}->{dest_ip
} ) {
327 run_cmd
("ssh-copy-id -i /root/.ssh/id_rsa.pub root\@$ip");
330 if ( my $ip = $source->{ip
} ) {
331 run_cmd
("ssh-copy-id -i /root/.ssh/id_rsa.pub root\@$ip");
334 die "Pool $dest->{abs_path} does not exists\n" if check_pool_exsits
($dest->{abs_path
}, $dest->{ip
});
336 my $check = check_pool_exsits
($source->{abs_path
}, $source->{ip
}) if !$source->{vmid
} && $source->{abs_path
};
338 die "Pool $source->{abs_path} does not exists\n" if undef($check);
341 my ($vm, $name) = @_;
343 if ($vm->{$name}->{vmid
}) {
344 $source .= $vm->{$name}->{vmid
};
346 $source .= $vm->{$name}->{source_pool
};
347 $source .= $vm->{$name}->{source_path
} if $vm->{$name}->{source_path
};
349 die "Config already exists\n" if $cfg->{$source}->{$name};
351 $cfg->{$source}->{$name} = $vm->{$name};
355 write_to_config
($cfg);
358 if ($source->{vmid
}) {
359 die "VM $source->{vmid} doesn't exist\n" if !vm_exists
($source);
360 my $disks = get_disks
($source);
361 $vm->{$name}->{vmid
} = $source->{vmid
};
362 $vm->{$name}->{lsync
} = 0;
363 $vm->{$name}->{source_ip
} = $source->{ip
} if $source->{ip
};
365 &$add_job($vm, $name);
368 $vm->{$name}->{source_pool
} = $source->{pool
};
369 $vm->{$name}->{source_ip
} = $source->{ip
} if $source->{ip
};
370 $vm->{$name}->{source_path
} = $source->{path
} if $source->{path
};
371 $vm->{$name}->{lsync
} = 0;
373 &$add_job($vm, $name);
379 eval {sync
($param) if !$param->{skip
};};
389 modify_configs
($param->{name
}, $param->{source
},1);
395 open(my $lock_fh, ">", $LOCKFILE) || die "cannot open Lock File: $LOCKFILE\n";
398 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
400 my $name = $param->{name
} ?
$param->{name
} : "default";
401 my $max_snap = $param->{maxsnap
} ?
$param->{maxsnap
} : 1;
402 my $method = $param->{method} ?
$param->{method} : "ssh";
404 my $dest = parse_target
($param->{dest
});
405 my $source = parse_target
($param->{source
});
407 my $sync_path = sub {
408 my ($source, $name, $cfg, $max_snap, $dest, $method) = @_;
410 ($source->{old_snap
},$source->{last_snap
}) = snapshot_get
($source, $dest, $max_snap, $name);
412 my $job_status = check_config
($source, $name, $cfg) if $cfg;
413 die "VM Status: $job_status syncing will not done!\n" if ($job_status && !($job_status eq "ok" || $job_status eq "stoped"));
416 my $conf_name = $source->{abs_path
};
417 $conf_name = $source->{vmid
} if $source->{vmid
};
418 $cfg->{$conf_name}->{$name}->{status
} = "syncing";
419 write_to_config
($cfg);
425 $date = snapshot_add
($source, $dest, $name);
427 send_image
($source, $dest, $method, $param->{verbose
}, $param->{limit
});
429 snapshot_destroy
($source, $dest, $method, $source->{old_snap
}) if ($source->{destroy
} && $source->{old_snap
});
433 my $conf_name = $source->{abs_path
};
434 $conf_name = $source->{vmid
} if $source->{vmid
};
435 $cfg->{$conf_name}->{$name}->{status
} = "error";
436 write_to_config
($cfg);
438 my $source_target = $source->{ip
} ?
$source->{ip
}.":" : '';
439 $source_target .= $source->{vmid
} ?
$source->{vmid
} : $source->{abs_path
};
448 my $conf_name = $source->{abs_path
};
449 $conf_name = $source->{vmid
} if $source->{vmid
};
450 $cfg->{$conf_name}->{$name}->{status
} = "ok";
451 $cfg->{$conf_name}->{$name}->{lsync
} = $date;
453 write_to_config
($cfg);
457 $param->{method} = "ssh" if !$param->{method};
459 if ($source->{vmid
}) {
460 die "VM $source->{vmid} doesn't exist\n" if !vm_exists
($source);
461 my $disks = get_disks
($source);
463 foreach my $disk (sort keys %{$disks}) {
464 $source->{abs_path
} = $disks->{$disk}->{pool
};
465 $source->{abs_path
} .= "\/$disks->{$disk}->{path}" if $disks->{$disk}->{path
};
467 $source->{pool
} = $disks->{$disk}->{pool
};
468 $source->{path
} = "\/$disks->{$disk}->{path}";
470 &$sync_path($source, $name, $cfg, $max_snap, $dest, $method);
472 if ($method eq "ssh") {
473 send_config
($source, $dest,'ssh');
476 &$sync_path($source, $name, $cfg, $max_snap, $dest, $method);
483 my ($source, $dest, $max_snap, $name) = @_;
485 my $cmd = "zfs list -r -t snapshot -Ho name, -S creation ";
487 $cmd .= $source->{abs_path
};
488 $cmd = "ssh root\@$source->{ip} ".$cmd if $source->{ip
};
490 my $raw = run_cmd
($cmd);
493 my $last_snap = undef;
495 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
497 $last_snap = $line if $index == 1;
498 if ($index == $max_snap) {
499 $source->{destroy
} = 1;
505 $line =~ m/^(.+)\@(rep_$name\_.+)(\n|$)/;
506 return ($2, $last_snap) if $2;
512 my ($source, $dest, $name) = @_;
514 my $date = get_date
();
516 my $snap_name = "rep_$name\_".$date;
518 $source->{new_snap
} = $snap_name;
520 my $path = $source->{abs_path
}."\@".$snap_name;
522 my $cmd = "zfs snapshot $path";
523 $cmd = "ssh root\@$source->{ip} ".$cmd if $source->{ip
};
530 snapshot_destroy
($source, $dest, 'ssh', $snap_name);
539 my $text = 'SHELL=/bin/sh'."\n";
540 $text .= 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin'."\n";
542 open(my $fh, '>', "$CRONJOBS")
543 or die "Could not open file: $!\n";
545 foreach my $source (sort keys%{$cfg}){
546 foreach my $sync_name (sort keys%{$cfg->{$source}}){
547 next if $cfg->{$source}->{$sync_name}->{status
} ne 'ok';
548 $text .= "*/$cfg->{$source}->{$sync_name}->{interval} * * * * root ";
549 $text .= "$PATH$PROGNAME sync";
550 $text .= " -source ";
551 if ($cfg->{$source}->{$sync_name}->{vmid
}) {
552 $text .= "$cfg->{$source}->{$sync_name}->{source_ip}:" if $cfg->{$source}->{$sync_name}->{source_ip
};
553 $text .= "$cfg->{$source}->{$sync_name}->{vmid} ";
555 $text .= "$cfg->{$source}->{$sync_name}->{source_ip}:" if $cfg->{$source}->{$sync_name}->{source_ip
};
556 $text .= "$cfg->{$source}->{$sync_name}->{source_pool}";
557 $text .= "$cfg->{$source}->{$sync_name}->{source_path}" if $cfg->{$source}->{$sync_name}->{source_path
};
560 $text .= "$cfg->{$source}->{$sync_name}->{dest_ip}:" if $cfg->{$source}->{$sync_name}->{dest_ip
};
561 $text .= "$cfg->{$source}->{$sync_name}->{dest_pool}";
562 $text .= "$cfg->{$source}->{$sync_name}->{dest_path}" if $cfg->{$source}->{$sync_name}->{dest_path
};
563 $text .= " -name $sync_name ";
564 $text .= " -limit $cfg->{$source}->{$sync_name}->{limit}" if $cfg->{$source}->{$sync_name}->{limit
};
565 $text .= " -maxsnap $cfg->{$source}->{$sync_name}->{maxsnap}" if $cfg->{$source}->{$sync_name}->{maxsnap
};
577 $cmd = "ssh root\@$target->{ip} " if $target->{ip
};
578 $cmd .= "qm config $target->{vmid}";
580 my $res = run_cmd
($cmd);
582 my $disks = parse_disks
($res, $target->{ip
});
589 print "Start CMD\n" if $DEBUG;
590 print Dumper
$cmd if $DEBUG;
591 my $output = `$cmd 2>&1`;
593 die $output if 0 != $?;
596 print Dumper
$output if $DEBUG;
597 print "END CMD\n" if $DEBUG;
602 my ($text, $ip) = @_;
608 $cmd .= "ssh root\@$ip " if $ip;
609 $cmd .= "pvesm zfsscan";
610 my $zfs_pools = run_cmd
($cmd);
611 while ($text && $text =~ s/^(.*?)(\n|$)//) {
615 my $is_disk = $line =~ m/^(virtio|ide|scsi|sata){1}\d+: /;
616 if($line =~ m/^(virtio\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
619 } elsif($line =~ m/^(ide\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
622 } elsif($line =~ m/^(scsi\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
625 } elsif($line =~ m/^(sata\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
630 die "disk is not on ZFS Storage\n" if $is_disk && !$disk && $line !~ m/cdrom/;
632 if($disk && $line !~ m/none/ && $line !~ m/cdrom/ ) {
634 $cmd .= "ssh root\@$ip " if $ip;
635 $cmd .= "pvesm path $stor$disk";
636 my $path = run_cmd
($cmd);
638 if ($path =~ m/^\/dev\
/zvol\/(\w
+).*(\
/$disk)$/){
640 $disks->{$num}->{pool
} = $1;
641 $disks->{$num}->{path
} = $disk;
645 die "ERROR: in path\n";
652 sub snapshot_destroy
{
653 my ($source, $dest, $method, $snap) = @_;
655 my $zfscmd = "zfs destroy ";
656 my $name = "$source->{path}\@$snap";
659 if($source->{ip
} && $method eq 'ssh'){
660 run_cmd
("ssh root\@$source->{ip} $zfscmd $source->{pool}$name");
662 run_cmd
("$zfscmd $source->{pool}$name");
669 my $ssh = $dest->{ip
} ?
"ssh root\@$dest->{ip}" : "";
672 $path ="$dest->{path}" if $dest->{path
};
674 my @dir = split(/\//, $source->{path
});
676 run_cmd
("$ssh $zfscmd $dest->{pool}$path\/$dir[@dir-1]\@$snap ");
685 my ($source ,$dest, $method) = @_;
688 $cmd = "ssh root\@$dest->{ip} " if $dest->{ip
};
689 $cmd .= "zfs list -rt snapshot -Ho name $dest->{pool}";
690 $cmd .= "$dest->{path}" if $dest->{path
};
691 my @dir = split(/\//, $source->{path
});
692 $cmd .= "\/$dir[@dir-1]\@$source->{old_snap}";
695 eval {$text =run_cmd
($cmd);};
701 while ($text && $text =~ s/^(.*?)(\n|$)//) {
703 return 1 if $line =~ m/^.*$source->{old_snap}$/;
708 my ($source, $dest, $method, $verbose, $limit) = @_;
712 $cmd .= "ssh root\@$source->{ip} " if $source->{ip
};
714 $cmd .= "-v " if $verbose;
716 if($source->{last_snap
} && snapshot_exist
($source ,$dest, $method)) {
717 $cmd .= "-i $source->{abs_path}\@$source->{old_snap} $source->{abs_path}\@$source->{new_snap} ";
719 $cmd .= "$source->{abs_path}\@$source->{new_snap} ";
723 my $bwl = $limit*1024;
724 $cmd .= "| cstream -t $bwl";
727 $cmd .= "ssh root\@$dest->{ip} " if $dest->{ip
};
728 $cmd .= "zfs recv $dest->{pool}";
729 $cmd .= "$dest->{path}" if $dest->{path
};
731 my @dir = split(/\//,$source->{path
});
732 $cmd .= "\/$dir[@dir-1]\@$source->{new_snap}";
739 snapshot_destroy
($source, undef, $method, $source->{new_snap
});
746 my ($source, $dest, $method) = @_;
748 if ($method eq 'ssh'){
749 if ($dest->{ip
} && $source->{ip
}) {
750 run_cmd
("ssh root\@$dest->{ip} mkdir $VMCONFIG -p");
751 run_cmd
("scp root\@$source->{ip}:$QEMU_CONF$source->{vmid}.conf root\@$dest->{ip}:$VMCONFIG$source->{vmid}.conf.$source->{new_snap}");
752 } elsif ($dest->{ip
}) {
753 run_cmd
("ssh root\@$dest->{ip} mkdir $VMCONFIG -p");
754 run_cmd
("scp $QEMU_CONF$source->{vmid}.conf root\@$dest->{ip}:$VMCONFIG$source->{vmid}.conf.$source->{new_snap}");
755 } elsif ($source->{ip
}) {
756 run_cmd
("mkdir $VMCONFIG -p");
757 run_cmd
("scp root\@$source->{ip}:$QEMU_CONF$source->{vmid}.conf $VMCONFIG$source->{vmid}.conf.$source->{new_snap}");
760 if ($source->{destroy
}){
762 run_cmd
("ssh root\@$dest->{ip} rm -f $VMCONFIG$source->{vmid}.conf.$source->{old_snap}");
764 run_cmd
("rm -f $VMCONFIG$source->{vmid}.conf.$source->{old_snap}");
771 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
772 my $datestamp = sprintf ( "%04d-%02d-%02d_%02d:%02d:%02d",$year+1900,$mon+1,$mday,$hour,$min,$sec);
778 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
780 my $status_list = sprintf("%-25s%-15s%-10s\n","SOURCE","NAME","STATUS");
782 foreach my $source (sort keys%{$cfg}){
783 foreach my $sync_name (sort keys%{$cfg->{$source}}){
786 my $source_name = $source;
788 $source_name = $cfg->{$source}->{$sync_name}->{source_ip
}.":".$source if $cfg->{$source}->{$sync_name}->{source_ip
};
790 $status = sprintf("%-10s",$cfg->{$source}->{$sync_name}->{status
});
792 $status_list .= sprintf("%-25s%-15s", cut_to_width
($source_name,25), cut_to_width
($sync_name,15));
793 $status_list .= "$status\n";
803 modify_configs
($param->{name
}, $param->{source
},4);
809 modify_configs
($param->{name
}, $param->{source
},2);
813 my ($name, $sou, $op) = @_;
815 $name = $name ?
$name : "default";
817 open(my $lock_fh, ">", $LOCKFILE) || die "cannot open Lock File: $LOCKFILE\n";
820 my $cfg = read_from_config
("$CONFIG_PATH$CONFIG");
822 my $source = parse_target
($sou);
824 my $change_configs = sub {
825 my ($path, $name, $cfg, $op) = @_;
827 die "Source does not exist!\n" unless $cfg->{$path} ;
829 die "Sync Name does not exist!\n" unless $cfg->{$path}->{$name};
831 my $source = $cfg->{$path}->{$name}->{source_ip
} ?
"$cfg->{$path}->{$name}->{source_ip}:" : '';
833 $source .= $cfg->{$path}->{$name}->{source_pool
} if $cfg->{$path}->{$name}->{source_pool
};
834 $source .= $cfg->{$path}->{$name}->{source_path
} ?
$cfg->{$path}->{$name}->{source_path
} :'';
836 my $dest = $cfg->{$path}->{$name}->{dest_ip
} ?
$cfg->{$path}->{$name}->{dest_ip
} :"";
837 $dest .= $cfg->{$path}->{$name}->{dest_pool
} if $cfg->{$path}->{$name}->{dest_pool
};
838 $dest .= $cfg->{$path}->{$name}->{dest_path
} ?
$cfg->{$path}->{$name}->{dest_path
} :'';
841 delete $cfg->{$path}->{$name};
843 delete $cfg->{$path} if keys%{$cfg->{$path}} == 0;
845 write_to_config
($cfg);
847 if($op == 1 || $op == 2) {
851 $cfg->{$path}->{$name}->{status
} = "stoped";
853 write_to_config
($cfg);
859 $cfg->{$path}->{$name}->{status
} = "ok";
861 write_to_config
($cfg);
868 if ($source->{vmid
}) {
869 my $path = $source->{vmid
};
871 &$change_configs($path, $name, $cfg, $op)
873 my $path = $source->{pool
};
874 $path .= $source->{path
} if $source->{path
};
876 &$change_configs($path, $name, $cfg, $op);
883 my $command = $ARGV[0];
885 my $commands = {'destroy' => 1,
894 if (!$command || !$commands->{$command}) {
908 my $help_sync = "$PROGNAME sync -dest <string> -source <string> [OPTIONS]\n
909 \twill sync one time\n
911 \t\tthe destination target is like [IP:]<Pool>[/Path]\n
913 \t\tmax sync speed in kBytes/s, default unlimited\n
914 \t-maxsnap\tinteger\n
915 \t\thow much snapshots will be kept before get erased, default 1/n
917 \t\tname of the sync job, if not set it is default.
918 \tIt is only necessary if scheduler allready contains this source.\n
920 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
922 my $help_create = "$PROGNAME create -dest <string> -source <string> [OPTIONS]/n
923 \tCreate a sync Job\n
925 \t\tthe destination target is like [IP]:<Pool>[/Path]\n
926 \t-interval\tinteger\n
927 \t\tthe interval in min in witch the zfs will sync,
928 \t\tdefault is 15 min\n
930 \t\tmax sync speed in kBytes/s, default unlimited\n
932 \t\thow much snapshots will be kept before get erased, default 1\n
934 \t\tname of the sync job, if not set it is default\n
936 \t\tif this flag is set it will skip the first sync\n
938 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
940 my $help_destroy = "$PROGNAME destroy -source <string> [OPTIONS]\n
941 \tremove a sync Job from the scheduler\n
943 \t\tname of the sync job, if not set it is default\n
945 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
947 my $help_help = "$PROGNAME help <cmd> [OPTIONS]\n
948 \tGet help about specified command.\n
951 \t-verbose\tboolean\n
952 \t\tVerbose output format.\n";
954 my $help_list = "$PROGNAME list\n
955 \tGet a List of all scheduled Sync Jobs\n";
957 my $help_status = "$PROGNAME status\n
958 \tGet the status of all scheduled Sync Jobs\n";
960 my $help_enable = "$PROGNAME enable -source <string> [OPTIONS]\n
961 \tenable a syncjob and reset error\n
963 \t\tname of the sync job, if not set it is default\n
965 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
967 my $help_disable = "$PROGNAME disable -source <string> [OPTIONS]\n
970 \t\tname of the sync job, if not set it is default\n
972 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
988 die "$help_destroy\n";
992 die "$help_create\n";
1000 die "$help_status\n";
1004 die "$help_enable\n";
1008 die "$help_enable\n";
1014 my $err = GetOptions
('dest=s' => \
$dest,
1015 'source=s' => \
$source,
1016 'verbose' => \
$verbose,
1017 'interval=i' => \
$interval,
1018 'limit=i' => \
$limit,
1019 'maxsnap=i' => \
$maxsnap,
1024 die "can't parse options\n";
1028 $param->{dest
} = $dest;
1029 $param->{source
} = $source;
1030 $param->{verbose
} = $verbose;
1031 $param->{interval
} = $interval;
1032 $param->{limit
} = $limit;
1033 $param->{maxsnap
} = $maxsnap;
1034 $param->{name
} = $name;
1035 $param->{skip
} = $skip;
1040 die "$help_destroy\n" if !$source;
1041 check_target
($source);
1046 die "$help_sync\n" if !$source || !$dest;
1047 check_target
($source);
1048 check_target
($dest);
1053 die "$help_create\n" if !$source || !$dest;
1054 check_target
($source);
1055 check_target
($dest);
1068 my $help_command = $ARGV[1];
1069 if ($help_command && $commands->{$help_command}) {
1070 print help
($help_command);
1073 exec("man $PROGNAME");
1080 die "$help_enable\n" if !$source;
1081 check_target
($source);
1086 die "$help_disable\n" if !$source;
1087 check_target
($source);
1095 print("ERROR:\tno command specified\n") if !$help;
1096 print("USAGE:\t$PROGNAME <COMMAND> [ARGS] [OPTIONS]\n");
1097 print("\t$PROGNAME help [<cmd>] [OPTIONS]\n\n");
1098 print("\t$PROGNAME create -dest <string> -source <string> [OPTIONS]\n");
1099 print("\t$PROGNAME destroy -source <string> [OPTIONS]\n");
1100 print("\t$PROGNAME disable -source <string> [OPTIONS]\n");
1101 print("\t$PROGNAME enable -source <string> [OPTIONS]\n");
1102 print("\t$PROGNAME list\n");
1103 print("\t$PROGNAME status\n");
1104 print("\t$PROGNAME sync -dest <string> -source <string> [OPTIONS]\n");
1112 if($target !~ m/(\d+.\d+.\d+.\d+:)?([\w\-\_\/]+)(\
/.+)?/){
1113 print("ERROR:\t$target is not valid.\n\tUse [IP:]<ZFSPool>[/Path]!\n");
1123 pve-zsync - PVE ZFS Replication Manager
1127 zfs-zsync <COMMAND> [ARGS] [OPTIONS]
1129 zfs-zsync help <cmd> [OPTIONS]
1131 Get help about specified command.
1139 Verbose output format.
1141 zfs-zsync create -dest <string> -source <string> [OPTIONS]
1147 the destination target is like [IP]:<Pool>[/Path]
1151 the interval in min in witch the zfs will sync, default is 15 min
1155 max sync speed in kBytes/s, default unlimited
1159 how much snapshots will be kept before get erased, default 1
1163 name of the sync job, if not set it is default
1167 if this flag is set it will skip the first sync
1171 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1173 zfs-zsync destroy -source <string> [OPTIONS]
1175 remove a sync Job from the scheduler
1179 name of the sync job, if not set it is default
1183 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1185 zfs-zsync disable -source <string> [OPTIONS]
1191 name of the sync job, if not set it is default
1195 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1197 zfs-zsync enable -source <string> [OPTIONS]
1199 enable a syncjob and reset error
1203 name of the sync job, if not set it is default
1207 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1210 Get a List of all scheduled Sync Jobs
1214 Get the status of all scheduled Sync Jobs
1216 zfs-zsync sync -dest <string> -source <string> [OPTIONS]
1222 the destination target is like [IP:]<Pool>[/Path]
1226 max sync speed in kBytes/s, default unlimited
1230 how much snapshots will be kept before get erased, default 1
1234 name of the sync job, if not set it is default.
1235 It is only necessary if scheduler allready contains this source.
1239 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1243 This Tool helps you to sync your VM or directory which stored on ZFS between 2 servers.
1244 This tool also has the capability to add jobs to cron so the sync will be automatically done.
1246 =head2 PVE ZFS Storage sync Tool
1248 This Tool can get remote pool on other PVE or send Pool to others ZFS machines
1252 add sync job from local VM to remote ZFS Server
1253 zfs-zsync -source=100 -dest=192.168.1.2:zfspool
1255 =head1 IMPORTANT FILES
1257 Where the cron jobs are stored /etc/cron.d/pve-zsync
1258 Where the VM config get copied on the destination machine /var/pve-zsync
1259 Where the config is stored /var/pve-zsync
1261 Copyright (C) 2007-2015 Proxmox Server Solutions GmbH
1263 This program is free software: you can redistribute it and/or modify it
1264 under the terms of the GNU Affero General Public License as published
1265 by the Free Software Foundation, either version 3 of the License, or
1266 (at your option) any later version.
1268 This program is distributed in the hope that it will be useful, but
1269 WITHOUT ANY WARRANTY; without even the implied warranty of
1270 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1271 Affero General Public License for more details.
1273 You should have received a copy of the GNU Affero General Public
1274 License along with this program. If not, see
1275 <http://www.gnu.org/licenses/>.