]>
git.proxmox.com Git - pve-zsync.git/blob - pve-zsync
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;
253 my $fh = IO
::File-
>new("< $STATE");
254 die "Could not open file $STATE: $!\n" if !$fh;
257 my $states = decode_json
($text);
271 $in_fh = IO
::File-
>new("< $STATE");
272 die "Could not open file $STATE: $!\n" if !$in_fh;
277 my $out_fh = IO
::File-
>new("> $STATE.new");
278 die "Could not open file ${STATE}.new: $!\n" if !$out_fh;
283 $states = decode_json
($text);
284 $state = $states->{$job->{source
}}->{$job->{name
}};
287 if ($job->{state} ne "del") {
288 $state->{state} = $job->{state};
289 $state->{lsync
} = $job->{lsync
};
291 for (my $i = 0; $job->{"snap$i"} ; $i++) {
292 $state->{"snap$i"} = $job->{"snap$i"};
294 $states->{$job->{source
}}->{$job->{name
}} = $state;
297 delete $states->{$job->{source
}}->{$job->{name
}};
298 delete $states->{$job->{source
}} if !keys %{$states->{$job->{source
}}};
301 $text = encode_json
($states);
305 move
("$STATE.new", $STATE);
320 my $header = "SHELL=/bin/sh\n";
321 $header .= "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n\n";
323 my $fh = IO
::File-
>new("< $CRONJOBS");
324 die "Could not open file $CRONJOBS: $!\n" if !$fh;
329 while (my $line = shift(@test)) {
331 if ($line =~ m/source $job->{source} .*name $job->{name} /) {
333 next if $job->{state} eq "del";
334 $text .= format_job
($job, $line);
336 if (($line_no < 3) && ($line =~ /^(PATH|SHELL)/ )) {
345 $text = "$header$text";
349 $text .= format_job
($job);
351 my $new_fh = IO
::File-
>new("> ${CRONJOBS}.new");
352 die "Could not open file ${CRONJOBS}.new: $!\n" if !$new_fh;
354 die "can't write to $CRONJOBS.new\n" if !print($new_fh $text);
357 die "can't move $CRONJOBS.new: $!\n" if !move
("${CRONJOBS}.new", "$CRONJOBS");
362 my ($job, $line) = @_;
365 if ($job->{state} eq "stopped") {
369 $line =~ /^#*(.+) root/;
372 $text .= "*/$INTERVAL * * * *";
375 $text .= " $PROGNAME sync --source $job->{source} --dest $job->{dest}";
376 $text .= " --name $job->{name} --maxsnap $job->{maxsnap}";
377 $text .= " --method $job->{method}";
378 $text .= " --verbose" if $job->{verbose
};
386 my $cfg = read_cron
();
388 my $list = sprintf("%-25s%-15s%-7s%-20s%-5s\n" , "SOURCE", "NAME", "STATE", "LAST SYNC", "TYPE");
390 my $states = read_state
();
391 foreach my $source (sort keys%{$cfg}) {
392 foreach my $name (sort keys%{$cfg->{$source}}) {
393 $list .= sprintf("%-25s", cut_target_width
($source, 25));
394 $list .= sprintf("%-15s", cut_target_width
($name, 15));
395 $list .= sprintf("%-7s", $states->{$source}->{$name}->{state});
396 $list .= sprintf("%-20s",$states->{$source}->{$name}->{lsync
});
397 $list .= sprintf("%-5s\n",$cfg->{$source}->{$name}->{method});
408 $cmd = "ssh root\@$target->{ip} " if ($target->{ip
});
409 $cmd .= "qm status $target->{vmid}";
411 my $res = run_cmd
($cmd);
413 return 1 if ($res =~ m/^status.*$/);
420 my $cfg = read_cron
();
422 my $job = param_to_job
($param);
424 $job->{state} = "ok";
427 my $source = parse_target
($param->{source
});
428 my $dest = parse_target
($param->{dest
});
430 if (my $ip = $dest->{ip
}) {
431 run_cmd
("ssh-copy-id -i /root/.ssh/id_rsa.pub root\@$ip");
434 if (my $ip = $source->{ip
}) {
435 run_cmd
("ssh-copy-id -i /root/.ssh/id_rsa.pub root\@$ip");
438 die "Pool $dest->{all} does not exists\n" if check_pool_exsits
($dest);
440 my $check = check_pool_exsits
($source->{path
}, $source->{ip
}) if !$source->{vmid
} && $source->{path
};
442 die "Pool $source->{path} does not exists\n" if undef($check);
444 die "VM $source->{vmid} doesn't exist\n" if $param->{vmid
} && !vm_exists
($source);
446 die "Config already exists\n" if $cfg->{$job->{source
}}->{$job->{name
}};
452 sync
($param) if !$param->{skip
};
463 my $cfg = read_cron
();
465 if (!$cfg->{$param->{source
}}->{$param->{name
}}) {
466 die "Job with source $param->{source} and name $param->{name} does not exist\n" ;
468 my $job = $cfg->{$param->{source
}}->{$param->{name
}};
469 $job->{name
} = $param->{name
};
470 $job->{source
} = $param->{source
};
471 $job = add_state_to_job
($job);
479 my $job = get_job
($param);
480 $job->{state} = "del";
489 my $lock_fh = IO
::File-
>new("> $LOCKFILE");
490 die "Can't open Lock File: $LOCKFILE $!\n" if !$lock_fh;
493 my $date = get_date
();
496 $job = get_job
($param);
499 if ($job && $job->{state} ne "ok") {
500 print "To reset error state use $PROGNAME enable\n" if $job->{state} eq "error" ;
501 die "Sync will not done! Status: $job->{state}\n";
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
});
513 snapshot_add
($source, $dest, $param->{name
}, $date);
515 send_image
($source, $dest, $param);
517 snapshot_destroy
($source, $dest, $param->{method}, $source->{old_snap
}) if ($source->{destroy
} && $source->{old_snap
});
521 $job->{state} = "error";
531 die "Job --source $param->{source} --name $param->{name} is syncing" if $job->{state} eq "syncing";
532 $job->{state} = "syncing";
536 if ($source->{vmid
}) {
537 die "VM $source->{vmid} doesn't exist\n" if !vm_exists
($source);
538 my $disks = get_disks
($source);
540 foreach my $disk (sort keys %{$disks}) {
541 $source->{all
} = $disks->{$disk}->{all
};
542 $source->{pool
} = $disks->{$disk}->{pool
};
543 $source->{path
} = $disks->{$disk}->{path
} if $disks->{$disk}->{path
};
544 $source->{last_part
} = $disks->{$disk}->{last_part
};
545 &$sync_path($source, $dest, $job, $param, $date);
547 if ($param->{method} eq "ssh") {
548 send_config
($source, $dest,'ssh');
551 &$sync_path($source, $dest, $job, $param, $date);
555 $job->{state} = "ok";
556 $job->{lsync
} = $date;
565 my ($source, $dest, $max_snap, $name) = @_;
567 my $cmd = "zfs list -r -t snapshot -Ho name, -S creation ";
569 $cmd .= $source->{all
};
570 $cmd = "ssh root\@$source->{ip} ".$cmd if $source->{ip
};
572 my $raw = run_cmd
($cmd);
575 my $last_snap = undef;
578 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
580 if ($line =~ m/(rep_$name.*)$/) {
581 $last_snap = $1 if (!$last_snap);
584 if ($index == $max_snap) {
585 $source->{destroy
} = 1;
591 return ($old_snap, $last_snap) if $last_snap;
597 my ($source, $dest, $name, $date) = @_;
599 my $snap_name = "rep_$name\_".$date;
601 $source->{new_snap
} = $snap_name;
603 my $path = "$source->{all}\@$snap_name";
605 my $cmd = "zfs snapshot $path";
606 $cmd = "ssh root\@$source->{ip} $cmd" if $source->{ip
};
613 snapshot_destroy
($source, $dest, 'ssh', $snap_name);
621 my $text = "SHELL=/bin/sh\n";
622 $text .= "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n";
624 my $fh = IO
::File-
>new("> $CRONJOBS");
625 die "Could not open file: $!\n" if !$fh;
627 foreach my $source (sort keys%{$cfg}) {
628 foreach my $sync_name (sort keys%{$cfg->{$source}}) {
629 next if $cfg->{$source}->{$sync_name}->{status
} ne 'ok';
630 $text .= "$PROG_PATH sync";
631 $text .= " -source ";
632 if ($cfg->{$source}->{$sync_name}->{vmid
}) {
633 $text .= "$cfg->{$source}->{$sync_name}->{source_ip}:" if $cfg->{$source}->{$sync_name}->{source_ip
};
634 $text .= "$cfg->{$source}->{$sync_name}->{vmid} ";
636 $text .= "$cfg->{$source}->{$sync_name}->{source_ip}:" if $cfg->{$source}->{$sync_name}->{source_ip
};
637 $text .= "$cfg->{$source}->{$sync_name}->{source_pool}";
638 $text .= "$cfg->{$source}->{$sync_name}->{source_path}" if $cfg->{$source}->{$sync_name}->{source_path
};
641 $text .= "$cfg->{$source}->{$sync_name}->{dest_ip}:" if $cfg->{$source}->{$sync_name}->{dest_ip
};
642 $text .= "$cfg->{$source}->{$sync_name}->{dest_pool}";
643 $text .= "$cfg->{$source}->{$sync_name}->{dest_path}" if $cfg->{$source}->{$sync_name}->{dest_path
};
644 $text .= " -name $sync_name ";
645 $text .= " -limit $cfg->{$source}->{$sync_name}->{limit}" if $cfg->{$source}->{$sync_name}->{limit
};
646 $text .= " -maxsnap $cfg->{$source}->{$sync_name}->{maxsnap}" if $cfg->{$source}->{$sync_name}->{maxsnap
};
650 die "Can't write to cron\n" if (!print($fh $text));
658 $cmd = "ssh root\@$target->{ip} " if $target->{ip
};
659 $cmd .= "qm config $target->{vmid}";
661 my $res = run_cmd
($cmd);
663 my $disks = parse_disks
($res, $target->{ip
});
670 print "Start CMD\n" if $DEBUG;
671 print Dumper
$cmd if $DEBUG;
672 my $output = `$cmd 2>&1`;
674 die $output if 0 != $?;
677 print Dumper
$output if $DEBUG;
678 print "END CMD\n" if $DEBUG;
683 my ($text, $ip) = @_;
688 while ($text && $text =~ s/^(.*?)(\n|$)//) {
692 my $is_disk = $line =~ m/^(virtio|ide|scsi|sata){1}\d+: /;
693 if($line =~ m/^(virtio\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
696 } elsif($line =~ m/^(ide\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
699 } elsif($line =~ m/^(scsi\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
702 } elsif($line =~ m/^(sata\d+: )(.+:)([A-Za-z0-9\-]+),(.*)$/) {
707 die "disk is not on ZFS Storage\n" if $is_disk && !$disk && $line !~ m/cdrom/;
709 if($disk && $line !~ m/none/ && $line !~ m/cdrom/ ) {
711 $cmd .= "ssh root\@$ip " if $ip;
712 $cmd .= "pvesm path $stor$disk";
713 my $path = run_cmd
($cmd);
715 if ($path =~ m/^\/dev\
/zvol\/(\w
+).*(\
/$disk)$/) {
717 my @array = split('/', $1);
718 $disks->{$num}->{pool
} = pop(@array);
719 $disks->{$num}->{all
} = $disks->{$num}->{pool
};
721 $disks->{$num}->{path
} = join('/', @array);
722 $disks->{$num}->{all
} .= "\/$disks->{$num}->{path}";
724 $disks->{$num}->{last_part
} = $disk;
725 $disks->{$num}->{all
} .= "\/$disk";
730 die "ERROR: in path\n";
738 sub snapshot_destroy
{
739 my ($source, $dest, $method, $snap) = @_;
741 my $zfscmd = "zfs destroy ";
742 my $snapshot = "$source->{all}\@$snap";
745 if($source->{ip
} && $method eq 'ssh'){
746 run_cmd
("ssh root\@$source->{ip} $zfscmd $snapshot");
748 run_cmd
("$zfscmd $snapshot");
755 my $ssh = $dest->{ip
} ?
"ssh root\@$dest->{ip}" : "";
757 my $path = "$dest->{all}\/$source->{last_part}";
760 run_cmd
("$ssh $zfscmd $path\@$snap ");
769 my ($source ,$dest, $method) = @_;
772 $cmd = "ssh root\@$dest->{ip} " if $dest->{ip
};
773 $cmd .= "zfs list -rt snapshot -Ho name $dest->{all}";
774 $cmd .= "\/$source->{last_part}\@$source->{old_snap}";
777 eval {$text =run_cmd
($cmd);};
783 while ($text && $text =~ s/^(.*?)(\n|$)//) {
785 return 1 if $line =~ m/^.*$source->{old_snap}$/;
790 my ($source, $dest, $param) = @_;
794 $cmd .= "ssh root\@$source->{ip} " if $source->{ip
};
796 $cmd .= "-v " if $param->{verbose
};
798 if($source->{last_snap
} && snapshot_exist
($source ,$dest, $param->{method})) {
799 $cmd .= "-i $source->{all}\@$source->{old_snap} $source->{all}\@$source->{new_snap} ";
801 $cmd .= "$source->{all}\@$source->{new_snap} ";
804 if ($param->{limit
}){
805 my $bwl = $param->{limit
}*1024;
806 $cmd .= "| cstream -t $bwl";
809 $cmd .= "ssh root\@$dest->{ip} " if $dest->{ip
};
810 $cmd .= "zfs recv $dest->{all}";
811 $cmd .= "\/$source->{last_part}\@$source->{new_snap}";
818 snapshot_destroy
($source, undef, $param->{method}, $source->{new_snap
});
825 my ($source, $dest, $method) = @_;
827 my $source_target ="$QEMU_CONF$source->{vmid}.conf";
828 my $dest_target_new ="$CONFIG_PATH$source->{vmid}.conf.$source->{new_snap}";
830 if ($method eq 'ssh'){
831 if ($dest->{ip
} && $source->{ip
}) {
832 run_cmd
("ssh root\@$dest->{ip} mkdir $CONFIG_PATH -p");
833 run_cmd
("scp root\@$source->{ip}:$source_target root\@$dest->{ip}:$dest_target_new");
834 } elsif ($dest->{ip
}) {
835 run_cmd
("ssh root\@$dest->{ip} mkdir $CONFIG_PATH -p");
836 run_cmd
("scp $source_target root\@$dest->{ip}:$dest_target_new");
837 } elsif ($source->{ip
}) {
838 run_cmd
("mkdir $CONFIG_PATH -p");
839 run_cmd
("scp root\@$source->{ip}:$source_target $dest_target_new");
842 if ($source->{destroy
}){
843 my $dest_target_old ="$CONFIG_PATH$source->{vmid}.conf.$source->{old_snap}";
845 run_cmd
("ssh root\@$dest->{ip} rm -f $dest_target_old");
847 run_cmd
("rm -f $dest_target_old");
854 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
855 my $datestamp = sprintf ("%04d-%02d-%02d_%02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
861 my $cfg = read_cron
();
863 my $status_list = sprintf("%-25s%-15s%-10s\n", "SOURCE", "NAME", "STATUS");
865 my $states = read_state
();
867 foreach my $source (sort keys%{$cfg}) {
868 foreach my $sync_name (sort keys%{$cfg->{$source}}) {
869 $status_list .= sprintf("%-25s", cut_target_width
($source, 25));
870 $status_list .= sprintf("%-15s", cut_target_width
($sync_name, 25));
871 $status_list .= "$states->{$source}->{$sync_name}->{state}\n";
881 my $job = get_job
($param);
882 $job->{state} = "ok";
890 my $job = get_job
($param);
891 $job->{state} = "stopped";
896 my $command = $ARGV[0];
898 my $commands = {'destroy' => 1,
907 if (!$command || !$commands->{$command}) {
912 my $help_sync = "$PROGNAME sync -dest <string> -source <string> [OPTIONS]\n
913 \twill sync one time\n
915 \t\tthe destination target is like [IP:]<Pool>[/Path]\n
917 \t\tmax sync speed in kBytes/s, default unlimited\n
918 \t-maxsnap\tinteger\n
919 \t\thow much snapshots will be kept before get erased, default 1/n
921 \t\tname of the sync job, if not set it is default.
922 \tIt is only necessary if scheduler allready contains this source.\n
924 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
926 my $help_create = "$PROGNAME create -dest <string> -source <string> [OPTIONS]/n
927 \tCreate a sync Job\n
929 \t\tthe destination target is like [IP]:<Pool>[/Path]\n
931 \t\tmax sync speed in kBytes/s, default unlimited\n
933 \t\thow much snapshots will be kept before get erased, default 1\n
935 \t\tname of the sync job, if not set it is default\n
937 \t\tif this flag is set it will skip the first sync\n
939 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
941 my $help_destroy = "$PROGNAME destroy -source <string> [OPTIONS]\n
942 \tremove a sync Job from the scheduler\n
944 \t\tname of the sync job, if not set it is default\n
946 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
948 my $help_help = "$PROGNAME help <cmd> [OPTIONS]\n
949 \tGet help about specified command.\n
952 \t-verbose\tboolean\n
953 \t\tVerbose output format.\n";
955 my $help_list = "$PROGNAME list\n
956 \tGet a List of all scheduled Sync Jobs\n";
958 my $help_status = "$PROGNAME status\n
959 \tGet the status of all scheduled Sync Jobs\n";
961 my $help_enable = "$PROGNAME enable -source <string> [OPTIONS]\n
962 \tenable a syncjob and reset error\n
964 \t\tname of the sync job, if not set it is default\n
966 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
968 my $help_disable = "$PROGNAME disable -source <string> [OPTIONS]\n
971 \t\tname of the sync job, if not set it is default\n
973 \t\tthe source can be an <VMID> or [IP:]<ZFSPool>[/Path]\n";
989 die "$help_destroy\n";
993 die "$help_create\n";
1001 die "$help_status\n";
1005 die "$help_enable\n";
1009 die "$help_enable\n";
1016 my $param = parse_argv
(@arg);
1022 die "$help_destroy\n" if !$param->{source
};
1023 check_target
($param->{source
});
1024 destroy_job
($param);
1028 die "$help_sync\n" if !$param->{source
} || !$param->{dest
};
1029 check_target
($param->{source
});
1030 check_target
($param->{dest
});
1035 die "$help_create\n" if !$param->{source
} || !$param->{dest
};
1036 check_target
($param->{source
});
1037 check_target
($param->{dest
});
1050 my $help_command = $ARGV[1];
1051 if ($help_command && $commands->{$help_command}) {
1052 print help
($help_command);
1054 if ($param->{verbose
} == 1){
1055 exec("man $PROGNAME");
1062 die "$help_enable\n" if !$param->{source
};
1063 check_target
($param->{source
});
1068 die "$help_disable\n" if !$param->{source
};
1069 check_target
($param->{source
});
1070 disable_job
($param);
1077 print("ERROR:\tno command specified\n") if !$help;
1078 print("USAGE:\t$PROGNAME <COMMAND> [ARGS] [OPTIONS]\n");
1079 print("\t$PROGNAME help [<cmd>] [OPTIONS]\n\n");
1080 print("\t$PROGNAME create -dest <string> -source <string> [OPTIONS]\n");
1081 print("\t$PROGNAME destroy -source <string> [OPTIONS]\n");
1082 print("\t$PROGNAME disable -source <string> [OPTIONS]\n");
1083 print("\t$PROGNAME enable -source <string> [OPTIONS]\n");
1084 print("\t$PROGNAME list\n");
1085 print("\t$PROGNAME status\n");
1086 print("\t$PROGNAME sync -dest <string> -source <string> [OPTIONS]\n");
1094 if($target !~ m/(\d+.\d+.\d+.\d+:)?([\w\-\_\/]+)(\
/.+)?/) {
1095 print("ERROR:\t$target is not valid.\n\tUse [IP:]<ZFSPool>[/Path]!\n");
1105 pve-zsync - PVE ZFS Replication Manager
1109 pve-zsync <COMMAND> [ARGS] [OPTIONS]
1111 pve-zsync help <cmd> [OPTIONS]
1113 Get help about specified command.
1121 Verbose output format.
1123 pve-zsync create -dest <string> -source <string> [OPTIONS]
1129 the destination target is like [IP]:<Pool>[/Path]
1133 max sync speed in kBytes/s, default unlimited
1137 how much snapshots will be kept before get erased, default 1
1141 name of the sync job, if not set it is default
1145 if this flag is set it will skip the first sync
1149 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1151 pve-zsync destroy -source <string> [OPTIONS]
1153 remove a sync Job from the scheduler
1157 name of the sync job, if not set it is default
1161 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1163 pve-zsync disable -source <string> [OPTIONS]
1169 name of the sync job, if not set it is default
1173 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1175 pve-zsync enable -source <string> [OPTIONS]
1177 enable a syncjob and reset error
1181 name of the sync job, if not set it is default
1185 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1188 Get a List of all scheduled Sync Jobs
1192 Get the status of all scheduled Sync Jobs
1194 pve-zsync sync -dest <string> -source <string> [OPTIONS]
1200 the destination target is like [IP:]<Pool>[/Path]
1204 max sync speed in kBytes/s, default unlimited
1208 how much snapshots will be kept before get erased, default 1
1212 name of the sync job, if not set it is default.
1213 It is only necessary if scheduler allready contains this source.
1217 the source can be an <VMID> or [IP:]<ZFSPool>[/Path]
1221 This Tool helps you to sync your VM or directory which stored on ZFS between 2 servers.
1222 This tool also has the capability to add jobs to cron so the sync will be automatically done.
1223 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.
1224 To config cron see man crontab.
1226 =head2 PVE ZFS Storage sync Tool
1228 This Tool can get remote pool on other PVE or send Pool to others ZFS machines
1232 add sync job from local VM to remote ZFS Server
1233 pve-zsync create -source=100 -dest=192.168.1.2:zfspool
1235 =head1 IMPORTANT FILES
1237 Cron jobs are stored at /etc/cron.d/pve-zsync
1239 The VM config get copied on the destination machine to /var/pve-zsync/
1241 The config is stored at /var/pve-zsync/
1243 =head1 COPYRIGHT AND DISCLAIMER
1245 Copyright (C) 2007-2015 Proxmox Server Solutions GmbH
1247 This program is free software: you can redistribute it and/or modify it
1248 under the terms of the GNU Affero General Public License as published
1249 by the Free Software Foundation, either version 3 of the License, or
1250 (at your option) any later version.
1252 This program is distributed in the hope that it will be useful, but
1253 WITHOUT ANY WARRANTY; without even the implied warranty of
1254 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1255 Affero General Public License for more details.
1257 You should have received a copy of the GNU Affero General Public
1258 License along with this program. If not, see
1259 <http://www.gnu.org/licenses/>.