mirror of https://github.com/digint/btrbk
btrbk: change preserve semantics (incompatible):
- add "{snapshot,target}_preserve_all" configuration option - change semantics of "{snapshot,target}_preserve NNd" to "preserve latest daily only" - change default of "preserve daily,monthly" to 0 (was: all) - add deprecated warning and enter compatibility mode: preserve everything if deprecated {snapshot,target}_preserve_* options are encounteredpull/88/head
parent
ceb4dbf19c
commit
326edfcc29
|
@ -5,6 +5,7 @@ btrbk-current
|
||||||
* Propagate targets defined in "volume" or "root" context to all
|
* Propagate targets defined in "volume" or "root" context to all
|
||||||
"subvolume" sections (close: #78).
|
"subvolume" sections (close: #78).
|
||||||
* Added "{snapshot,target}_preserve NNd NNw NNm NNy" shortcut.
|
* Added "{snapshot,target}_preserve NNd NNw NNm NNy" shortcut.
|
||||||
|
* Added hourly retention policies (close: #36).
|
||||||
* Added yearly retention policies (close: #69).
|
* Added yearly retention policies (close: #69).
|
||||||
* Added configuration option "rate_limit" (close: #72).
|
* Added configuration option "rate_limit" (close: #72).
|
||||||
* Detect interrupted transfers of raw targets (close: #75).
|
* Detect interrupted transfers of raw targets (close: #75).
|
||||||
|
|
255
btrbk
255
btrbk
|
@ -43,7 +43,7 @@ use strict;
|
||||||
use warnings FATAL => qw( all );
|
use warnings FATAL => qw( all );
|
||||||
|
|
||||||
use Carp qw(confess);
|
use Carp qw(confess);
|
||||||
use Date::Calc qw(Today_and_Now Delta_Days Day_of_Week);
|
use Date::Calc qw(Today_and_Now Delta_Days Delta_DHMS Day_of_Week);
|
||||||
use Getopt::Long qw(GetOptions);
|
use Getopt::Long qw(GetOptions);
|
||||||
use POSIX qw(strftime);
|
use POSIX qw(strftime);
|
||||||
use Data::Dumper;
|
use Data::Dumper;
|
||||||
|
@ -80,16 +80,10 @@ my %config_options = (
|
||||||
incremental => { default => "yes", accept => [ "yes", "no", "strict" ] },
|
incremental => { default => "yes", accept => [ "yes", "no", "strict" ] },
|
||||||
resume_missing => { default => "yes", accept => [ "yes", "no" ] },
|
resume_missing => { default => "yes", accept => [ "yes", "no" ] },
|
||||||
preserve_day_of_week => { default => "sunday", accept => [ (keys %day_of_week_map) ] },
|
preserve_day_of_week => { default => "sunday", accept => [ (keys %day_of_week_map) ] },
|
||||||
snapshot_preserve_daily => { default => "all", accept => [ "all" ], accept_numeric => 1, context => [ "root", "volume", "subvolume" ] },
|
snapshot_preserve => { default => undef, accept_preserve_matrix => 1, context => [ "root", "volume", "subvolume" ], },
|
||||||
snapshot_preserve_weekly => { default => 0, accept => [ "all" ], accept_numeric => 1, context => [ "root", "volume", "subvolume" ] },
|
snapshot_preserve_all => { default => '12h', accept => [ "forever", "no" ], accept_regexp => qr/^[0-9]+[hdwmy]$/, context => [ "root", "volume", "subvolume" ], },
|
||||||
snapshot_preserve_monthly => { default => "all", accept => [ "all" ], accept_numeric => 1, context => [ "root", "volume", "subvolume" ] },
|
target_preserve => { default => undef, accept_preserve_matrix => 1 },
|
||||||
snapshot_preserve_yearly => { default => "0", accept => [ "all" ], accept_numeric => 1, context => [ "root", "volume", "subvolume" ] },
|
target_preserve_all => { default => undef, accept => [ "forever", "no" ], accept_regexp => qr/^[0-9]+[hdwmy]$/ },
|
||||||
snapshot_preserve => { shortcut => 1, accept_preserve_matrix => 1, context => [ "root", "volume", "subvolume" ], },
|
|
||||||
target_preserve_daily => { default => "all", accept => [ "all" ], accept_numeric => 1 },
|
|
||||||
target_preserve_weekly => { default => 0, accept => [ "all" ], accept_numeric => 1 },
|
|
||||||
target_preserve_monthly => { default => "all", accept => [ "all" ], accept_numeric => 1 },
|
|
||||||
target_preserve_yearly => { default => "0", accept => [ "all" ], accept_numeric => 1 },
|
|
||||||
target_preserve => { shortcut => 1, accept_preserve_matrix => 1, },
|
|
||||||
btrfs_commit_delete => { default => undef, accept => [ "after", "each", "no" ] },
|
btrfs_commit_delete => { default => undef, accept => [ "after", "each", "no" ] },
|
||||||
ssh_identity => { default => undef, accept_file => { absolute => 1 } },
|
ssh_identity => { default => undef, accept_file => { absolute => 1 } },
|
||||||
ssh_user => { default => "root", accept_regexp => qr/^[a-z_][a-z0-9_-]*$/ },
|
ssh_user => { default => "root", accept_regexp => qr/^[a-z_][a-z0-9_-]*$/ },
|
||||||
|
@ -110,6 +104,18 @@ my %config_options = (
|
||||||
group => { default => undef, accept_regexp => qr/^$group_match(\s*,\s*$group_match)*$/, split => qr/\s*,\s*/ },
|
group => { default => undef, accept_regexp => qr/^$group_match(\s*,\s*$group_match)*$/, split => qr/\s*,\s*/ },
|
||||||
|
|
||||||
# deprecated options
|
# deprecated options
|
||||||
|
snapshot_preserve_daily => { default => 'all', accept => [ "all" ], accept_numeric => 1, context => [ "root", "volume", "subvolume" ],
|
||||||
|
deprecated => { COMPAT_PRESERVE_ALL => 1, DEFAULT => { warn => 'Please use "snapshot_preserve" and/or "snapshot_preserve_all"' } } },
|
||||||
|
snapshot_preserve_weekly => { default => 0, accept => [ "all" ], accept_numeric => 1, context => [ "root", "volume", "subvolume" ],
|
||||||
|
deprecated => { COMPAT_PRESERVE_ALL => 1, DEFAULT => { warn => 'Please use "snapshot_preserve" and/or "snapshot_preserve_all"' } } },
|
||||||
|
snapshot_preserve_monthly => { default => 'all', accept => [ "all" ], accept_numeric => 1, context => [ "root", "volume", "subvolume" ],
|
||||||
|
deprecated => { COMPAT_PRESERVE_ALL => 1, DEFAULT => { warn => 'Please use "snapshot_preserve" and/or "snapshot_preserve_all"' } } },
|
||||||
|
target_preserve_daily => { default => 'all', accept => [ "all" ], accept_numeric => 1,
|
||||||
|
deprecated => { COMPAT_PRESERVE_ALL => 1, DEFAULT => { warn => 'Please use "snapshot_preserve" and/or "snapshot_preserve_all"' } } },
|
||||||
|
target_preserve_weekly => { default => 0, accept => [ "all" ], accept_numeric => 1,
|
||||||
|
deprecated => { COMPAT_PRESERVE_ALL => 1, DEFAULT => { warn => 'Please use "snapshot_preserve" and/or "snapshot_preserve_all"' } } },
|
||||||
|
target_preserve_monthly => { default => 'all', accept => [ "all" ], accept_numeric => 1,
|
||||||
|
deprecated => { COMPAT_PRESERVE_ALL => 1, DEFAULT => { warn => 'Please use "snapshot_preserve" and/or "snapshot_preserve_all"' } } },
|
||||||
snapshot_create_always => { default => undef, accept => [ "yes", "no" ],
|
snapshot_create_always => { default => undef, accept => [ "yes", "no" ],
|
||||||
deprecated => { yes => { warn => "Please use \"snapshot_create always\"",
|
deprecated => { yes => { warn => "Please use \"snapshot_create always\"",
|
||||||
replace_key => "snapshot_create",
|
replace_key => "snapshot_create",
|
||||||
|
@ -153,7 +159,7 @@ my %table_formats = (
|
||||||
|
|
||||||
schedule => { table => [ qw( action host subvol scheme reason ) ],
|
schedule => { table => [ qw( action host subvol scheme reason ) ],
|
||||||
long => [ qw( action host root_path subvol_path scheme reason ) ],
|
long => [ qw( action host root_path subvol_path scheme reason ) ],
|
||||||
raw => [ qw( topic action url host path dow d w m y) ],
|
raw => [ qw( topic action url host path dow min h d w m y) ],
|
||||||
},
|
},
|
||||||
|
|
||||||
usage => { table => [ qw( host path size used free ) ],
|
usage => { table => [ qw( host path size used free ) ],
|
||||||
|
@ -184,6 +190,7 @@ my $tlog_fh;
|
||||||
my $current_transaction;
|
my $current_transaction;
|
||||||
my @transaction_log;
|
my @transaction_log;
|
||||||
my %config_override;
|
my %config_override;
|
||||||
|
my @today_and_now;
|
||||||
|
|
||||||
|
|
||||||
$SIG{__DIE__} = sub {
|
$SIG{__DIE__} = sub {
|
||||||
|
@ -2018,13 +2025,21 @@ sub config_preserve_hash($$)
|
||||||
{
|
{
|
||||||
my $config = shift || die;
|
my $config = shift || die;
|
||||||
my $prefix = shift || die;
|
my $prefix = shift || die;
|
||||||
return (
|
my $ret = config_key($config, $prefix . "_preserve") // {};
|
||||||
preserve_day_of_week => config_key($config, "preserve_day_of_week"),
|
my $preserve_all = config_key($config, $prefix . "_preserve_all");
|
||||||
preserve_daily => config_key($config, "${prefix}_preserve_daily"),
|
if(defined($preserve_all)) {
|
||||||
preserve_weekly => config_key($config, "${prefix}_preserve_weekly"),
|
$ret->{all} = $preserve_all; # used for raw schedule output
|
||||||
preserve_monthly => config_key($config, "${prefix}_preserve_monthly"),
|
if($preserve_all eq 'forever') {
|
||||||
preserve_yearly => config_key($config, "${prefix}_preserve_yearly"),
|
$ret->{all_n} = 'forever';
|
||||||
);
|
}
|
||||||
|
elsif($preserve_all =~ /^([0-9]+)([hdwmy])$/) {
|
||||||
|
$ret->{all_n} = $1;
|
||||||
|
$ret->{all_q} = $2;
|
||||||
|
}
|
||||||
|
else { die; }
|
||||||
|
}
|
||||||
|
$ret->{dow} = config_key($config, "preserve_day_of_week");
|
||||||
|
return $ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2039,8 +2054,6 @@ sub config_dump_keys($;@)
|
||||||
foreach my $key (sort keys %config_options)
|
foreach my $key (sort keys %config_options)
|
||||||
{
|
{
|
||||||
my $val;
|
my $val;
|
||||||
next if($config_options{$key}->{deprecated});
|
|
||||||
next if($config_options{$key}->{shortcut});
|
|
||||||
if($opts{resolve}) {
|
if($opts{resolve}) {
|
||||||
$val = config_key($config, $key);
|
$val = config_key($config, $key);
|
||||||
} else {
|
} else {
|
||||||
|
@ -2055,6 +2068,9 @@ sub config_dump_keys($;@)
|
||||||
next; # both undef, skip
|
next; # both undef, skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if($config_options{$key}->{accept_preserve_matrix}) {
|
||||||
|
$val = format_preserve_matrix($val);
|
||||||
|
}
|
||||||
if(ref($val) eq "ARRAY") {
|
if(ref($val) eq "ARRAY") {
|
||||||
my $val2 = join(',', @$val);
|
my $val2 = join(',', @$val);
|
||||||
$val = $val2;
|
$val = $val2;
|
||||||
|
@ -2097,18 +2113,24 @@ sub append_config_option($$$$;$)
|
||||||
}
|
}
|
||||||
|
|
||||||
if($opt->{accept_preserve_matrix}) {
|
if($opt->{accept_preserve_matrix}) {
|
||||||
# special case: preserve matrix of form: "[NNd] [NNw] [NNm] [NNy]"
|
my %preserve;
|
||||||
my $s = $value;
|
my $s = ' ' . $value;
|
||||||
TRACE "option \"$key=$value\" is preserve matrix, parsing...";
|
while($s =~ s/\s+(\*|[0-9]+)([hdwmyHDWMY])//) {
|
||||||
if($s =~ s/([0-9*][0-9]*)\s*d\s*,?\s*//) { my $v = ($1 eq '*' ? 'all' : $1); $config->{$key . "_daily"} = $v; TRACE "adding option \"${key}_daily=$v\" to $context context"; }
|
my $n = $1;
|
||||||
if($s =~ s/([0-9*][0-9]*)\s*w\s*,?\s*//) { my $v = ($1 eq '*' ? 'all' : $1); $config->{$key . "_weekly"} = $v; TRACE "adding option \"${key}_weekly=$v\" to $context context"; }
|
my $q = lc($2); # qw( h d w m y )
|
||||||
if($s =~ s/([0-9*][0-9]*)\s*m\s*,?\s*//) { my $v = ($1 eq '*' ? 'all' : $1); $config->{$key . "_monthly"} = $v; TRACE "adding option \"${key}_monthly=$v\" to $context context"; }
|
$n = 'all' if($n eq '*');
|
||||||
if($s =~ s/([0-9*][0-9]*)\s*y\s*,?\s*//) { my $v = ($1 eq '*' ? 'all' : $1); $config->{$key . "_yearly"} = $v; TRACE "adding option \"${key}_yearly=$v\" to $context context"; }
|
if(exists($preserve{$q})) {
|
||||||
|
ERROR "Value \"$value\" failed input validation for option \"$key\": multiple definitions of '$q'" . $config_file_statement;
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
$preserve{$q} = $n;
|
||||||
|
}
|
||||||
unless($s eq "") {
|
unless($s eq "") {
|
||||||
ERROR "Value \"$value\" failed input validation for option \"$key\"" . $config_file_statement;
|
ERROR "Value \"$value\" failed input validation for option \"$key\"" . $config_file_statement;
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
TRACE "successfully parsed preserve matrix";
|
TRACE "adding preserve matrix $context context:" . Data::Dumper->new([\%preserve], [ $key ])->Indent(0)->Pad(' ')->Quotekeys(0)->Pair('=>')->Dump() if($loglevel >= 4);
|
||||||
|
$config->{$key} = \%preserve;
|
||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2164,6 +2186,11 @@ sub append_config_option($$$$;$)
|
||||||
$value = $replace_value;
|
$value = $replace_value;
|
||||||
WARN "Using \"$key $value\"";
|
WARN "Using \"$key $value\"";
|
||||||
}
|
}
|
||||||
|
if($opt->{deprecated}->{COMPAT_PRESERVE_ALL}) {
|
||||||
|
$config_override{snapshot_preserve_all} = 'forever';
|
||||||
|
$config_override{target_preserve_all} = 'forever';
|
||||||
|
WARN "Refusing to delete anything!";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TRACE "adding option \"$key=$value\" to $context context";
|
TRACE "adding option \"$key=$value\" to $context context";
|
||||||
|
@ -2324,7 +2351,6 @@ sub init_config(@)
|
||||||
# set defaults
|
# set defaults
|
||||||
foreach (keys %config_options) {
|
foreach (keys %config_options) {
|
||||||
next if $config_options{$_}->{deprecated}; # don't pollute hash with deprecated options
|
next if $config_options{$_}->{deprecated}; # don't pollute hash with deprecated options
|
||||||
next if $config_options{$_}->{shortcut}; # don't pollute hash with shortcuts
|
|
||||||
$config_root{$_} = $config_options{$_}->{default};
|
$config_root{$_} = $config_options{$_}->{default};
|
||||||
}
|
}
|
||||||
return \%config_root;
|
return \%config_root;
|
||||||
|
@ -2553,40 +2579,44 @@ sub cmp_date($$)
|
||||||
sub schedule(@)
|
sub schedule(@)
|
||||||
{
|
{
|
||||||
my %args = @_;
|
my %args = @_;
|
||||||
my $schedule = $args{schedule} || die;
|
my $schedule = $args{schedule} || die;
|
||||||
my @today = @{$args{today}};
|
my $preserve = $args{preserve} || die;
|
||||||
my $preserve_day_of_week = $args{preserve_day_of_week} || die;
|
my $preserve_latest = $args{preserve_latest} || 0;
|
||||||
my $preserve_daily = $args{preserve_daily} // die;
|
my $results_list = $args{results};
|
||||||
my $preserve_weekly = $args{preserve_weekly} // die;
|
my $result_hints = $args{result_hints} // {};
|
||||||
my $preserve_monthly = $args{preserve_monthly} // die;
|
|
||||||
my $preserve_yearly = $args{preserve_yearly} // die;
|
|
||||||
my $preserve_latest = $args{preserve_latest} || 0;
|
|
||||||
my $results_list = $args{results};
|
|
||||||
my $result_hints = $args{result_hints} // {};
|
|
||||||
|
|
||||||
my %preserve_matrix = ( d => $preserve_daily,
|
my $preserve_day_of_week = $preserve->{dow} || die;
|
||||||
w => $preserve_weekly,
|
my $preserve_all_n = $preserve->{all_n};
|
||||||
m => $preserve_monthly,
|
my $preserve_all_q = $preserve->{all_q};
|
||||||
y => $preserve_yearly,
|
my $preserve_hourly = $preserve->{h};
|
||||||
dow => $preserve_day_of_week,
|
my $preserve_daily = $preserve->{d};
|
||||||
);
|
my $preserve_weekly = $preserve->{w};
|
||||||
DEBUG "Schedule: " . format_preserve_matrix(%preserve_matrix, format => "debug_text");
|
my $preserve_monthly = $preserve->{m};
|
||||||
|
my $preserve_yearly = $preserve->{y};
|
||||||
|
|
||||||
|
my @today_ymdhms = ref($args{today_and_now}) ? @{$args{today_and_now}} : @today_and_now;
|
||||||
|
die unless(scalar(@today_ymdhms) == 6);
|
||||||
|
|
||||||
|
DEBUG "Schedule: " . format_preserve_matrix($preserve, format => "debug_text");
|
||||||
|
|
||||||
# sort the schedule, ascending by date
|
# sort the schedule, ascending by date
|
||||||
my @sorted_schedule = sort { cmp_date($a->{btrbk_date}, $b->{btrbk_date} ) } @$schedule;
|
my @sorted_schedule = sort { cmp_date($a->{btrbk_date}, $b->{btrbk_date} ) } @$schedule;
|
||||||
|
|
||||||
# first, do our calendar calculations
|
# first, do our calendar calculations
|
||||||
# note: our week starts on $preserve_day_of_week
|
# note: our week starts on $preserve_day_of_week
|
||||||
|
my @today = @today_ymdhms[0..2];
|
||||||
my $delta_days_to_eow_from_today = $day_of_week_map{$preserve_day_of_week} - Day_of_Week(@today) - 1;
|
my $delta_days_to_eow_from_today = $day_of_week_map{$preserve_day_of_week} - Day_of_Week(@today) - 1;
|
||||||
$delta_days_to_eow_from_today = $delta_days_to_eow_from_today + 7 if($delta_days_to_eow_from_today < 0);
|
$delta_days_to_eow_from_today = $delta_days_to_eow_from_today + 7 if($delta_days_to_eow_from_today < 0);
|
||||||
TRACE "last day before next $preserve_day_of_week is in $delta_days_to_eow_from_today days";
|
TRACE "last day before next $preserve_day_of_week is in $delta_days_to_eow_from_today days";
|
||||||
foreach my $href (@sorted_schedule)
|
foreach my $href (@sorted_schedule)
|
||||||
{
|
{
|
||||||
my @date = @{$href->{btrbk_date}}[0..2]; # Date::Calc takes: @date = ( yy, mm, dd )
|
my @date = (@{$href->{btrbk_date}}[0..4], 0); # btrbk_date: (y m d h m NN); @date: (y m d h m s)
|
||||||
my $delta_days = Delta_Days(@date, @today);
|
my ($dd, $dh, undef, undef) = Delta_DHMS(@date, @today_ymdhms);
|
||||||
|
my $delta_days = Delta_Days(@date[0..2], @today);
|
||||||
my $delta_days_to_eow = $delta_days + $delta_days_to_eow_from_today;
|
my $delta_days_to_eow = $delta_days + $delta_days_to_eow_from_today;
|
||||||
{
|
{
|
||||||
use integer; # do integer arithmetics
|
use integer; # do integer arithmetics
|
||||||
|
$href->{delta_hours} = ($dd * 24) + $dh;
|
||||||
$href->{delta_days} = $delta_days;
|
$href->{delta_days} = $delta_days;
|
||||||
$href->{delta_weeks} = $delta_days_to_eow / 7;
|
$href->{delta_weeks} = $delta_days_to_eow / 7;
|
||||||
$href->{delta_months} = ($today[0] - $date[0]) * 12 + ($today[1] - $date[1]);
|
$href->{delta_months} = ($today[0] - $date[0]) * 12 + ($today[1] - $date[1]);
|
||||||
|
@ -2604,33 +2634,65 @@ sub schedule(@)
|
||||||
$href->{preserve} ||= $preserve_latest;
|
$href->{preserve} ||= $preserve_latest;
|
||||||
}
|
}
|
||||||
|
|
||||||
# filter daily, weekly, monthly
|
my %last_in_delta_hours;
|
||||||
|
my %last_in_delta_days;
|
||||||
my %first_in_delta_weeks;
|
my %first_in_delta_weeks;
|
||||||
my %last_weekly_in_delta_months;
|
my %last_weekly_in_delta_months;
|
||||||
my %last_monthly_in_delta_years;
|
my %last_monthly_in_delta_years;
|
||||||
|
|
||||||
|
# filter "preserve all within N days/weeks/..."
|
||||||
foreach my $href (@sorted_schedule) {
|
foreach my $href (@sorted_schedule) {
|
||||||
if($preserve_daily && (($preserve_daily eq "all") || ($href->{delta_days} <= $preserve_daily))) {
|
if($preserve_all_n) {
|
||||||
$href->{preserve} ||= "preserved daily: $href->{delta_days} days ago";
|
if($preserve_all_n eq 'forever') {
|
||||||
|
$href->{preserve} ||= "preserve all: forever";
|
||||||
|
} elsif($preserve_all_q eq 'h') {
|
||||||
|
$href->{preserve} ||= "preserve all: $href->{delta_hours} hours ago" if($href->{delta_hours} <= $preserve_all_n);
|
||||||
|
} elsif($preserve_all_q eq 'd') {
|
||||||
|
$href->{preserve} ||= "preserve all: $href->{delta_days} days ago" if($href->{delta_days} <= $preserve_all_n);
|
||||||
|
} elsif($preserve_all_q eq 'w') {
|
||||||
|
$href->{preserve} ||= "preserve all: $href->{delta_weeks} weeks ago" if($href->{delta_weeks} <= $preserve_all_n);
|
||||||
|
} elsif($preserve_all_q eq 'm') {
|
||||||
|
$href->{preserve} ||= "preserve all: $href->{delta_months} months ago" if($href->{delta_months} <= $preserve_all_n);
|
||||||
|
} elsif($preserve_all_q eq 'y') {
|
||||||
|
$href->{preserve} ||= "preserve all: $href->{delta_years} years ago" if($href->{delta_years} <= $preserve_all_n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$last_in_delta_hours{$href->{delta_hours}} = $href;
|
||||||
|
}
|
||||||
|
|
||||||
|
# filter hourly, daily, weekly, monthly, yearly
|
||||||
|
foreach (sort {$b <=> $a} keys %last_in_delta_hours) {
|
||||||
|
my $href = $last_in_delta_hours{$_} || die;
|
||||||
|
if($preserve_hourly && (($preserve_hourly eq 'all') || $href->{delta_hours} <= $preserve_hourly)) {
|
||||||
|
$href->{preserve} ||= "preserved hourly: last present of hour, $href->{delta_hours} hours ago";
|
||||||
|
}
|
||||||
|
$last_in_delta_days{$href->{delta_days}} = $href;
|
||||||
|
}
|
||||||
|
foreach (sort {$b <=> $a} keys %last_in_delta_days) {
|
||||||
|
my $href = $last_in_delta_days{$_} || die;
|
||||||
|
if($preserve_daily && (($preserve_daily eq 'all') || ($href->{delta_days} <= $preserve_daily))) {
|
||||||
|
$href->{preserve} ||= "preserved daily: last present of day, $href->{delta_days} days ago";
|
||||||
}
|
}
|
||||||
$first_in_delta_weeks{$href->{delta_weeks}} //= $href;
|
$first_in_delta_weeks{$href->{delta_weeks}} //= $href;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (sort {$b <=> $a} keys %first_in_delta_weeks) {
|
foreach (sort {$b <=> $a} keys %first_in_delta_weeks) {
|
||||||
my $href = $first_in_delta_weeks{$_} || die;
|
my $href = $first_in_delta_weeks{$_} || die;
|
||||||
if($preserve_weekly && (($preserve_weekly eq "all") || ($href->{delta_weeks} <= $preserve_weekly))) {
|
if($preserve_weekly && (($preserve_weekly eq 'all') || ($href->{delta_weeks} <= $preserve_weekly))) {
|
||||||
$href->{preserve} ||= "preserved weekly: $href->{delta_weeks} weeks ago, $href->{err_days_text}";
|
$href->{preserve} ||= "preserved weekly: $href->{delta_weeks} weeks ago, $href->{err_days_text}";
|
||||||
}
|
}
|
||||||
$last_weekly_in_delta_months{$href->{delta_months}} = $href;
|
$last_weekly_in_delta_months{$href->{delta_months}} = $href;
|
||||||
}
|
}
|
||||||
foreach (sort {$b <=> $a} keys %last_weekly_in_delta_months) {
|
foreach (sort {$b <=> $a} keys %last_weekly_in_delta_months) {
|
||||||
my $href = $last_weekly_in_delta_months{$_} || die;
|
my $href = $last_weekly_in_delta_months{$_} || die;
|
||||||
if($preserve_monthly && (($preserve_monthly eq "all") || ($href->{delta_months} <= $preserve_monthly))) {
|
if($preserve_monthly && (($preserve_monthly eq 'all') || ($href->{delta_months} <= $preserve_monthly))) {
|
||||||
$href->{preserve} ||= "preserved monthly: last present weekly of month $href->{month} ($href->{delta_months} months ago, $href->{err_days_text})";
|
$href->{preserve} ||= "preserved monthly: last present weekly of month $href->{month} ($href->{delta_months} months ago, $href->{err_days_text})";
|
||||||
}
|
}
|
||||||
$last_monthly_in_delta_years{$href->{delta_years}} = $href;
|
$last_monthly_in_delta_years{$href->{delta_years}} = $href;
|
||||||
}
|
}
|
||||||
foreach (sort {$b <=> $a} keys %last_monthly_in_delta_years) {
|
foreach (sort {$b <=> $a} keys %last_monthly_in_delta_years) {
|
||||||
my $href = $last_monthly_in_delta_years{$_} || die;
|
my $href = $last_monthly_in_delta_years{$_} || die;
|
||||||
if($preserve_yearly && (($preserve_yearly eq "all") || ($href->{delta_years} <= $preserve_yearly))) {
|
if($preserve_yearly && (($preserve_yearly eq 'all') || ($href->{delta_years} <= $preserve_yearly))) {
|
||||||
$href->{preserve} ||= "preserved yearly: last present weekly of year $href->{year} ($href->{delta_years} years ago, $href->{err_days_text})";
|
$href->{preserve} ||= "preserved yearly: last present weekly of year $href->{year} ($href->{delta_years} years ago, $href->{err_days_text})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2638,8 +2700,8 @@ sub schedule(@)
|
||||||
# assemble results
|
# assemble results
|
||||||
my @delete;
|
my @delete;
|
||||||
my @preserve;
|
my @preserve;
|
||||||
my %result_base = ( %preserve_matrix,
|
my %result_base = ( %$preserve,
|
||||||
scheme => format_preserve_matrix(%preserve_matrix, format => "short"),
|
scheme => format_preserve_matrix($preserve),
|
||||||
%$result_hints,
|
%$result_hints,
|
||||||
);
|
);
|
||||||
my $count_defined = 0;
|
my $count_defined = 0;
|
||||||
|
@ -2671,40 +2733,49 @@ sub schedule(@)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sub format_preserve_matrix(@)
|
sub format_preserve_matrix($@)
|
||||||
{
|
{
|
||||||
my %args = @_;
|
my $preserve = shift || die;
|
||||||
my $dow = $args{dow} // $args{preserve_day_of_week};
|
my %opts = @_;
|
||||||
my $d = $args{d} // $args{preserve_daily};
|
my $format = $opts{format} // "short";
|
||||||
my $w = $args{w} // $args{preserve_weekly};
|
|
||||||
my $m = $args{m} // $args{preserve_monthly};
|
|
||||||
my $y = $args{y} // $args{preserve_yearly};
|
|
||||||
my $format = $args{format} // "long";
|
|
||||||
|
|
||||||
if($format eq "debug_text") {
|
if($format eq "debug_text") {
|
||||||
my $s = "preserving all within $d days";
|
my @out;
|
||||||
unless($d eq 'all') {
|
my %trans = ( h => 'hours', d => 'days', w => 'weeks', m => 'months', y => 'years' );
|
||||||
$s .= "; first in week (starting on $dow), for $w weeks" if($w);
|
if($preserve->{all_n} && $preserve->{all_n} eq 'forever') {
|
||||||
unless($w eq 'all') {
|
push @out, "preserving all forever";
|
||||||
$s .= "; last weekly of month, for $m months" if($m);
|
}
|
||||||
unless($m eq 'all') {
|
else {
|
||||||
$s .= "; last weekly of year, for $y years" if($y);
|
push @out, "preserving all within $preserve->{all_n} $trans{$preserve->{all_q}}" if($preserve->{all_n} && $preserve->{all_q});
|
||||||
|
push @out, "last present daily for $preserve->{d} days" if($preserve->{d});
|
||||||
|
unless($preserve->{d} && ($preserve->{d} eq 'all')) {
|
||||||
|
push @out, "first daily in week (starting on $preserve->{dow}) for $preserve->{w} weeks" if($preserve->{w});
|
||||||
|
unless($preserve->{w} && ($preserve->{w} eq 'all')) {
|
||||||
|
push @out, "last weekly of month for $preserve->{m} months" if($preserve->{m});
|
||||||
|
unless($preserve->{m} && ($preserve->{m} eq 'all')) {
|
||||||
|
push @out, "last weekly of year for $preserve->{y} years" if($preserve->{y});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $s;
|
return join('; ', @out);
|
||||||
}
|
}
|
||||||
|
|
||||||
$d =~ s/^all$/\*/;
|
my $s = "";
|
||||||
$w =~ s/^all$/\*/;
|
if($preserve->{all_n} && ($preserve->{all_n} eq 'forever')) {
|
||||||
$m =~ s/^all$/\*/;
|
$s = '*d+';
|
||||||
$y =~ s/^all$/\*/;
|
|
||||||
if($format eq "short") {
|
|
||||||
# short format
|
|
||||||
return sprintf("%2sd %2sw %2sm %2sy", $d, $w, $m, $y);
|
|
||||||
}
|
}
|
||||||
# long format
|
else {
|
||||||
return sprintf("%2sd %2sw %2sm %2sy ($dow)", $d, $w, $m, $y);
|
$s .= $preserve->{all_n} . $preserve->{all_q} . '+' if($preserve->{all_n} && $preserve->{all_q});
|
||||||
|
foreach (qw(h d w m y)) {
|
||||||
|
my $val = $preserve->{$_} // 0;
|
||||||
|
next unless($val);
|
||||||
|
$val = '*' if($val eq 'all');
|
||||||
|
$s .= ($s ? ' ' : '') . $val . $_;
|
||||||
|
}
|
||||||
|
$s .= " ($preserve->{dow})" if($preserve->{dow} && ($preserve->{w} || $preserve->{m} || $preserve->{y}));
|
||||||
|
}
|
||||||
|
return $s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2910,12 +2981,11 @@ MAIN:
|
||||||
|
|
||||||
Getopt::Long::Configure qw(gnu_getopt);
|
Getopt::Long::Configure qw(gnu_getopt);
|
||||||
$Data::Dumper::Sortkeys = 1;
|
$Data::Dumper::Sortkeys = 1;
|
||||||
|
$Data::Dumper::Quotekeys = 0;
|
||||||
my $start_time = time;
|
my $start_time = time;
|
||||||
my @today_and_now = Today_and_Now();
|
@today_and_now = Today_and_Now();
|
||||||
my @today = @today_and_now[0..2];
|
|
||||||
my %config_override_opts;
|
my %config_override_opts;
|
||||||
|
|
||||||
|
|
||||||
my ($config_cmdline, $quiet, $verbose, $preserve_backups, $resume_only, $print_schedule);
|
my ($config_cmdline, $quiet, $verbose, $preserve_backups, $resume_only, $print_schedule);
|
||||||
unless(GetOptions(
|
unless(GetOptions(
|
||||||
'help|h' => sub { VERSION_MESSAGE(); HELP_MESSAGE(0); exit 0; },
|
'help|h' => sub { VERSION_MESSAGE(); HELP_MESSAGE(0); exit 0; },
|
||||||
|
@ -4037,7 +4107,7 @@ MAIN:
|
||||||
|
|
||||||
# find unique snapshot name
|
# find unique snapshot name
|
||||||
my $timestamp = ((config_key($svol, "timestamp_format") eq "short") ?
|
my $timestamp = ((config_key($svol, "timestamp_format") eq "short") ?
|
||||||
sprintf("%04d%02d%02d", @today) :
|
sprintf("%04d%02d%02d", @today_and_now[0..2]) :
|
||||||
sprintf("%04d%02d%02dT%02d%02d", @today_and_now[0..4]));
|
sprintf("%04d%02d%02dT%02d%02d", @today_and_now[0..4]));
|
||||||
my @unconfirmed_target_name;
|
my @unconfirmed_target_name;
|
||||||
my @lookup = map { $_->{SUBVOL_PATH} } @{vinfo_subvol_list($sroot)};
|
my @lookup = map { $_->{SUBVOL_PATH} } @{vinfo_subvol_list($sroot)};
|
||||||
|
@ -4133,10 +4203,9 @@ MAIN:
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
my ($preserve, undef) = schedule(
|
my ($preserve, undef) = schedule(
|
||||||
schedule => \@schedule,
|
schedule => \@schedule,
|
||||||
today => \@today,
|
preserve => config_preserve_hash($droot, "target"),
|
||||||
preserve_latest => $preserve_latest,
|
preserve_latest => $preserve_latest,
|
||||||
config_preserve_hash($droot, "target"),
|
|
||||||
);
|
);
|
||||||
my @resume = grep defined, @$preserve; # remove entries with no value from list (target subvolumes)
|
my @resume = grep defined, @$preserve; # remove entries with no value from list (target subvolumes)
|
||||||
$resume_total = scalar @resume;
|
$resume_total = scalar @resume;
|
||||||
|
@ -4231,8 +4300,7 @@ MAIN:
|
||||||
#
|
#
|
||||||
INFO "Cleaning backups of subvolume \"$svol->{PRINT}\": $droot->{PRINT}/$snapshot_basename.*";
|
INFO "Cleaning backups of subvolume \"$svol->{PRINT}\": $droot->{PRINT}/$snapshot_basename.*";
|
||||||
unless(macro_delete($droot, "", $snapshot_basename, $droot,
|
unless(macro_delete($droot, "", $snapshot_basename, $droot,
|
||||||
{ today => \@today,
|
{ preserve => config_preserve_hash($droot, "target"),
|
||||||
config_preserve_hash($droot, "target"),
|
|
||||||
preserve_latest => $preserve_latest_backup,
|
preserve_latest => $preserve_latest_backup,
|
||||||
results => $schedule_results,
|
results => $schedule_results,
|
||||||
result_hints => { topic => "backup", root_path => $droot->{PATH} },
|
result_hints => { topic => "backup", root_path => $droot->{PATH} },
|
||||||
|
@ -4258,8 +4326,7 @@ MAIN:
|
||||||
}
|
}
|
||||||
INFO "Cleaning snapshots: $sroot->{PRINT}/$snapdir_ts$snapshot_basename.*";
|
INFO "Cleaning snapshots: $sroot->{PRINT}/$snapdir_ts$snapshot_basename.*";
|
||||||
macro_delete($sroot, $snapdir_ts, $snapshot_basename, $svol,
|
macro_delete($sroot, $snapdir_ts, $snapshot_basename, $svol,
|
||||||
{ today => \@today,
|
{ preserve => config_preserve_hash($svol, "snapshot"),
|
||||||
config_preserve_hash($svol, "snapshot"),
|
|
||||||
preserve_latest => $preserve_latest_snapshot,
|
preserve_latest => $preserve_latest_snapshot,
|
||||||
results => $schedule_results,
|
results => $schedule_results,
|
||||||
result_hints => { topic => "snapshot", root_path => $sroot->{PATH} },
|
result_hints => { topic => "snapshot", root_path => $sroot->{PATH} },
|
||||||
|
|
115
doc/btrbk.conf.5
115
doc/btrbk.conf.5
|
@ -72,8 +72,8 @@ space-separated table format.
|
||||||
.PP
|
.PP
|
||||||
\fBtimestamp_format\fR short|long
|
\fBtimestamp_format\fR short|long
|
||||||
.RS 4
|
.RS 4
|
||||||
Timestamp format used as postfix for new snapshot subvolume names.
|
Timestamp format used as postfix for new snapshot subvolume
|
||||||
Defaults to \[lq]short\[rq].
|
names. Defaults to \[lq]short\[rq].
|
||||||
.PP
|
.PP
|
||||||
.IP \fBshort\fR
|
.IP \fBshort\fR
|
||||||
YYYYMMDD[_N] (e.g. "20150825", "20150825_1")
|
YYYYMMDD[_N] (e.g. "20150825", "20150825_1")
|
||||||
|
@ -129,58 +129,30 @@ snapshots, and missing backups are created if needed (complying to the
|
||||||
target preserve matrix). Defaults to \[lq]yes\[rq].
|
target preserve matrix). Defaults to \[lq]yes\[rq].
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBtarget_preserve\fR [<daily>d] [<weekly>w] [<monthly>m] [<yearly>y]
|
\fBsnapshot_preserve\fR <retention_policy>
|
||||||
.RS 4
|
.RS 4
|
||||||
Shortcut to set \fItarget_preserve_daily\fR,
|
Set retention policy for snapshots (see RETENTION POLICY below).
|
||||||
\fItarget_preserve_weekly\fR, \fItarget_preserve_monthly\fR and
|
|
||||||
\fItarget_preserve_yearly\fR options (see below) on a single line. Use
|
|
||||||
an asterisk for \[lq]all\[rq] (e.g. "target_preserve 60d *m" expands
|
|
||||||
to "target_preserve_daily 60" and "target_preserve_monthly all").
|
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBtarget_preserve_daily\fR all|<number>
|
\fBsnapshot_preserve_all\fR forever|no|<number>{h,d,w,m,y}
|
||||||
.RS 4
|
.RS 4
|
||||||
How many days of backups should be preserved. Defaults to \[lq]all\[rq].
|
Preserve all snapshots for the given amount of hours (h), days (d),
|
||||||
|
weeks (w), months (m) or years (y), regardless of how many there
|
||||||
|
are. Defaults to \[lq]12h\[rq].
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBtarget_preserve_weekly\fR all|<number>
|
\fBtarget_preserve\fR <retention_policy>
|
||||||
.RS 4
|
.RS 4
|
||||||
Defines for how many weeks back weekly backups should be
|
Set retention policy for backups (see RETENTION POLICY below).
|
||||||
preserved. Every backup created at \fIpreserve_day_of_week\fR (or
|
|
||||||
the next backup in this week if none was made on the exact day) is
|
|
||||||
considered as a weekly backup. Defaults to \[lq]0\[rq].
|
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBtarget_preserve_monthly\fR all|<number>
|
\fBtarget_preserve_all\fR forever|no|<number>{h,d,w,m,y}
|
||||||
.RS 4
|
.RS 4
|
||||||
Defines for how many months back monthly backups should be
|
Preserve all backups for the given amount of hours (h), days (d),
|
||||||
preserved. Every last weekly backup in a month is considered a
|
weeks (w), months (m) or years (y), regardless of how many there
|
||||||
monthly backup. Defaults to \[lq]all\[rq].
|
are. Defaults to \[lq]no\[rq].
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBtarget_preserve_yearly\fR all|<number>
|
|
||||||
.RS 4
|
|
||||||
Defines for how many years back yearly backups should be
|
|
||||||
preserved. Every last monthly backup in a year is considered a yearly
|
|
||||||
backup. Defaults to \[lq]0\[rq].
|
|
||||||
.RE
|
|
||||||
.PP
|
|
||||||
\fBsnapshot_preserve\fR
|
|
||||||
.PD 0
|
|
||||||
.PP
|
|
||||||
\fBsnapshot_preserve_daily\fR
|
|
||||||
.PP
|
|
||||||
\fBsnapshot_preserve_weekly\fR
|
|
||||||
.PP
|
|
||||||
\fBsnapshot_preserve_monthly\fR
|
|
||||||
.PP
|
|
||||||
\fBsnapshot_preserve_yearly\fR
|
|
||||||
.RS 4
|
|
||||||
Defines retention policy for the snapshots, with same semantics as the
|
|
||||||
\fItarget_preserve_*\fR options.
|
|
||||||
.RE
|
|
||||||
.PD
|
|
||||||
.PP
|
|
||||||
\fBpreserve_day_of_week\fR monday|tuesday|...|sunday
|
\fBpreserve_day_of_week\fR monday|tuesday|...|sunday
|
||||||
.RS 4
|
.RS 4
|
||||||
Defines on what day a backup/snapshot is considered as a weekly
|
Defines on what day a backup/snapshot is considered as a weekly
|
||||||
|
@ -253,6 +225,65 @@ manipulated by hand (moved, deleted).
|
||||||
.PP
|
.PP
|
||||||
Lines that contain a hash character (#) in the first column are
|
Lines that contain a hash character (#) in the first column are
|
||||||
treated as comments.
|
treated as comments.
|
||||||
|
.SH RETENTION POLICY
|
||||||
|
btrbk uses separate retention policy for snapshots and backups, which
|
||||||
|
are defined by the \fIsnapshot_preserve_all\fR,
|
||||||
|
\fIsnapshot_preserve\fR, \fItarget_preserve_all\fR,
|
||||||
|
\fItarget_preserve\fR, and the \fIpreserve_day_of_week\fR
|
||||||
|
configuration options.
|
||||||
|
.PP
|
||||||
|
Within this section, any statement about "backups" is always valid for
|
||||||
|
backups as well as snapshots, referring to \fItarget_preserve\fR or
|
||||||
|
\fIsnapshot_preserve\fR respectively.
|
||||||
|
.PP
|
||||||
|
The format for \fI<retention_policy>\fR is:
|
||||||
|
.PP
|
||||||
|
.RS 4
|
||||||
|
[<hourly>h] [<daily>d] [<weekly>w] [<monthly>m] [<yearly>y]
|
||||||
|
.RE
|
||||||
|
.PP
|
||||||
|
With the following semantics:
|
||||||
|
.PP
|
||||||
|
.B hourly
|
||||||
|
.RS 4
|
||||||
|
Defines how many hours back hourly backups should be preserved. The
|
||||||
|
last backup of an hour is considered an hourly backup. Note that if
|
||||||
|
you use <hourly> scheduling, make sure to also set
|
||||||
|
\fItimestamp_format\fR to \[lq]long\[rq], or the scheduler will
|
||||||
|
interpret the time as "00:00" (midnight).
|
||||||
|
.RE
|
||||||
|
.PP
|
||||||
|
.B daily
|
||||||
|
.RS 4
|
||||||
|
Defines how many days back daily backups should be preserved. The
|
||||||
|
last backup of a day is considered a daily backup.
|
||||||
|
.RE
|
||||||
|
.PP
|
||||||
|
.B weekly
|
||||||
|
.RS 4
|
||||||
|
Defines how many weeks back weekly backups should be preserved. The
|
||||||
|
first daily backup created at \fIpreserve_day_of_week\fR (or the first
|
||||||
|
backup in this week if none was made on the exact day) is considered
|
||||||
|
as a weekly backup.
|
||||||
|
.RE
|
||||||
|
.PP
|
||||||
|
.B monthly
|
||||||
|
.RS 4
|
||||||
|
Defines how many months back monthly backups should be
|
||||||
|
preserved. Every last weekly backup in a month is considered a
|
||||||
|
monthly backup.
|
||||||
|
.RE
|
||||||
|
.PP
|
||||||
|
.B yearly
|
||||||
|
.RS 4
|
||||||
|
Defines for how many years back yearly backups should be
|
||||||
|
preserved. Every last monthly backup in a year is considered a yearly
|
||||||
|
backup.
|
||||||
|
.RE
|
||||||
|
.PP
|
||||||
|
Use an asterisk for \[lq]all\[rq] (e.g. "target_preserve 60d *m"
|
||||||
|
states: "preserve daily backups for 60 days back, and all monthly
|
||||||
|
backups").
|
||||||
.SH TARGET TYPES
|
.SH TARGET TYPES
|
||||||
.PP
|
.PP
|
||||||
\fBsend-receive\fR
|
\fBsend-receive\fR
|
||||||
|
|
Loading…
Reference in New Issue