]>
git.proxmox.com Git - pve-zsync.git/blob - pve-zsync
ed65947b7c281849b979e19fa4f99a186f38e431
5 use Data
::Dumper
qw(Dumper);
6 use Fcntl
qw(:flock SEEK_END);
7 use Getopt
::Long
qw(GetOptionsFromArray);
8 use File
::Copy
qw(move);
13 my $PROGNAME = "pve-zsync";
14 my $CONFIG_PATH = "/var/lib/${PROGNAME}/";
15 my $STATE = "${CONFIG_PATH}sync_state";
16 my $CRONJOBS = "/etc/cron.d/$PROGNAME";
17 my $PATH = "/usr/sbin/";
18 my $QEMU_CONF = "/etc/pve/local/qemu-server/";
19 my $LOCKFILE = "$CONFIG_PATH${PROGNAME}.lock";
20 my $PROG_PATH = "$PATH${PROGNAME}";
24 check_bin
('cstream');
32 foreach my $p (split (/:/, $ENV{PATH
})) {
39 die "unable to find command '$bin'\n";
42 sub cut_target_width
{
43 my ($target, $max) = @_;
45 return $target if (length($target) <= $max);
46 my @spl = split('/', $target);
48 my $count = length($spl[@spl-1]);
49 return "..\/".substr($spl[@spl-1],($count-$max)+3 ,$count) if $count > $max;
51 $count += length($spl[0]) if @spl > 1;
52 return substr($spl[0], 0, $max-4-length
($spl[@spl-1]))."\/..\/".$spl[@spl-1] if $count > $max;
55 $rest = $max-$count if ($max-$count > 0);
57 return "$spl[0]".substr($target, length($spl[0]), $rest)."..\/".$spl[@spl-1];
62 flock($fh, LOCK_EX
) || die "Can't lock config - $!\n";
67 flock($fh, LOCK_UN
) || die "Can't unlock config- $!\n";
71 my ($source, $name, $status) = @_;
73 if ($status->{$source->{all
}}->{$name}->{status
}) {
80 sub check_pool_exsits
{
84 $cmd = "ssh root\@$target->{ip} " if $target->{ip
};
85 $cmd .= "zfs list $target->{all} -H";
99 my $errstr = "$text : is not a valid input! Use [IP:]<VMID> or [IP:]<ZFSPool>[/Path]";
102 $text =~ m/^((\d+.\d+.\d+.\d+):)?(.*)$/;
109 my @parts = split('/', $3);
111 die "$text $errstr\n" if !($target->{pool
} = shift(@parts));
112 die "$text $errstr\n" if $target->{pool
} =~ /^(\d+.\d+.\d+.\d+)$/;
114 if ($target->{pool
} =~ m/^\d+$/) {
115 $target->{vmid
} = $target->{pool
};
116 delete $target->{pool
};
119 return $target if (@parts == 0);
120 $target->{last_part
} = pop(@parts);
126 $target->{path
} = join('/', @parts);
134 #This is for the first use to init file;
136 my $new_fh = IO
::File-
>new("> $CRONJOBS");
137 die "Could not create $CRONJOBS: $!\n" if !$new_fh;
142 my $fh = IO
::File-
>new("< $CRONJOBS");
143 die "Could not open file $CRONJOBS: $!\n" if !$fh;
149 return encode_cron
(@text);
156 $param->{dest
} = undef;
157 $param->{source
} = undef;
158 $param->{verbose
} = undef;
159 $param->{limit
} = undef;
160 $param->{maxsnap
} = undef;
161 $param->{name
} = undef;
162 $param->{skip
} = undef;
163 $param->{method} = undef;
165 my ($ret, $ar) = GetOptionsFromArray
(\
@arg,
166 'dest=s' => \
$param->{dest
},
167 'source=s' => \
$param->{source
},
168 'verbose' => \
$param->{verbose
},
169 'limit=i' => \
$param->{limit
},
170 'maxsnap=i' => \
$param->{maxsnap
},
171 'name=s' => \
$param->{name
},
172 'skip' => \
$param->{skip
},
173 'method=s' => \
$param->{method});
176 die "can't parse options\n";
179 $param->{name
} = "default" if !$param->{name
};
180 $param->{maxsnap
} = 1 if !$param->{maxsnap
};
181 $param->{method} = "ssh" if !$param->{method};
186 sub add_state_to_job
{
189 my $states = read_state
();
190 my $state = $states->{$job->{source
}}->{$job->{name
}};
192 $job->{state} = $state->{state};
193 $job->{lsync
} = $state->{lsync
};
195 for (my $i = 0; $state->{"snap$i"}; $i++) {
196 $job->{"snap$i"} = $state->{"snap$i"};
207 while (my $line = shift(@text)) {
209 my @arg = split('\s', $line);
210 my $param = parse_argv
(@arg);
212 if ($param->{source
} && $param->{dest
}) {
213 $cfg->{$param->{source
}}->{$param->{name
}}->{dest
} = $param->{dest
};
214 $cfg->{$param->{source
}}->{$param->{name
}}->{verbose
} = $param->{verbose
};
215 $cfg->{$param->{source
}}->{$param->{name
}}->{limit
} = $param->{limit
};
216 $cfg->{$param->{source
}}->{$param->{name
}}->{maxsnap
} = $param->{maxsnap
};
217 $cfg->{$param->{source
}}->{$param->{name
}}->{skip
} = $param->{skip
};
218 $cfg->{$param->{source
}}->{$param->{name
}}->{method} = $param->{method};
230 my $source = parse_target
($param->{source
});
231 my $dest = parse_target
($param->{dest
}) if $param->{dest
};
233 $job->{name
} = !$param->{name
} ?
"default" : $param->{name
};
234 $job->{dest
} = $param->{dest
} if $param->{dest
};
235 $job->{method} = "local" if !$dest->{ip
} && !$source->{ip
};
236 $job->{method} = "ssh" if !$job->{method};
237 $job->{limit
} = $param->{limit
};
238 $job->{maxsnap
} = $param->{maxsnap
} if $param->{maxsnap
};
239 $job->{source
} = $param->{source
};
247 my $new_fh = IO
::File-
>new("> $STATE");
248 die "Could not create $STATE: $!\n" if !$new_fh;
254 my $fh = IO
::File-
>new("< $STATE");
255 die "Could not open file $STATE: $!\n" if !$fh;
258 my $states = decode_json
($text);
272 $in_fh = IO
::File-
>new("< $STATE");
273 die "Could not open file $STATE: $!\n" if !$in_fh;
278 my $out_fh = IO
::File-
>new("> $STATE.new");
279 die "Could not open file ${STATE}.new: $!\n" if !$out_fh;
284 $states = decode_json
($text);
285 $state = $states->{$job->{source
}}->{$job->{name
}};
288 if ($job->{state} ne "del") {
289 $state->{state} = $job->{state};
290 $state->{lsync
} = $job->{lsync
};
292 for (my $i = 0; $job->{"snap$i"} ; $i++) {
293 $state->{"snap$i"} = $job->{"snap$i"};
295 $states->{$job->{source
}}->{$job->{name
}} = $state;
298 delete $states->{$job->{source
}}->{$job->{name
}};
299 delete $states->{$job->{source
}} if !keys %{$states->{$job->{source
}}};
302 $text = encode_json
($states);
306 move
("$STATE.new", $STATE);
321 my $header = "SHELL=/bin/sh\n";
322 $header .= "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n\n";
324 my $fh = IO
::File-
>new("< $CRONJOBS");
325 die "Could not open file $CRONJOBS: $!\n" if !$fh;
330 while (my $line = shift(@test)) {
332 if ($line =~ m/source $job->{source} .*name $job->{name} /) {
334 next if $job->{state} eq "del";
335 $text .= format_job
($job, $line);
337 if (($line_no < 3) && ($line =~ /^(PATH|SHELL)/ )) {
346 $text = "$header$text";
350 $text .= format_job
($job);
352 my $new_fh = IO
::File-
>new("> ${CRONJOBS}.new");
353 die "Could not open file ${CRONJOBS}.new: $!\n" if !$new_fh;
355 die "can't write to $CRONJOBS.new\n" if !print($new_fh $text);
358 die "can't move $CRONJOBS.new: $!\n" if !move
("${CRONJOBS}.new", "$CRONJOBS");
363 my ($job, $line) = @_;
366 if ($job->{state} eq "stopped") {
370 $line =~ /^#*(.+) root/;
373 $text .= "*/$INTERVAL * * * *";
376 $text .= " $PROGNAME sync --source $job->{source} --dest $job->{dest}";
377 $text .= " --name $job->{name} --maxsnap $job->{maxsnap}";
378 $text .= " --method $job->{method}";
379 $text .= " --verbose" if $job->{verbose
};
387 my $cfg = read_cron
();
389 my $list = sprintf("%-25s%-15s%-7s%-20s%-5s\n" , "SOURCE", "NAME", "STATE", "LAST SYNC", "TYPE");
391 my $states = read_state
();
392 foreach my $source (sort keys%{$cfg}) {
393 foreach my $name (sort keys%{$cfg->{$source}}) {
394 $list .= sprintf("%-25s", cut_target_width
($source, 25));
395 $list .= sprintf("%-15s", cut_target_width
($name, 15));
396 $list .= sprintf("%-7s", $states->{$source}->{$name}->{state});
397 $list .= sprintf("%-20s",$states->{$source}->{$name}->{lsync
});
398 $list .= sprintf("%-5s\n",$cfg->{$source}->{$name}->{method});
409 $cmd = "ssh root\@$target->{ip} " if ($target->{ip
});
410 $cmd .= "qm status $target->{vmid}";
412 my $res = run_cmd
($cmd);
414 return 1 if ($res =~ m/^status.*$/);
421 my $cfg = read_cron
();
423 my $job = param_to_job
($param);
425 $job->{state} = "ok";
428 my $source = parse_target
($param->{source
});
429 my $dest = parse_target
($param->{dest
});
431 if (my $ip = $dest->{ip
}) {
432 run_cmd
("ssh-copy-id -i /root/.ssh/id_rsa.pub root\@$ip");
435 if (my $ip = $source->{ip
}) {
436 run_cmd
("ssh-copy-id -i /root/.ssh/id_rsa.pub root\@$ip");
439 die "Pool $dest->{all} does not exists\n" if check_pool_exsits
($dest);
441 my $check = check_pool_exsits
($source->{path
}, $source->{ip
}) if !$source->{vmid
} && $source->{path
};
443 die "Pool $source->{path} does not exists\n" if undef($check);
445 die "VM $source->{vmid} doesn't exist\n" if $param->{vmid
} && !vm_exists
($source);
447 die "Config already exists\n" if $cfg->{$job->{source
}}->{$job->{name
}};
453 sync
($param) if !$param->{skip
};
464 my $cfg = read_cron
();
466 if (!$cfg->{$param->{source
}}->{$param->{name
}}) {
467 die "Job with source $param->{source} and name $param->{name} does not exist\n" ;
469 my $job = $cfg->{$param->{source
}}->{$param->{name
}};
470 $job->{name
} = $param->{name
};
471 $job->{source
} = $param->{source
};
472 $job = add_state_to_job
($job);
480 my $job = get_job
($param);
481 $job->{state} = "del";
490 my $lock_fh = IO
::File-
>new("> $LOCKFILE");
491 die "Can't open Lock File: $LOCKFILE $!\n" if !$lock_fh;
494 my $date = get_date
();
497 $job = get_job
($param);
500 if ($job && $job->{state} eq "syncing") {
501 die "Job --source $param->{source} --name $param->{name} is syncing at the moment";
504 my $dest = parse_target
($param->{dest
});
505 my $source = parse_target
($param->{source
});
507 my $sync_path = sub {
508 my ($source, $dest, $job, $param, $date) = @_;
510 ($source->{old_snap
},$source->{last_snap
}) = snapshot_get
($source, $dest, $param->{maxsnap
}, $param->{name
});
512 snapshot_add
($source, $dest, $param->{name
}, $date);
514 send_image
($source, $dest, $param);
516 snapshot_destroy
($source, $dest, $param->{method}, $source->{old_snap
}) if ($source->{destroy
} && $source->{old_snap
});
521 $job->{state} = "syncing";
526 if ($source->{vmid
}) {
527 die "VM $source->{vmid} doesn't exist\n" if !vm_exists
($source);
528 my $disks = get_disks
($source);
530 foreach my $disk (sort keys %{$disks}) {
531 $source->{all
} = $disks->{$disk}->{all
};
532 $source->{pool
} = $disks->{$disk}->{pool
};
533 $source->{path
} = $disks->{$disk}->{path
} if $disks->{$disk}->{path
};
534 $source->{last_part
} = $disks->{$disk}->{last_part
};
535 &$sync_path($source, $dest, $job, $param, $date);
537 if ($param->{method} eq "ssh") {
538 send_config
($source, $dest,'ssh');
541 &$sync_path($source, $dest, $job, $param, $date);
546 $job->{state} = "error";
550 print "Job --source $param->{source} --name $param->{name} got an ERROR!!!\nERROR Message:\n";
556 $job->{state} = "ok";
557 $job->{lsync
} = $date;
566 my ($source, $dest, $max_snap, $name) = @_;
568 my $cmd = "zfs list -r -t snapshot -Ho name, -S creation ";
570 $cmd .= $source->{all
};
571 $cmd = "ssh root\@$source->{ip} ".$cmd if $source->{ip
};
573 my $raw = run_cmd
($cmd);
576 my $last_snap = undef;
579 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
581 if ($line =~ m/(rep_$name.*)$/) {
582 $last_snap = $1 if (!$last_snap);
585 if ($index == $max_snap) {
586 $source->{destroy
} = 1;
592 return ($old_snap, $last_snap) if $last_snap;
598 my ($source, $dest, $name, $date) = @_;
600 my $snap_name = "rep_$name\_".$date;
602 $source->{new_snap
} = $snap_name;
604 my $path = "$source->{all}\@$snap_name";
606 my $cmd = "zfs snapshot $path";
607 $cmd = "ssh root\@$source->{ip} $cmd" if $source->{ip
};
614 snapshot_destroy
($source, $dest, 'ssh', $snap_name);
622 my $text = "SHELL=/bin/sh\n";
623 $text .= "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n";
625 my $fh = IO
::File-
>new("> $CRONJOBS");
626 die "Could not open file: $!\n" if !$fh;
628 foreach my $source (sort keys%{$cfg}) {
629 foreach my $sync_name (sort keys%{$cfg->{$source}}) {
630 next if $cfg->{$source}->{$sync_name}->{status
} ne 'ok';
631 $text .= "$PROG_PATH sync";
632 $text .= " -source ";
633 if ($cfg->{$source}->{$sync_name}->{vmid
}) {
634 $text .= "$cfg->{$source}->{$sync_name}->{source_ip}:" if $cfg->{$source}->{$sync_name}->{source_ip
};
635 $text .= "$cfg->{$source}->{$sync_name}->{vmid} ";
637 $text .= "$cfg->{$source}->{$sync_name}->{source_ip}:" if $cfg->{$source}->{$sync_name}->{source_ip
};
638 $text .= "$cfg->{$source}->{$sync_name}->{source_pool}";
639 $text .= "$cfg->{$source}->{$sync_name}->{source_path}" if $cfg->{$source}->{$sync_name}->{source_path
};
642 $text .= "$cfg->{$source}->{$sync_name}->{dest_ip}:" if $cfg->{$source}->{$sync_name}->{dest_ip
};
643 $text .= "$cfg->{$source}->{$sync_name}->{dest_pool}";
644 $text .= "$cfg->{$source}->{$sync_name}->{dest_path}" if $cfg->{$source}->{$sync_name}->{dest_path
};
645 $text .= " -name $sync_name ";
646 $text .= " -limit $cfg->{$source}->{$sync_name}->{limit}" if $cfg->{$source}->{$sync_name}->{limit
};
647 $text .= " -maxsnap $cfg->{$source}->{$sync_name}->{maxsnap}" if $cfg->{$source}->{$sync_name}->{maxsnap
};
651 die "Can't write to cron\n" if (!print($fh $text));
659 $cmd = "ssh root\@$target->{ip} " if $target->{ip
};
660 $cmd .= "qm config $target->{vmid}";
662 my $res = run_cmd
($cmd);
664 my $disks = parse_disks
($res, $target->{ip
});
671 print "Start CMD\n" if $DEBUG;
672 print Dumper
$cmd if $DEBUG;
673 my $output = `$cmd 2>&1`;
675 die "COMMAND:\n\t$cmd\nGET ERROR:\n\t$output" if 0 != $?;
678 print Dumper
$output if $DEBUG;
679 print "END CMD\n" if $DEBUG;
684 my ($text, $ip) = @_;
689 while ($text && $text =~ s/^(.*?)(\n|$)//) {
693 my $is_disk = $line =~ m/^(virtio|ide|scsi|sata){1}\d+: /;
694 if($line =~ m/^(virtio\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
697 } elsif($line =~ m/^(ide\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
700 } elsif($line =~ m/^(scsi\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
703 } elsif($line =~ m/^(sata\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
708 die "disk is not on ZFS Storage\n" if $is_disk && !$disk && $line !~ m/cdrom/;
710 if($disk && $line !~ m/none/ && $line !~ m/cdrom/ ) {
712 $cmd .= "ssh root\@$ip " if $ip;
713 $cmd .= "pvesm path $stor$disk";
714 my $path = run_cmd
($cmd);
716 if ($path =~ m/^\/dev\
/zvol\/(\w
+).*(\
/$disk)$/) {
718 my @array = split('/', $1);
719 $disks->{$num}->{pool
} = pop(@array);
720 $disks->{$num}->{all
} = $disks->{$num}->{pool
};
722 $disks->{$num}->{path
} = join('/', @array);
723 $disks->{$num}->{all
} .= "\/$disks->{$num}->{path}";
725 $disks->{$num}->{last_part
} = $disk;
726 $disks->{$num}->{all
} .= "\/$disk";
731 die "ERROR: in path\n";
739 sub snapshot_destroy
{
740 my ($source, $dest, $method, $snap) = @_;
742 my $zfscmd = "zfs destroy ";
743 my $snapshot = "$source->{all}\@$snap";
746 if($source->{ip
} && $method eq 'ssh'){
747 run_cmd
("ssh root\@$source->{ip} $zfscmd $snapshot");
749 run_cmd
("$zfscmd $snapshot");
756 my $ssh = $dest->{ip
} ?
"ssh root\@$dest->{ip}" : "";
758 my $path = "$dest->{all}\/$source->{last_part}";
761 run_cmd
("$ssh $zfscmd $path\@$snap ");
770 my ($source ,$dest, $method) = @_;
773 $cmd = "ssh root\@$dest->{ip} " if $dest->{ip
};
774 $cmd .= "zfs list -rt snapshot -Ho name $dest->{all}";
775 $cmd .= "\/$source->{last_part}\@$source->{old_snap}";
778 eval {$text =run_cmd
($cmd);};
784 while ($text && $text =~ s/^(.*?)(\n|$)//) {
786 return 1 if $line =~ m/^.*$source->{old_snap}$/;
791 my ($source, $dest, $param) = @_;
795 $cmd .= "ssh root\@$source->{ip} " if $source->{ip
};
797 $cmd .= "-v " if $param->{verbose
};
799 if($source->{last_snap
} && snapshot_exist
($source ,$dest, $param->{method})) {
800 $cmd .= "-i $source->{all}\@$source->{last_snap} $source->{all}\@$source->{new_snap} ";
802 $cmd .= "$source->{all}\@$source->{new_snap} ";
805 if ($param->{limit
}){
806 my $bwl = $param->{limit
}*1024;
807 $cmd .= "| cstream -t $bwl";
810 $cmd .= "ssh root\@$dest->{ip} " if $dest->{ip
};
811 $cmd .= "zfs recv $dest->{all}";
812 $cmd .= "\/$source->{last_part}\@$source->{new_snap}";
819 snapshot_destroy
($source, undef, $param->{method}, $source->{new_snap
});
826 my ($source, $dest, $method) = @_;
828 my $source_target ="$QEMU_CONF$source->{vmid}.conf";
829 my $dest_target_new ="$CONFIG_PATH$source->{vmid}.conf.$source->{new_snap}";
831 if ($method eq 'ssh'){
832 if ($dest->{ip
} && $source->{ip
}) {
833 run_cmd
("ssh root\@$dest->{ip} mkdir $CONFIG_PATH -p");
834 run_cmd
("scp root\@$source->{ip}:$source_target root\@$dest->{ip}:$dest_target_new");
835 } elsif ($dest->{ip
}) {
836 run_cmd
("ssh root\@$dest->{ip} mkdir $CONFIG_PATH -p");
837 run_cmd
("scp $source_target root\@$dest->{ip}:$dest_target_new");
838 } elsif ($source->{ip
}) {
839 run_cmd
("mkdir $CONFIG_PATH -p");
840 run_cmd
("scp root\@$source->{ip}:$source_target $dest_target_new");
843 if ($source->{destroy
}){
844 my $dest_target_old ="$CONFIG_PATH$source->{vmid}.conf.$source->{old_snap}";
846 run_cmd
("ssh root\@$dest->{ip} rm -f $dest_target_old");
848 run_cmd
("rm -f $dest_target_old");
855 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
856 my $datestamp = sprintf ("%04d-%02d-%02d_%02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
862 my $cfg = read_cron
();
864 my $status_list = sprintf("%-25s%-15s%-10s\n", "SOURCE", "NAME", "STATUS");
866 my $states = read_state
();
868 foreach my $source (sort keys%{$cfg}) {
869 foreach my $sync_name (sort keys%{$cfg->{$source}}) {
870 $status_list .= sprintf("%-25s", cut_target_width
($source, 25));
871 $status_list .= sprintf("%-15s", cut_target_width
($sync_name, 25));
872 $status_list .= "$states->{$source}->{$sync_name}->{state}\n";
882 my $job = get_job
($param);
883 $job->{state} = "ok";
891 my $job = get_job
($param);
892 $job->{state} = "stopped";
897 my $command = $ARGV[0];
899 my $commands = {'destroy' => 1,
908 if (!$command || !$commands->{$command}) {
913 my $help_sync = "$PROGNAME sync -dest <string> -source <string> [OPTIONS]\n
914 \twill sync one time\n
916 \t\tthe destination target is like [IP:]<Pool>[/Path]\n
918 \t\tmax sync speed in kBytes/s, default unlimited\n
919 \t-maxsnap\tinteger\n
920 \t\thow much snapshots will be kept before get erased, default 1/n
922 \t\tname of the sync job, if not set it is default.
923 \tIt is only necessary if scheduler allready contains this source.\n
925 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
927 my $help_create = "$PROGNAME create -dest <string> -source <string> [OPTIONS]/n
928 \tCreate a sync Job\n
930 \t\tthe destination target is like [IP]:<Pool>[/Path]\n
932 \t\tmax sync speed in kBytes/s, default unlimited\n
934 \t\thow much snapshots will be kept before get erased, default 1\n
936 \t\tname of the sync job, if not set it is default\n
938 \t\tif this flag is set it will skip the first sync\n
940 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
942 my $help_destroy = "$PROGNAME destroy -source <string> [OPTIONS]\n
943 \tremove a sync Job from the scheduler\n
945 \t\tname of the sync job, if not set it is default\n
947 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
949 my $help_help = "$PROGNAME help <cmd> [OPTIONS]\n
950 \tGet help about specified command.\n
953 \t-verbose\tboolean\n
954 \t\tVerbose output format.\n";
956 my $help_list = "$PROGNAME list\n
957 \tGet a List of all scheduled Sync Jobs\n";
959 my $help_status = "$PROGNAME status\n
960 \tGet the status of all scheduled Sync Jobs\n";
962 my $help_enable = "$PROGNAME enable -source <string> [OPTIONS]\n
963 \tenable a syncjob and reset error\n
965 \t\tname of the sync job, if not set it is default\n
967 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
969 my $help_disable = "$PROGNAME disable -source <string> [OPTIONS]\n
972 \t\tname of the sync job, if not set it is default\n
974 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
990 die "$help_destroy\n";
994 die "$help_create\n";
1002 die "$help_status\n";
1006 die "$help_enable\n";
1010 die "$help_enable\n";
1017 my $param = parse_argv
(@arg);
1023 die "$help_destroy\n" if !$param->{source
};
1024 check_target
($param->{source
});
1025 destroy_job
($param);
1029 die "$help_sync\n" if !$param->{source
} || !$param->{dest
};
1030 check_target
($param->{source
});
1031 check_target
($param->{dest
});
1036 die "$help_create\n" if !$param->{source
} || !$param->{dest
};
1037 check_target
($param->{source
});
1038 check_target
($param->{dest
});
1051 my $help_command = $ARGV[1];
1052 if ($help_command && $commands->{$help_command}) {
1053 print help
($help_command);
1055 if ($param->{verbose
} == 1){
1056 exec("man $PROGNAME");
1063 die "$help_enable\n" if !$param->{source
};
1064 check_target
($param->{source
});
1069 die "$help_disable\n" if !$param->{source
};
1070 check_target
($param->{source
});
1071 disable_job
($param);
1078 print("ERROR:\tno command specified\n") if !$help;
1079 print("USAGE:\t$PROGNAME <COMMAND> [ARGS] [OPTIONS]\n");
1080 print("\t$PROGNAME help [<cmd>] [OPTIONS]\n\n");
1081 print("\t$PROGNAME create -dest <string> -source <string> [OPTIONS]\n");
1082 print("\t$PROGNAME destroy -source <string> [OPTIONS]\n");
1083 print("\t$PROGNAME disable -source <string> [OPTIONS]\n");
1084 print("\t$PROGNAME enable -source <string> [OPTIONS]\n");
1085 print("\t$PROGNAME list\n");
1086 print("\t$PROGNAME status\n");
1087 print("\t$PROGNAME sync -dest <string> -source <string> [OPTIONS]\n");
1095 if($target !~ m/(\d+.\d+.\d+.\d+:)?([\w\-\_\/]+)(\
/.+)?/) {
1096 print("ERROR:\t$target is not valid.\n\tUse [IP:]<ZFSPool>[/Path]!\n");
1106 pve-zsync - PVE ZFS Replication Manager
1110 pve-zsync <COMMAND> [ARGS] [OPTIONS]
1112 pve-zsync help <cmd> [OPTIONS]
1114 Get help about specified command.
1122 Verbose output format.
1124 pve-zsync create -dest <string> -source <string> [OPTIONS]
1130 the destination target is like [IP]:<Pool>[/Path]
1134 max sync speed in kBytes/s, default unlimited
1138 how much snapshots will be kept before get erased, default 1
1142 name of the sync job, if not set it is default
1146 if this flag is set it will skip the first sync
1150 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1152 pve-zsync destroy -source <string> [OPTIONS]
1154 remove a sync Job from the scheduler
1158 name of the sync job, if not set it is default
1162 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1164 pve-zsync disable -source <string> [OPTIONS]
1170 name of the sync job, if not set it is default
1174 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1176 pve-zsync enable -source <string> [OPTIONS]
1178 enable a syncjob and reset error
1182 name of the sync job, if not set it is default
1186 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1189 Get a List of all scheduled Sync Jobs
1193 Get the status of all scheduled Sync Jobs
1195 pve-zsync sync -dest <string> -source <string> [OPTIONS]
1201 the destination target is like [IP:]<Pool>[/Path]
1205 max sync speed in kBytes/s, default unlimited
1209 how much snapshots will be kept before get erased, default 1
1213 name of the sync job, if not set it is default.
1214 It is only necessary if scheduler allready contains this source.
1218 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1222 This Tool helps you to sync your VM or directory which stored on ZFS between 2 servers.
1223 This tool also has the capability to add jobs to cron so the sync will be automatically done.
1224 The default syncing interval is set to 15 min, if you want to change this value you can do this in /etc/cron.d/pve-zsync.
1225 To config cron see man crontab.
1227 =head2 PVE ZFS Storage sync Tool
1229 This Tool can get remote pool on other PVE or send Pool to others ZFS machines
1233 add sync job from local VM to remote ZFS Server
1234 pve-zsync create -source=100 -dest=192.168.1.2:zfspool
1236 =head1 IMPORTANT FILES
1238 Cron jobs are stored at /etc/cron.d/pve-zsync
1240 The VM config get copied on the destination machine to /var/pve-zsync/
1242 The config is stored at /var/pve-zsync/
1244 =head1 COPYRIGHT AND DISCLAIMER
1246 Copyright (C) 2007-2015 Proxmox Server Solutions GmbH
1248 This program is free software: you can redistribute it and/or modify it
1249 under the terms of the GNU Affero General Public License as published
1250 by the Free Software Foundation, either version 3 of the License, or
1251 (at your option) any later version.
1253 This program is distributed in the hope that it will be useful, but
1254 WITHOUT ANY WARRANTY; without even the implied warranty of
1255 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1256 Affero General Public License for more details.
1258 You should have received a copy of the GNU Affero General Public
1259 License along with this program. If not, see
1260 <http://www.gnu.org/licenses/>.