Merge branch 'master' of https://github.com/jimsalterjrs/sanoid
This commit is contained in:
commit
c80c442b7e
|
@ -1,3 +1,7 @@
|
|||
1.4.17 changed die to warn when unexpectedly unable to remove a snapshot - this
|
||||
allows sanoid to continue taking/removing other snapshots not affected by
|
||||
whatever lock prevented the first from being taken or removed
|
||||
|
||||
1.4.16 merged @hrast01's extended fix to support -o option1=val,option2=val passthrough to SSH. merged @JakobR's
|
||||
off-by-one fix to stop unnecessary extra snapshots being taken under certain conditions. merged @stardude900's
|
||||
update to INSTALL for FreeBSD users re:symlinks. Implemented @LordAro's update to change DIE to WARN when
|
||||
|
|
3
INSTALL
3
INSTALL
|
@ -8,6 +8,7 @@ default for SSH transport since v1.4.6. Syncoid runs will fail if one of them
|
|||
is not available on either end of the transport.
|
||||
|
||||
On Ubuntu: apt install pv lzop mbuffer
|
||||
On CentOS: yum install lzo pv mbuffer lzop
|
||||
On FreeBSD: pkg install pv lzop
|
||||
|
||||
FreeBSD notes: FreeBSD may place pv and lzop in somewhere other than
|
||||
|
@ -26,5 +27,5 @@ without it. Config::IniFiles may be installed from CPAN, though the project
|
|||
strongly recommends using your distribution's repositories instead.
|
||||
|
||||
On Ubuntu: apt install libconfig-inifiles-perl
|
||||
On CentOS: yum install perl-Config-IniFiles
|
||||
On FreeBSD: pkg install p5-Config-Inifiles
|
||||
|
||||
|
|
1
LICENSE
1
LICENSE
|
@ -672,4 +672,3 @@ may consider it more useful to permit linking proprietary applications with
|
|||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
|
||||
|
|
12
README.md
12
README.md
|
@ -111,11 +111,11 @@ Syncoid supports recursive replication (replication of a dataset and all its chi
|
|||
|
||||
##### Syncoid Command Line Options
|
||||
|
||||
+ --[source]
|
||||
+ [source]
|
||||
|
||||
This is the source dataset. It can be either local or remote.
|
||||
|
||||
+ --[destination]
|
||||
+ [destination]
|
||||
|
||||
This is the destination dataset. It can be either local or remote.
|
||||
|
||||
|
@ -125,7 +125,7 @@ Syncoid supports recursive replication (replication of a dataset and all its chi
|
|||
|
||||
+ --compress <compression type>
|
||||
|
||||
Currently accepts gzip and lzo. lzo is fast and light on the processsor and is the default. If the selected compression method is unavailable on the source and destination, no compression will be used.
|
||||
Currently accepted options: gzip, pigz-fast, pigz-slow, lzo (default) & none. If the selected compression method is unavailable on the source and destination, no compression will be used.
|
||||
|
||||
+ --source-bwlimit <limit t|g|m|k>
|
||||
|
||||
|
@ -135,7 +135,7 @@ Syncoid supports recursive replication (replication of a dataset and all its chi
|
|||
|
||||
This is the bandwidth limit imposed upon the target. This is mainly used if the source does not have mbuffer installed, but bandwidth limites are desired.
|
||||
|
||||
+ --nocommandchecks
|
||||
+ --no-command-checks
|
||||
|
||||
Do not check the existance of commands before attempting the transfer. It assumes all programs are available. This should never be used.
|
||||
|
||||
|
@ -163,10 +163,6 @@ Syncoid supports recursive replication (replication of a dataset and all its chi
|
|||
|
||||
Supress non-error output.
|
||||
|
||||
+ --verbose
|
||||
|
||||
This prints additional information during the sanoid run.
|
||||
|
||||
+ --debug
|
||||
|
||||
This prints out quite alot of additional information during a sanoid run, and is normally not needed.
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
sanoid (1.4.16) unstable; urgency=medium
|
||||
|
||||
* merged @hrast01's extended fix to support -o option1=val,option2=val passthrough to SSH. merged @JakobR's
|
||||
* off-by-one fix to stop unnecessary extra snapshots being taken under certain conditions. merged @stardude900's
|
||||
* update to INSTALL for FreeBSD users re:symlinks. Implemented @LordAro's update to change DIE to WARN when
|
||||
* encountering a dataset with no snapshots and --no-sync-snap set during recursive replication. Implemented
|
||||
* @LordAro's update to sanoid.conf to add an ignore template which does not snap, prune, or monitor.
|
||||
|
||||
-- Jim Salter <github@jrs-s.net> Wed, 9 Aug 2017 12:28:49 -0400
|
|
@ -0,0 +1 @@
|
|||
9
|
|
@ -0,0 +1,14 @@
|
|||
Source: sanoid
|
||||
Section: unknown
|
||||
Priority: optional
|
||||
Maintainer: Jim Salter <jim@openoid.net>
|
||||
Build-Depends: debhelper (>= 9)
|
||||
Standards-Version: 3.9.8
|
||||
Homepage: https://github.com/jimsalterjrs/sanoid
|
||||
Vcs-Git: https://github.com/jimsalterjrs/sanoid.git
|
||||
Vcs-Browser: https://github.com/jimsalterjrs/sanoid
|
||||
|
||||
Package: sanoid
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, ${perl:Depends}, zfsutils-linux | zfs, libconfig-inifiles-perl
|
||||
Description: Policy-driven snapshot management and replication tools
|
|
@ -0,0 +1,33 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: sanoid
|
||||
Source: <https://github.com/jimsalterjrs/sanoid>
|
||||
|
||||
Files: *
|
||||
Copyright: 2017 Jim Salter <github@jrs-s.net>
|
||||
License: GPL-3.0+
|
||||
|
||||
Files: debian/*
|
||||
Copyright: 2017 Jim Salter <github@jrs-s.net>
|
||||
License: GPL-3.0+
|
||||
|
||||
License: GPL-3.0+
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
.
|
||||
This package is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
.
|
||||
On Debian systems, the complete text of the GNU General
|
||||
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
|
||||
|
||||
# Please also look if there are files or directories which have a
|
||||
# different copyright/license attached and list them here.
|
||||
# Please avoid picking licenses with terms that are more restrictive than the
|
||||
# packaged work, as it may make Debian's contributions unacceptable upstream.
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/make -f
|
||||
# See debhelper(7) for more info
|
||||
|
||||
# output every command that modifies files on the build system.
|
||||
#export DH_VERBOSE = 1
|
||||
|
||||
%:
|
||||
dh $@ --with systemd
|
||||
|
||||
DESTDIR = $(CURDIR)/debian/sanoid
|
||||
override_dh_auto_install:
|
||||
@mkdir -p $(DESTDIR)/usr/sbin; \
|
||||
cp sanoid syncoid findoid sleepymutex $(DESTDIR)/usr/sbin;
|
||||
@mkdir -p $(DESTDIR)/etc/sanoid; \
|
||||
cp sanoid.defaults.conf $(DESTDIR)/etc/sanoid;
|
||||
@mkdir -p $(DESTDIR)/usr/share/doc/sanoid; \
|
||||
cp sanoid.conf $(DESTDIR)/usr/share/doc/sanoid/sanoid.conf.example;
|
||||
@mkdir -p $(DESTDIR)/lib/systemd/system; \
|
||||
cp debian/sanoid.timer $(DESTDIR)/lib/systemd/system;
|
|
@ -0,0 +1 @@
|
|||
To start, copy the example config file in /usr/share/doc/sanoid to /etc/sanoid/sanoid.conf.
|
|
@ -0,0 +1 @@
|
|||
README.md
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Snapshot ZFS Pool
|
||||
Requires=zfs.target
|
||||
After=zfs.target
|
||||
ConditionFileNotEmpty=/etc/sanoid/sanoid.conf
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/sbin/sanoid --cron
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Run Sanoid Every 15 Minutes
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*:0/15
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
|
@ -0,0 +1 @@
|
|||
3.0 (native)
|
161
sanoid
161
sanoid
|
@ -4,22 +4,33 @@
|
|||
# from http://www.gnu.org/licenses/gpl-3.0.html on 2014-11-17. A copy should also be available in this
|
||||
# project's Git repository at https://github.com/jimsalterjrs/sanoid/blob/master/LICENSE.
|
||||
|
||||
my $version = '1.4.16';
|
||||
$::VERSION = '1.4.17';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Config::IniFiles; # read samba-style conf file
|
||||
use File::Path; # for rmtree command in use_prune
|
||||
use Data::Dumper; # debugging - print contents of hash
|
||||
use File::Path; # for rmtree command in use_prune
|
||||
use Getopt::Long qw(:config auto_version auto_help);
|
||||
use Pod::Usage; # pod2usage
|
||||
use Time::Local; # to parse dates in reverse
|
||||
|
||||
# parse CLI arguments
|
||||
my %args = getargs(@ARGV);
|
||||
my %args = ("configdir" => "/etc/sanoid");
|
||||
GetOptions(\%args, "verbose", "debug", "cron", "readonly", "quiet",
|
||||
"monitor-health", "force-update", "configdir=s",
|
||||
"monitor-snapshots", "take-snapshots", "prune-snapshots"
|
||||
) or pod2usage(2);
|
||||
|
||||
# If only config directory (or nothing) has been specified, default to --cron --verbose
|
||||
if (keys %args < 2) {
|
||||
$args{'cron'} = 1;
|
||||
$args{'verbose'} = 1;
|
||||
}
|
||||
|
||||
my $pscmd = '/bin/ps';
|
||||
|
||||
my $zfs = '/sbin/zfs';
|
||||
|
||||
if ($args{'configdir'} eq '') { $args{'configdir'} = '/etc/sanoid'; }
|
||||
my $conf_file = "$args{'configdir'}/sanoid.conf";
|
||||
my $default_conf_file = "$args{'configdir'}/sanoid.defaults.conf";
|
||||
|
||||
|
@ -42,11 +53,9 @@ if ($args{'debug'}) { $args{'verbose'}=1; blabber (@params); }
|
|||
if ($args{'monitor-snapshots'}) { monitor_snapshots(@params); }
|
||||
if ($args{'monitor-health'}) { monitor_health(@params); }
|
||||
if ($args{'force-update'}) { my $snaps = getsnaps( \%config, $cacheTTL, 1 ); }
|
||||
if ($args{'version'}) { print "INFO: Sanoid version: $version\n"; }
|
||||
|
||||
if ($args{'cron'} || $args{'noargs'}) {
|
||||
if ($args{'noargs'}) { print "INFO: No arguments given - assuming --cron and --verbose.\n"; }
|
||||
if (!$args{'quiet'}) { $args{'verbose'} = 1; }
|
||||
if ($args{'cron'}) {
|
||||
if ($args{'quiet'}) { $args{'verbose'} = 0; }
|
||||
take_snapshots (@params);
|
||||
prune_snapshots (@params);
|
||||
} else {
|
||||
|
@ -61,7 +70,7 @@ exit 0;
|
|||
####################################################################################
|
||||
####################################################################################
|
||||
|
||||
sub monitor_health() {
|
||||
sub monitor_health {
|
||||
my ($config, $snaps, $snapsbytype, $snapsbypath) = @_;
|
||||
my %pools;
|
||||
my @messages;
|
||||
|
@ -84,13 +93,13 @@ sub monitor_health() {
|
|||
print "$message\n";
|
||||
exit $errlevel;
|
||||
|
||||
} # end monitor_health()
|
||||
}
|
||||
|
||||
####################################################################################
|
||||
####################################################################################
|
||||
####################################################################################
|
||||
|
||||
sub monitor_snapshots() {
|
||||
sub monitor_snapshots {
|
||||
|
||||
# nagios plugin format: exit 0,1,2,3 for OK, WARN, CRITICAL, or ERROR.
|
||||
|
||||
|
@ -127,8 +136,10 @@ sub monitor_snapshots() {
|
|||
my $warn = $config{$section}{$typewarn} * $smallerperiod;
|
||||
my $crit = $config{$section}{$typecrit} * $smallerperiod;
|
||||
my $elapsed = -1;
|
||||
if (defined $snapsbytype{$path}{$type}{'newest'}) { $elapsed = $snapsbytype{$path}{$type}{'newest'}; }
|
||||
my $dispelapsed = displaytime($snapsbytype{$path}{$type}{'newest'});
|
||||
if (defined $snapsbytype{$path}{$type}{'newest'}) {
|
||||
$elapsed = $snapsbytype{$path}{$type}{'newest'};
|
||||
}
|
||||
my $dispelapsed = displaytime($elapsed);
|
||||
my $dispwarn = displaytime($warn);
|
||||
my $dispcrit = displaytime($crit);
|
||||
if ( $elapsed > $crit || $elapsed == -1) {
|
||||
|
@ -161,7 +172,7 @@ sub monitor_snapshots() {
|
|||
|
||||
print "$msg\n";
|
||||
exit $errorlevel;
|
||||
} # end monitor()
|
||||
}
|
||||
|
||||
####################################################################################
|
||||
####################################################################################
|
||||
|
@ -194,6 +205,8 @@ sub prune_snapshots {
|
|||
elsif ($type eq 'monthly') { $period = 60*60*24*31; }
|
||||
elsif ($type eq 'yearly') { $period = 60*60*24*365.25; }
|
||||
|
||||
# avoid pissing off use warnings by not executing this block if no matching snaps exist
|
||||
if (defined $snapsbytype{$path}{$type}{'sorted'}) {
|
||||
my @sorted = split (/\|/,$snapsbytype{$path}{$type}{'sorted'});
|
||||
|
||||
# if we say "daily=30" we really mean "don't keep any dailies more than 30 days old", etc
|
||||
|
@ -224,7 +237,7 @@ sub prune_snapshots {
|
|||
if (iszfsbusy($path)) {
|
||||
print "INFO: deferring pruning of $snap - $path is currently in zfs send or receive.\n";
|
||||
} else {
|
||||
if (! $args{'readonly'}) { system($zfs, "destroy",$snap) == 0 or die "could not remove $snap : $?"; }
|
||||
if (! $args{'readonly'}) { system($zfs, "destroy",$snap) == 0 or warn "could not remove $snap : $?"; }
|
||||
}
|
||||
}
|
||||
removelock('sanoid_pruning');
|
||||
|
@ -236,6 +249,7 @@ sub prune_snapshots {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} # end prune_snapshots
|
||||
|
@ -333,7 +347,7 @@ sub take_snapshots {
|
|||
if ($args{'verbose'}) { print "taking snapshot $snap\n"; }
|
||||
if (!$args{'readonly'}) {
|
||||
system($zfs, "snapshot", "$snap") == 0
|
||||
or die "CRITICAL ERROR: $zfs snapshot $snap failed, $?";
|
||||
or warn "CRITICAL ERROR: $zfs snapshot $snap failed, $?";
|
||||
# make sure we don't end up with multiple snapshots with the same ctime
|
||||
sleep 1;
|
||||
}
|
||||
|
@ -473,11 +487,9 @@ sub getsnaps {
|
|||
my $cache = '/var/cache/sanoidsnapshots.txt';
|
||||
my @rawsnaps;
|
||||
|
||||
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
|
||||
$atime,$mtime,$ctime,$blksize,$blocks)
|
||||
= stat($cache);
|
||||
my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($cache);
|
||||
|
||||
if ( $forcecacheupdate || (time() - $mtime) > $cacheTTL ) {
|
||||
if ( $forcecacheupdate || ! -f $cache || (time() - $mtime) > $cacheTTL ) {
|
||||
if (checklock('sanoid_cacheupdate')) {
|
||||
writelock('sanoid_cacheupdate');
|
||||
if ($args{'verbose'}) {
|
||||
|
@ -510,12 +522,16 @@ sub getsnaps {
|
|||
|
||||
foreach my $snap (@rawsnaps) {
|
||||
my ($fs,$snapname,$snapdate) = ($snap =~ m/(.*)\@(.*ly)\s*creation\s*(\d*)/);
|
||||
|
||||
# avoid pissing off use warnings
|
||||
if (defined $snapname) {
|
||||
my ($snaptype) = ($snapname =~ m/.*_(\w*ly)/);
|
||||
if ($snapname =~ /^autosnap/) {
|
||||
$snaps{$fs}{$snapname}{'ctime'}=$snapdate;
|
||||
$snaps{$fs}{$snapname}{'type'}=$snaptype;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return %snaps;
|
||||
}
|
||||
|
@ -601,10 +617,10 @@ sub init {
|
|||
# make sure that true values are true and false values are false for any toggled values
|
||||
foreach my $toggle(@toggles) {
|
||||
foreach my $true (@istrue) {
|
||||
if ($config{$section}{$toggle} eq $true) { $config{$section}{$toggle} = 1; }
|
||||
if (defined $config{$section}{$toggle} && $config{$section}{$toggle} eq $true) { $config{$section}{$toggle} = 1; }
|
||||
}
|
||||
foreach my $false (@isfalse) {
|
||||
if ($config{$section}{$toggle} eq $false) { $config{$section}{$toggle} = 0; }
|
||||
if (defined $config{$section}{$toggle} && $config{$section}{$toggle} eq $false) { $config{$section}{$toggle} = 0; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -665,7 +681,7 @@ sub get_date {
|
|||
|
||||
sub displaytime {
|
||||
# take a time in seconds, return it in human readable form
|
||||
my $elapsed = shift;
|
||||
my ($elapsed) = @_;
|
||||
|
||||
my $days = int ($elapsed / 60 / 60 / 24);
|
||||
$elapsed -= $days * 60 * 60 * 24;
|
||||
|
@ -1026,75 +1042,10 @@ sub iszfsbusy {
|
|||
#######################################################################################################################3
|
||||
#######################################################################################################################3
|
||||
|
||||
sub getargs {
|
||||
my @args = @_;
|
||||
my %args;
|
||||
|
||||
my @validargs;
|
||||
my @novalueargs;
|
||||
my %validargs;
|
||||
my %novalueargs;
|
||||
|
||||
push my @validargs, 'verbose','debug','version','monitor-health','monitor-snapshots','force-update','cron','take-snapshots','prune-snapshots','readonly','configdir','quiet';
|
||||
push my @novalueargs, 'verbose','debug','version','monitor-health','monitor-snapshots','force-update','cron','take-snapshots','prune-snapshots','readonly','quiet';
|
||||
foreach my $item (@validargs) { $validargs{$item}=1; }
|
||||
foreach my $item (@novalueargs) { $novalueargs{$item}=1; }
|
||||
|
||||
if (! (scalar @args)) {
|
||||
$args{'noargs'} = 1;
|
||||
}
|
||||
|
||||
while (my $rawarg = shift(@args)) {
|
||||
my $argvalue;
|
||||
my $arg = $rawarg;
|
||||
if ($rawarg =~ /=/) {
|
||||
# user specified the value for a CLI argument with =
|
||||
# instead of with blank space. separate appropriately.
|
||||
$argvalue = $arg;
|
||||
$arg =~ s/=.*$//;
|
||||
$argvalue =~ s/^.*=//;
|
||||
}
|
||||
if ($rawarg =~ /^--/) {
|
||||
# doubledash arg
|
||||
$arg =~ s/^--//;
|
||||
if ($novalueargs{$arg}) {
|
||||
$args{$arg} = 1;
|
||||
} else {
|
||||
# if this CLI arg takes a user-specified value and
|
||||
# we don't already have it, then the user must have
|
||||
# specified with a space, so pull in the next value
|
||||
# from the array as this value rather than as the
|
||||
# next argument.
|
||||
if ($argvalue eq '') { $argvalue = shift(@args); }
|
||||
$args{$arg} = $argvalue;
|
||||
}
|
||||
} elsif ($rawarg =~ /^-/) {
|
||||
# singledash arg
|
||||
$arg =~ s/^-//;
|
||||
if ($novalueargs{$arg}) {
|
||||
$args{$arg} = 1;
|
||||
} else {
|
||||
# if this CLI arg takes a user-specified value and
|
||||
# we don't already have it, then the user must have
|
||||
# specified with a space, so pull in the next value
|
||||
# from the array as this value rather than as the
|
||||
# next argument.
|
||||
if ($argvalue eq '') { $argvalue = shift(@args); }
|
||||
$args{$arg} = $argvalue;
|
||||
}
|
||||
} else {
|
||||
# bare arg
|
||||
die "ERROR: don't know what to do with bare argument $rawarg.\n";
|
||||
}
|
||||
if (! ($validargs{$arg})) { die "ERROR: don't understand argument $rawarg.\n"; }
|
||||
}
|
||||
return %args;
|
||||
}
|
||||
|
||||
sub getchilddatasets {
|
||||
# for later, if we make sanoid itself support sudo use
|
||||
my $fs = shift;
|
||||
my $mysudocmd;
|
||||
my $mysudocmd = '';
|
||||
|
||||
my $getchildrencmd = "$mysudocmd $zfs list -o name -Hr $fs |";
|
||||
if ($args{'debug'}) { print "DEBUG: getting list of child datasets on $fs using $getchildrencmd...\n"; }
|
||||
|
@ -1105,3 +1056,33 @@ sub getchilddatasets {
|
|||
return @children;
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
sanoid - ZFS snapshot management and replication tool
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
sanoid [options]
|
||||
|
||||
Assumes --cron --verbose if no other arguments (other than configdir) are specified
|
||||
|
||||
Options:
|
||||
|
||||
--configdir=DIR Specify a directory to find config file sanoid.conf
|
||||
|
||||
--cron Creates snapshots and purges expired snapshots
|
||||
--verbose Prints out additional information during a sanoid run
|
||||
--readonly Simulates creation/deletion of snapshots
|
||||
--quiet Suppresses non-error output
|
||||
--force-update Clears out sanoid's zfs snapshot cache
|
||||
|
||||
--monitor-health Reports on zpool "health", in a Nagios compatible format
|
||||
--monitor-snapshots Reports on snapshot "health", in a Nagios compatible format
|
||||
--take-snapshots Creates snapshots as specified in sanoid.conf
|
||||
--prune-snapshots Purges expired snapshots as specified in sanoid.conf
|
||||
|
||||
--help Prints this helptext
|
||||
--version Prints the version number
|
||||
--debug Prints out a lot of additional information during a sanoid run
|
||||
|
|
|
@ -34,21 +34,21 @@ min_percent_free = 10
|
|||
# Note that we will not take snapshots for a given type if that type is set to 0 above,
|
||||
# regardless of the autosnap setting - for example, if yearly=0 we will not take yearlies
|
||||
# even if we've defined a preferred time for yearlies and autosnap is on.
|
||||
autosnap = 1;
|
||||
autosnap = 1
|
||||
# hourly - top of the hour
|
||||
hourly_min = 0;
|
||||
hourly_min = 0
|
||||
# daily - at 23:59 (most people expect a daily to contain everything done DURING that day)
|
||||
daily_hour = 23;
|
||||
daily_min = 59;
|
||||
daily_hour = 23
|
||||
daily_min = 59
|
||||
# monthly - immediately at the beginning of the month (ie 00:00 of day 1)
|
||||
monthly_mday = 1;
|
||||
monthly_hour = 0;
|
||||
monthly_min = 0;
|
||||
monthly_mday = 1
|
||||
monthly_hour = 0
|
||||
monthly_min = 0
|
||||
# yearly - immediately at the beginning of the year (ie 00:00 on Jan 1)
|
||||
yearly_mon = 1;
|
||||
yearly_mday = 1;
|
||||
yearly_hour = 0;
|
||||
yearly_min = 0;
|
||||
yearly_mon = 1
|
||||
yearly_mday = 1
|
||||
yearly_hour = 0
|
||||
yearly_min = 0
|
||||
|
||||
# monitoring plugin - define warn / crit levels for each snapshot type by age, in units of one period down
|
||||
# example hourly_warn = 90 means issue WARNING if most recent hourly snapshot is not less than 90 minutes old,
|
||||
|
@ -70,5 +70,3 @@ monthly_warn = 32
|
|||
monthly_crit = 35
|
||||
yearly_warn = 0
|
||||
yearly_crit = 0
|
||||
|
||||
|
||||
|
|
66
sanoid.spec
66
sanoid.spec
|
@ -1,17 +1,25 @@
|
|||
%global version 1.4.13
|
||||
%global version 1.4.14
|
||||
%global git_tag v%{version}
|
||||
|
||||
# Enable with systemctl "enable sanoid.timer"
|
||||
%global _with_systemd 1
|
||||
|
||||
Name: sanoid
|
||||
Version: %{version}
|
||||
Release: 1%{?dist}
|
||||
Release: 2%{?dist}
|
||||
BuildArch: noarch
|
||||
Summary: A policy-driven snapshot management tool for ZFS file systems
|
||||
Group: Applications/System
|
||||
License: GPLv3
|
||||
URL: https://github.com/jimsalterjrs/sanoid
|
||||
Source0: https://github.com/jimsalterjrs/%{name}/archive/%{git_tag}/%{name}-%{version}.tar.gz
|
||||
#BuildRequires:
|
||||
|
||||
Requires: perl, mbuffer, lzop, pv
|
||||
%if 0%{?_with_systemd}
|
||||
Requires: systemd >= 212
|
||||
|
||||
BuildRequires: systemd
|
||||
%endif
|
||||
|
||||
%description
|
||||
Sanoid is a policy-driven snapshot management
|
||||
|
@ -24,20 +32,62 @@ human-readable TOML configuration file.
|
|||
%setup -q
|
||||
|
||||
%build
|
||||
echo "Nothing to build"
|
||||
|
||||
%install
|
||||
%{__install} -D -m 0644 sanoid.defaults.conf %{buildroot}/etc/sanoid/sanoid.defaults.conf
|
||||
%{__install} -d %{buildroot}%{_sbindir}
|
||||
%{__install} -m 0755 sanoid syncoid findoid sleepymutex %{buildroot}%{_sbindir}
|
||||
|
||||
%if 0%{?_with_systemd}
|
||||
%{__install} -d %{buildroot}%{_unitdir}
|
||||
%endif
|
||||
|
||||
%if 0%{?fedora}
|
||||
%{__install} -D -m 0644 sanoid.conf %{buildroot}%{_docdir}/%{name}/examples/sanoid.conf
|
||||
echo "* * * * * root %{_sbindir}/sanoid --cron" > %{buildroot}%{_docdir}/%{name}/examples/sanoid.cron
|
||||
%endif
|
||||
%if 0%{?rhel}
|
||||
%{__install} -D -m 0644 sanoid.conf %{buildroot}%{_docdir}/%{name}-%{version}/examples/sanoid.conf
|
||||
%endif
|
||||
|
||||
%if 0%{?_with_systemd}
|
||||
cat > %{buildroot}%{_unitdir}/%{name}.service <<EOF
|
||||
[Unit]
|
||||
Description=Snapshot ZFS Pool
|
||||
Requires=zfs.target
|
||||
After=zfs.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=%{_sbindir}/sanoid --cron
|
||||
EOF
|
||||
|
||||
cat > %{buildroot}%{_unitdir}/%{name}.timer <<EOF
|
||||
[Unit]
|
||||
Description=Run Sanoid Every Minute
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*:0/1
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
EOF
|
||||
|
||||
%else
|
||||
%if 0%{?fedora}
|
||||
%{__install} -D -m 0644 sanoid.conf %{buildroot}%{_docdir}/%{name}/examples/sanoid.conf
|
||||
%endif
|
||||
%if 0%{?rhel}
|
||||
echo "* * * * * root %{_sbindir}/sanoid --cron" > %{buildroot}%{_docdir}/%{name}-%{version}/examples/sanoid.cron
|
||||
%endif
|
||||
%endif
|
||||
|
||||
%post
|
||||
%{?_with_systemd:%{_bindir}/systemctl daemon-reload}
|
||||
|
||||
%postun
|
||||
%{?_with_systemd:%{_bindir}/systemctl daemon-reload}
|
||||
|
||||
%files
|
||||
%doc CHANGELIST VERSION README.md FREEBSD.readme
|
||||
|
@ -54,8 +104,16 @@ echo "* * * * * root %{_sbindir}/sanoid --cron" > %{buildroot}%{_docdir}/%{name}
|
|||
%if 0%{?rhel}
|
||||
%{_docdir}/%{name}-%{version}
|
||||
%endif
|
||||
%if 0%{?_with_systemd}
|
||||
%{_unitdir}/%{name}.service
|
||||
%{_unitdir}/%{name}.timer
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Thu Aug 31 2017 Dominic Robinson <github@dcrdev.com> - 1.4.14-2
|
||||
- Add systemd timers
|
||||
* Wed Aug 30 2017 Dominic Robinson <github@dcrdev.com> - 1.4.14-1
|
||||
- Version bump
|
||||
* Wed Jul 12 2017 Thomas M. Lapp <tmlapp@gmail.com> - 1.4.13-1
|
||||
- Version bump
|
||||
- Include FREEBSD.readme in docs
|
||||
|
|
358
syncoid
358
syncoid
|
@ -4,26 +4,44 @@
|
|||
# from http://www.gnu.org/licenses/gpl-3.0.html on 2014-11-17. A copy should also be available in this
|
||||
# project's Git repository at https://github.com/jimsalterjrs/sanoid/blob/master/LICENSE.
|
||||
|
||||
my $version = '1.4.16';
|
||||
$::VERSION = '1.4.16';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Data::Dumper;
|
||||
use Getopt::Long qw(:config auto_version auto_help);
|
||||
use Pod::Usage;
|
||||
use Time::Local;
|
||||
use Sys::Hostname;
|
||||
|
||||
my %args = getargs(@ARGV);
|
||||
# Blank defaults to use ssh client's default
|
||||
# TODO: Merge into a single "sshflags" option?
|
||||
my %args = ('sshkey' => '', 'sshport' => '', 'sshcipher' => '', 'sshoption' => [], 'target-bwlimit' => '', 'source-bwlimit' => '');
|
||||
GetOptions(\%args, "no-command-checks", "monitor-version", "compress=s", "dumpsnaps", "recursive|r",
|
||||
"source-bwlimit=s", "target-bwlimit=s", "sshkey=s", "sshport=i", "sshcipher|c=s", "sshoption|o=s@",
|
||||
"debug", "quiet", "no-stream", "no-sync-snap") or pod2usage(2);
|
||||
|
||||
if ($args{'version'}) {
|
||||
print "Syncoid version: $version\n";
|
||||
exit 0;
|
||||
}
|
||||
my %compressargs = %{compressargset($args{'compress'} || 'default')}; # Can't be done with GetOptions arg, as default still needs to be set
|
||||
|
||||
if (!(defined $args{'source'} && defined $args{'target'})) {
|
||||
print 'usage: syncoid [src_user@src_host:]src_pool/src_dataset [dst_user@dst_host:]dst_pool/dst_dataset'."\n";
|
||||
# TODO Expand to accept multiple sources?
|
||||
if (scalar(@ARGV) != 2) {
|
||||
print("Source or target not found!\n");
|
||||
pod2usage(2);
|
||||
exit 127;
|
||||
} else {
|
||||
$args{'source'} = $ARGV[0];
|
||||
$args{'target'} = $ARGV[1];
|
||||
}
|
||||
|
||||
# Could possibly merge these into an options function
|
||||
if (length $args{'source-bwlimit'}) {
|
||||
$args{'source-bwlimit'} = "-R $args{'source-bwlimit'}";
|
||||
}
|
||||
if (length $args{'target-bwlimit'}) {
|
||||
$args{'target-bwlimit'} = "-r $args{'target-bwlimit'}";
|
||||
}
|
||||
$args{'streamarg'} = (defined $args{'no-stream'} ? '-i' : '-I');
|
||||
|
||||
my $rawsourcefs = $args{'source'};
|
||||
my $rawtargetfs = $args{'target'};
|
||||
my $debug = $args{'debug'};
|
||||
|
@ -32,25 +50,7 @@ my $quiet = $args{'quiet'};
|
|||
my $zfscmd = '/sbin/zfs';
|
||||
my $sshcmd = '/usr/bin/ssh';
|
||||
my $pscmd = '/bin/ps';
|
||||
my $sshcipher;
|
||||
if (defined $args{'c'}) {
|
||||
$sshcipher = "-c $args{'c'}";
|
||||
} else {
|
||||
$sshcipher = '-c chacha20-poly1305@openssh.com,arcfour';
|
||||
}
|
||||
my $sshport = '-p 22';
|
||||
my $sshoption;
|
||||
if (defined $args{'o'}) {
|
||||
my @options = split(',', $args{'o'});
|
||||
foreach my $option (@options) {
|
||||
$sshoption .= " -o $option";
|
||||
if ($option eq "NoneSwitch=yes") {
|
||||
$sshcipher = "";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$sshoption = "";
|
||||
}
|
||||
|
||||
my $pvcmd = '/usr/bin/pv';
|
||||
my $mbuffercmd = '/usr/bin/mbuffer';
|
||||
my $sudocmd = '/usr/bin/sudo';
|
||||
|
@ -59,23 +59,25 @@ my $mbufferoptions = '-q -s 128k -m 16M 2>/dev/null';
|
|||
# being present on remote machines.
|
||||
my $lscmd = '/bin/ls';
|
||||
|
||||
if ( $args{'sshport'} ) {
|
||||
$sshport = "-p $args{'sshport'}";
|
||||
if (length $args{'sshcipher'}) {
|
||||
$args{'sshcipher'} = "-c $args{'sshcipher'}";
|
||||
}
|
||||
if (length $args{'sshport'}) {
|
||||
$args{'sshport'} = "-p $args{'sshport'}";
|
||||
}
|
||||
if (length $args{'sshkey'}) {
|
||||
$args{'sshkey'} = "-i $args{'sshkey'}";
|
||||
}
|
||||
my $sshoptions = join " ", map { "-o " . $_ } @{$args{'sshoption'}}; # deref required
|
||||
|
||||
# figure out if source and/or target are remote.
|
||||
if ( $args{'sshkey'} ) {
|
||||
$sshcmd = "$sshcmd $sshoption $sshcipher $sshport -i $args{'sshkey'}";
|
||||
}
|
||||
else {
|
||||
$sshcmd = "$sshcmd $sshoption $sshcipher $sshport";
|
||||
}
|
||||
$sshcmd = "$sshcmd $args{'sshcipher'} $sshoptions $args{'sshport'} $args{'sshkey'}";
|
||||
if ($debug) { print "DEBUG: SSHCMD: $sshcmd\n"; }
|
||||
my ($sourcehost,$sourcefs,$sourceisroot) = getssh($rawsourcefs);
|
||||
my ($targethost,$targetfs,$targetisroot) = getssh($rawtargetfs);
|
||||
|
||||
my $sourcesudocmd;
|
||||
my $targetsudocmd;
|
||||
if ($sourceisroot) { $sourcesudocmd = ''; } else { $sourcesudocmd = $sudocmd; }
|
||||
if ($targetisroot) { $targetsudocmd = ''; } else { $targetsudocmd = $sudocmd; }
|
||||
my $sourcesudocmd = $sourceisroot ? '' : $sudocmd;
|
||||
my $targetsudocmd = $targetisroot ? '' : $sudocmd;
|
||||
|
||||
# figure out whether compression, mbuffering, pv
|
||||
# are available on source, target, local machines.
|
||||
|
@ -88,7 +90,7 @@ my %snaps;
|
|||
## can loop across children separately, for recursive ##
|
||||
## replication ##
|
||||
|
||||
if (! $args{'recursive'}) {
|
||||
if (!defined $args{'recursive'}) {
|
||||
syncdataset($sourcehost, $sourcefs, $targethost, $targetfs);
|
||||
} else {
|
||||
if ($debug) { print "DEBUG: recursive sync of $sourcefs.\n"; }
|
||||
|
@ -161,11 +163,15 @@ sub syncdataset {
|
|||
%snaps = (%sourcesnaps, %targetsnaps);
|
||||
}
|
||||
|
||||
if ($args{'dumpsnaps'}) { print "merged snapshot list of $targetfs: \n"; dumphash(\%snaps); print "\n\n\n"; }
|
||||
if (defined $args{'dumpsnaps'}) {
|
||||
print "merged snapshot list of $targetfs: \n";
|
||||
dumphash(\%snaps);
|
||||
print "\n\n\n";
|
||||
}
|
||||
|
||||
# create a new syncoid snapshot on the source filesystem.
|
||||
my $newsyncsnap;
|
||||
if (!defined ($args{'no-sync-snap'}) ) {
|
||||
if (!defined $args{'no-sync-snap'}) {
|
||||
$newsyncsnap = newsyncsnap($sourcehost,$sourcefs,$sourceisroot);
|
||||
} else {
|
||||
# we don't want sync snapshots created, so use the newest snapshot we can find.
|
||||
|
@ -334,121 +340,66 @@ sub syncdataset {
|
|||
|
||||
} # end syncdataset()
|
||||
|
||||
sub compressargset {
|
||||
my ($value) = @_;
|
||||
my $DEFAULT_COMPRESSION = 'lzo';
|
||||
my %COMPRESS_ARGS = (
|
||||
'none' => {
|
||||
rawcmd => '',
|
||||
args => '',
|
||||
decomrawcmd => '',
|
||||
decomargs => '',
|
||||
},
|
||||
'gzip' => {
|
||||
rawcmd => '/bin/gzip',
|
||||
args => '-3',
|
||||
decomrawcmd => '/bin/zcat',
|
||||
decomargs => '',
|
||||
},
|
||||
'pigz-fast' => {
|
||||
rawcmd => '/usr/bin/pigz',
|
||||
args => '-3',
|
||||
decomrawcmd => '/usr/bin/pigz',
|
||||
decomargs => '-dc',
|
||||
},
|
||||
'pigz-slow' => {
|
||||
rawcmd => '/usr/bin/pigz',
|
||||
args => '-9',
|
||||
decomrawcmd => '/usr/bin/pigz',
|
||||
decomargs => '-dc',
|
||||
},
|
||||
'lzo' => {
|
||||
rawcmd => '/usr/bin/lzop',
|
||||
args => '',
|
||||
decomrawcmd => '/usr/bin/lzop',
|
||||
decomargs => '-dfc',
|
||||
},
|
||||
'lz4-fast' => {
|
||||
rawcmd => '/usr/bin/lz4',
|
||||
args => '-1',
|
||||
decomrawcmd => '/usr/bin/lz4',
|
||||
decomargs => '-dc',
|
||||
},
|
||||
'lz4-slow' => {
|
||||
rawcmd => '/usr/bin/lz4',
|
||||
args => '-9',
|
||||
decomrawcmd => '/usr/bin/lz4',
|
||||
decomargs => '-dc',
|
||||
},
|
||||
);
|
||||
|
||||
sub getargs {
|
||||
my @args = @_;
|
||||
my %args;
|
||||
|
||||
my %novaluearg;
|
||||
my %validarg;
|
||||
push my @validargs, ('debug','nocommandchecks','version','monitor-version','compress','c','o','source-bwlimit','target-bwlimit','dumpsnaps','recursive','r','sshkey','sshport','quiet','no-stream','no-sync-snap');
|
||||
foreach my $item (@validargs) { $validarg{$item} = 1; }
|
||||
push my @novalueargs, ('debug','nocommandchecks','version','monitor-version','dumpsnaps','recursive','r','quiet','no-stream','no-sync-snap');
|
||||
foreach my $item (@novalueargs) { $novaluearg{$item} = 1; }
|
||||
|
||||
while (my $rawarg = shift(@args)) {
|
||||
my $arg = $rawarg;
|
||||
my $argvalue = '';
|
||||
if ($rawarg =~ /=/) {
|
||||
# user specified the value for a CLI argument with =
|
||||
# instead of with blank space. separate appropriately.
|
||||
$argvalue = $arg;
|
||||
$arg =~ s/=.*$//;
|
||||
$argvalue =~ s/^.*=//;
|
||||
}
|
||||
if ($rawarg =~ /^--/) {
|
||||
# doubledash arg
|
||||
$arg =~ s/^--//;
|
||||
if (! $validarg{$arg}) { die "ERROR: don't understand argument $rawarg.\n"; }
|
||||
if ($novaluearg{$arg}) {
|
||||
$args{$arg} = 1;
|
||||
} else {
|
||||
# if this CLI arg takes a user-specified value and
|
||||
# we don't already have it, then the user must have
|
||||
# specified with a space, so pull in the next value
|
||||
# from the array as this value rather than as the
|
||||
# next argument.
|
||||
if ($argvalue eq '') { $argvalue = shift(@args); }
|
||||
$args{$arg} = $argvalue;
|
||||
}
|
||||
} elsif ($arg =~ /^-/) {
|
||||
# singledash arg
|
||||
$arg =~ s/^-//;
|
||||
if (! $validarg{$arg}) { die "ERROR: don't understand argument $rawarg.\n"; }
|
||||
if ($novaluearg{$arg}) {
|
||||
$args{$arg} = 1;
|
||||
} else {
|
||||
# if this CLI arg takes a user-specified value and
|
||||
# we don't already have it, then the user must have
|
||||
# specified with a space, so pull in the next value
|
||||
# from the array as this value rather than as the
|
||||
# next argument.
|
||||
if ($argvalue eq '') { $argvalue = shift(@args); }
|
||||
$args{$arg} = $argvalue;
|
||||
}
|
||||
} else {
|
||||
# bare arg
|
||||
if (defined $args{'source'}) {
|
||||
if (! defined $args{'target'}) {
|
||||
$args{'target'} = $arg;
|
||||
} else {
|
||||
die "ERROR: don't know what to do with third bare argument $rawarg.\n";
|
||||
}
|
||||
} else {
|
||||
$args{'source'} = $arg;
|
||||
}
|
||||
}
|
||||
if ($value eq 'default') {
|
||||
$value = $DEFAULT_COMPRESSION;
|
||||
} elsif (!(grep $value eq $_, ('gzip', 'pigz-fast', 'pigz-slow', 'lzo', 'default', 'none'))) {
|
||||
warn "Unrecognised compression value $value, defaulting to $DEFAULT_COMPRESSION";
|
||||
$value = $DEFAULT_COMPRESSION;
|
||||
}
|
||||
|
||||
if (defined $args{'source-bwlimit'}) { $args{'source-bwlimit'} = "-R $args{'source-bwlimit'}"; } else { $args{'source-bwlimit'} = ''; }
|
||||
if (defined $args{'target-bwlimit'}) { $args{'target-bwlimit'} = "-r $args{'target-bwlimit'}"; } else { $args{'target-bwlimit'} = ''; }
|
||||
|
||||
if (defined $args{'no-stream'}) { $args{'streamarg'} = '-i'; } else { $args{'streamarg'} = '-I'; }
|
||||
|
||||
if ($args{'r'}) { $args{'recursive'} = $args{'r'}; }
|
||||
|
||||
if (!defined $args{'compress'}) { $args{'compress'} = 'default'; }
|
||||
|
||||
if ($args{'compress'} eq 'gzip') {
|
||||
$args{'rawcompresscmd'} = '/bin/gzip';
|
||||
$args{'compressargs'} = '-3';
|
||||
$args{'rawdecompresscmd'} = '/bin/zcat';
|
||||
$args{'decompressargs'} = '';
|
||||
} elsif ( ($args{'compress'} eq 'pigz-fast')) {
|
||||
$args{'rawcompresscmd'} = '/usr/bin/pigz';
|
||||
$args{'compressargs'} = '-3';
|
||||
$args{'rawdecompresscmd'} = '/usr/bin/pigz';
|
||||
$args{'decompressargs'} = '-dc';
|
||||
} elsif ( ($args{'compress'} eq 'pigz-slow')) {
|
||||
$args{'rawcompresscmd'} = '/usr/bin/pigz';
|
||||
$args{'compressargs'} = '-9';
|
||||
$args{'rawdecompresscmd'} = '/usr/bin/pigz';
|
||||
$args{'decompressargs'} = '-dc';
|
||||
} elsif ( ($args{'compress'} eq 'lz4-fast')) {
|
||||
$args{'rawcompresscmd'} = '/usr/bin/lz4';
|
||||
$args{'compressargs'} = '-1';
|
||||
$args{'rawdecompresscmd'} = '/usr/bin/lz4';
|
||||
$args{'decompressargs'} = '-dc';
|
||||
} elsif ( ($args{'compress'} eq 'lz4-slow')) {
|
||||
$args{'rawcompresscmd'} = '/usr/bin/lz4';
|
||||
$args{'compressargs'} = '-9';
|
||||
$args{'rawdecompresscmd'} = '/usr/bin/lz4';
|
||||
$args{'decompressargs'} = '-dc';
|
||||
} elsif ( ($args{'compress'} eq 'lzo') || ($args{'compress'} eq 'default') ) {
|
||||
$args{'rawcompresscmd'} = '/usr/bin/lzop';
|
||||
$args{'compressargs'} = '';
|
||||
$args{'rawdecompresscmd'} = '/usr/bin/lzop';
|
||||
$args{'decompressargs'} = '-dfc';
|
||||
} else {
|
||||
$args{'rawcompresscmd'} = '';
|
||||
$args{'compressargs'} = '';
|
||||
$args{'rawdecompresscmd'} = '';
|
||||
$args{'decompressargs'} = '';
|
||||
}
|
||||
$args{'compresscmd'} = "$args{'rawcompresscmd'} $args{'compressargs'}";
|
||||
$args{'decompresscmd'} = "$args{'rawdecompresscmd'} $args{'decompressargs'}";
|
||||
|
||||
return %args;
|
||||
my %comargs = %{$COMPRESS_ARGS{$value}}; # copy
|
||||
$comargs{'compress'} = $value;
|
||||
$comargs{'cmd'} = "$comargs{'rawcmd'} $comargs{'args'}";
|
||||
$comargs{'decomcmd'} = "$comargs{'decomrawcmd'} $comargs{'decomargs'}";
|
||||
return \%comargs;
|
||||
}
|
||||
|
||||
sub checkcommands {
|
||||
|
@ -478,24 +429,15 @@ sub checkcommands {
|
|||
|
||||
# if raw compress command is null, we must have specified no compression. otherwise,
|
||||
# make sure that compression is available everywhere we need it
|
||||
if ($args{'rawcompresscmd'} eq '') {
|
||||
$avail{'sourcecompress'} = 0;
|
||||
$avail{'sourcecompress'} = 0;
|
||||
$avail{'localcompress'} = 0;
|
||||
if ($args{'compress'} eq 'none' ||
|
||||
$args{'compress'} eq 'no' ||
|
||||
$args{'compress'} eq '0') {
|
||||
if ($compressargs{'compress'} eq 'none') {
|
||||
if ($debug) { print "DEBUG: compression forced off from command line arguments.\n"; }
|
||||
} else {
|
||||
print "WARN: value $args{'compress'} for argument --compress not understood, proceeding without compression.\n";
|
||||
}
|
||||
} else {
|
||||
if ($debug) { print "DEBUG: checking availability of $args{'rawcompresscmd'} on source...\n"; }
|
||||
$avail{'sourcecompress'} = `$sourcessh $lscmd $args{'rawcompresscmd'} 2>/dev/null`;
|
||||
if ($debug) { print "DEBUG: checking availability of $args{'rawcompresscmd'} on target...\n"; }
|
||||
$avail{'targetcompress'} = `$targetssh $lscmd $args{'rawcompresscmd'} 2>/dev/null`;
|
||||
if ($debug) { print "DEBUG: checking availability of $args{'rawcompresscmd'} on local machine...\n"; }
|
||||
$avail{'localcompress'} = `$lscmd $args{'rawcompresscmd'} 2>/dev/null`;
|
||||
if ($debug) { print "DEBUG: checking availability of $compressargs{'rawcmd'} on source...\n"; }
|
||||
$avail{'sourcecompress'} = `$sourcessh $lscmd $compressargs{'rawcmd'} 2>/dev/null`;
|
||||
if ($debug) { print "DEBUG: checking availability of $compressargs{'rawcmd'} on target...\n"; }
|
||||
$avail{'targetcompress'} = `$targetssh $lscmd $compressargs{'rawcmd'} 2>/dev/null`;
|
||||
if ($debug) { print "DEBUG: checking availability of $compressargs{'rawcmd'} on local machine...\n"; }
|
||||
$avail{'localcompress'} = `$lscmd $compressargs{'rawcmd'} 2>/dev/null`;
|
||||
}
|
||||
|
||||
my ($s,$t);
|
||||
|
@ -521,14 +463,14 @@ sub checkcommands {
|
|||
|
||||
|
||||
if ($avail{'sourcecompress'} eq '') {
|
||||
if ($args{'rawcompresscmd'} ne '') {
|
||||
print "WARN: $args{'compresscmd'} not available on source $s- sync will continue without compression.\n";
|
||||
if ($compressargs{'rawcmd'} ne '') {
|
||||
print "WARN: $compressargs{'rawcmd'} not available on source $s- sync will continue without compression.\n";
|
||||
}
|
||||
$avail{'compress'} = 0;
|
||||
}
|
||||
if ($avail{'targetcompress'} eq '') {
|
||||
if ($args{'rawcompresscmd'} ne '') {
|
||||
print "WARN: $args{'compresscmd'} not available on target $t - sync will continue without compression.\n";
|
||||
if ($compressargs{'rawcmd'} ne '') {
|
||||
print "WARN: $compressargs{'rawcmd'} not available on target $t - sync will continue without compression.\n";
|
||||
}
|
||||
$avail{'compress'} = 0;
|
||||
}
|
||||
|
@ -540,8 +482,8 @@ sub checkcommands {
|
|||
|
||||
# corner case - if source AND target are BOTH remote, we have to check for local compress too
|
||||
if ($sourcehost ne '' && $targethost ne '' && $avail{'localcompress'} eq '') {
|
||||
if ($args{'rawcompresscmd'} ne '') {
|
||||
print "WARN: $args{'compresscmd'} not available on local machine - sync will continue without compression.\n";
|
||||
if ($compressargs{'rawcmd'} ne '') {
|
||||
print "WARN: $compressargs{'rawcmd'} not available on local machine - sync will continue without compression.\n";
|
||||
}
|
||||
$avail{'compress'} = 0;
|
||||
}
|
||||
|
@ -697,9 +639,9 @@ sub buildsynccmd {
|
|||
$synccmd = "$sendcmd |";
|
||||
# avoid confusion - accept either source-bwlimit or target-bwlimit as the bandwidth limiting option here
|
||||
my $bwlimit = '';
|
||||
if (defined $args{'source-bwlimit'}) {
|
||||
if (length $args{'bwlimit'}) {
|
||||
$bwlimit = $args{'source-bwlimit'};
|
||||
} elsif (defined $args{'target-bwlimit'}) {
|
||||
} elsif (length $args{'target-bwlimit'}) {
|
||||
$bwlimit = $args{'target-bwlimit'};
|
||||
}
|
||||
|
||||
|
@ -708,40 +650,40 @@ sub buildsynccmd {
|
|||
$synccmd .= " $recvcmd";
|
||||
} elsif ($sourcehost eq '') {
|
||||
# local source, remote target.
|
||||
#$synccmd = "$sendcmd | $pvcmd | $args{'compresscmd'} | $mbuffercmd | $sshcmd $targethost '$args{'decompresscmd'} | $mbuffercmd | $recvcmd'";
|
||||
#$synccmd = "$sendcmd | $pvcmd | $compressargs{'cmd'} | $mbuffercmd | $sshcmd $targethost '$compressargs{'decomcmd'} | $mbuffercmd | $recvcmd'";
|
||||
$synccmd = "$sendcmd |";
|
||||
if ($avail{'localpv'} && !$quiet) { $synccmd .= " $pvcmd -s $pvsize |"; }
|
||||
if ($avail{'compress'}) { $synccmd .= " $args{'compresscmd'} |"; }
|
||||
if ($avail{'compress'}) { $synccmd .= " $compressargs{'cmd'} |"; }
|
||||
if ($avail{'sourcembuffer'}) { $synccmd .= " $mbuffercmd $args{'source-bwlimit'} $mbufferoptions |"; }
|
||||
$synccmd .= " $sshcmd $targethost '";
|
||||
if ($avail{'targetmbuffer'}) { $synccmd .= " $mbuffercmd $args{'target-bwlimit'} $mbufferoptions |"; }
|
||||
if ($avail{'compress'}) { $synccmd .= " $args{'decompresscmd'} |"; }
|
||||
if ($avail{'compress'}) { $synccmd .= " $compressargs{'decomcmd'} |"; }
|
||||
$synccmd .= " $recvcmd'";
|
||||
} elsif ($targethost eq '') {
|
||||
# remote source, local target.
|
||||
#$synccmd = "$sshcmd $sourcehost '$sendcmd | $args{'compresscmd'} | $mbuffercmd' | $args{'decompresscmd'} | $mbuffercmd | $pvcmd | $recvcmd";
|
||||
#$synccmd = "$sshcmd $sourcehost '$sendcmd | $compressargs{'cmd'} | $mbuffercmd' | $args{'decompress'}{'cmd'} | $mbuffercmd | $pvcmd | $recvcmd";
|
||||
$synccmd = "$sshcmd $sourcehost '$sendcmd";
|
||||
if ($avail{'compress'}) { $synccmd .= " | $args{'compresscmd'}"; }
|
||||
if ($avail{'compress'}) { $synccmd .= " | $compressargs{'cmd'}"; }
|
||||
if ($avail{'sourcembuffer'}) { $synccmd .= " | $mbuffercmd $args{'source-bwlimit'} $mbufferoptions"; }
|
||||
$synccmd .= "' | ";
|
||||
if ($avail{'targetmbuffer'}) { $synccmd .= "$mbuffercmd $args{'target-bwlimit'} $mbufferoptions | "; }
|
||||
if ($avail{'compress'}) { $synccmd .= "$args{'decompresscmd'} | "; }
|
||||
if ($avail{'compress'}) { $synccmd .= "$compressargs{'decomcmd'} | "; }
|
||||
if ($avail{'localpv'} && !$quiet) { $synccmd .= "$pvcmd -s $pvsize | "; }
|
||||
$synccmd .= "$recvcmd";
|
||||
} else {
|
||||
#remote source, remote target... weird, but whatever, I'm not here to judge you.
|
||||
#$synccmd = "$sshcmd $sourcehost '$sendcmd | $args{'compresscmd'} | $mbuffercmd' | $args{'decompresscmd'} | $pvcmd | $args{'compresscmd'} | $mbuffercmd | $sshcmd $targethost '$args{'decompresscmd'} | $mbuffercmd | $recvcmd'";
|
||||
#$synccmd = "$sshcmd $sourcehost '$sendcmd | $compressargs{'cmd'} | $mbuffercmd' | $compressargs{'decomcmd'} | $pvcmd | $compressargs{'cmd'} | $mbuffercmd | $sshcmd $targethost '$compressargs{'decomcmd'} | $mbuffercmd | $recvcmd'";
|
||||
$synccmd = "$sshcmd $sourcehost '$sendcmd";
|
||||
if ($avail{'compress'}) { $synccmd .= " | $args{'compresscmd'}"; }
|
||||
if ($avail{'compress'}) { $synccmd .= " | $compressargs{'cmd'}"; }
|
||||
if ($avail{'sourcembuffer'}) { $synccmd .= " | $mbuffercmd $args{'source-bwlimit'} $mbufferoptions"; }
|
||||
$synccmd .= "' | ";
|
||||
if ($avail{'compress'}) { $synccmd .= "$args{'decompresscmd'} | "; }
|
||||
if ($avail{'compress'}) { $synccmd .= "$compressargs{'decomcmd'} | "; }
|
||||
if ($avail{'localpv'} && !$quiet) { $synccmd .= "$pvcmd -s $pvsize | "; }
|
||||
if ($avail{'compress'}) { $synccmd .= "$args{'compresscmd'} | "; }
|
||||
if ($avail{'compress'}) { $synccmd .= "$compressargs{'cmd'} | "; }
|
||||
if ($avail{'localmbuffer'}) { $synccmd .= "$mbuffercmd $mbufferoptions | "; }
|
||||
$synccmd .= "$sshcmd $targethost '";
|
||||
if ($avail{'targetmbuffer'}) { $synccmd .= "$mbuffercmd $args{'target-bwlimit'} $mbufferoptions | "; }
|
||||
if ($avail{'compress'}) { $synccmd .= "$args{'decompresscmd'} | "; }
|
||||
if ($avail{'compress'}) { $synccmd .= "$compressargs{'decomcmd'} | "; }
|
||||
$synccmd .= "$recvcmd'";
|
||||
}
|
||||
return $synccmd;
|
||||
|
@ -875,7 +817,7 @@ sub getssh {
|
|||
if ($remoteuser eq 'root') { $isroot = 1; } else { $isroot = 0; }
|
||||
# now we need to establish a persistent master SSH connection
|
||||
$socket = "/tmp/syncoid-$remoteuser-$rhost-" . time();
|
||||
open FH, "$sshcmd -M -S $socket -o ControlPersist=1m $sshport $rhost exit |";
|
||||
open FH, "$sshcmd -M -S $socket -o ControlPersist=1m $args{'sshport'} $rhost exit |";
|
||||
close FH;
|
||||
$rhost = "-S $socket $rhost";
|
||||
} else {
|
||||
|
@ -995,4 +937,40 @@ sub getdate {
|
|||
return %date;
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
syncoid - ZFS snapshot replication tool
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
syncoid [options]... SOURCE TARGET
|
||||
or syncoid [options]... SOURCE [USER@]HOST:TARGET
|
||||
or syncoid [options]... [USER@]HOST:SOURCE [TARGET]
|
||||
or syncoid [options]... [USER@]HOST:SOURCE [USER@]HOST:TARGET
|
||||
|
||||
SOURCE Source ZFS dataset. Can be either local or remote
|
||||
TARGET Target ZFS dataset. Can be either local or remote
|
||||
|
||||
Options:
|
||||
|
||||
--compress=FORMAT Compresses data during transfer. Currently accepted options are gzip, pigz-fast, pigz-slow, lzo (default) & none
|
||||
--recursive|r Also transfers child datasets
|
||||
--source-bwlimit=<limit k|m|g|t> Bandwidth limit on the source transfer
|
||||
--target-bwlimit=<limit k|m|g|t> Bandwidth limit on the target transfer
|
||||
--no-stream Replicates using newest snapshot instead of intermediates
|
||||
--no-sync-snap Does not create new snapshot, only transfers existing
|
||||
|
||||
--sshkey=FILE Specifies a ssh public key to use to connect
|
||||
--sshport=PORT Connects to remote on a particular port
|
||||
--sshcipher|c=CIPHER Passes CIPHER to ssh to use a particular cipher set
|
||||
--sshoption|o=OPTION Passes OPTION to ssh for remote usage. Can be specified multiple times
|
||||
|
||||
--help Prints this helptext
|
||||
--verbose Prints the version number
|
||||
--debug Prints out a lot of additional information during a syncoid run
|
||||
--monitor-version Currently does nothing
|
||||
--quiet Suppresses non-error output
|
||||
--dumpsnaps Dumps a list of snapshots during the run
|
||||
--no-command-checks Do not check command existence before attempting transfer. Not recommended
|
||||
|
|
Loading…
Reference in New Issue