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
  encountered
pull/88/head
Axel Burri 2016-04-12 11:47:28 +02:00
parent ceb4dbf19c
commit 326edfcc29
3 changed files with 235 additions and 136 deletions

View File

@ -5,6 +5,7 @@ btrbk-current
* Propagate targets defined in "volume" or "root" context to all
"subvolume" sections (close: #78).
* Added "{snapshot,target}_preserve NNd NNw NNm NNy" shortcut.
* Added hourly retention policies (close: #36).
* Added yearly retention policies (close: #69).
* Added configuration option "rate_limit" (close: #72).
* Detect interrupted transfers of raw targets (close: #75).

243
btrbk
View File

@ -43,7 +43,7 @@ use strict;
use warnings FATAL => qw( all );
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 POSIX qw(strftime);
use Data::Dumper;
@ -80,16 +80,10 @@ my %config_options = (
incremental => { default => "yes", accept => [ "yes", "no", "strict" ] },
resume_missing => { default => "yes", accept => [ "yes", "no" ] },
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_weekly => { default => 0, accept => [ "all" ], accept_numeric => 1, context => [ "root", "volume", "subvolume" ] },
snapshot_preserve_monthly => { default => "all", accept => [ "all" ], accept_numeric => 1, context => [ "root", "volume", "subvolume" ] },
snapshot_preserve_yearly => { default => "0", accept => [ "all" ], accept_numeric => 1, context => [ "root", "volume", "subvolume" ] },
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, },
snapshot_preserve => { default => undef, accept_preserve_matrix => 1, context => [ "root", "volume", "subvolume" ], },
snapshot_preserve_all => { default => '12h', accept => [ "forever", "no" ], accept_regexp => qr/^[0-9]+[hdwmy]$/, context => [ "root", "volume", "subvolume" ], },
target_preserve => { default => undef, accept_preserve_matrix => 1 },
target_preserve_all => { default => undef, accept => [ "forever", "no" ], accept_regexp => qr/^[0-9]+[hdwmy]$/ },
btrfs_commit_delete => { default => undef, accept => [ "after", "each", "no" ] },
ssh_identity => { default => undef, accept_file => { absolute => 1 } },
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*/ },
# 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" ],
deprecated => { yes => { warn => "Please use \"snapshot_create always\"",
replace_key => "snapshot_create",
@ -153,7 +159,7 @@ my %table_formats = (
schedule => { table => [ qw( action host subvol 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 ) ],
@ -184,6 +190,7 @@ my $tlog_fh;
my $current_transaction;
my @transaction_log;
my %config_override;
my @today_and_now;
$SIG{__DIE__} = sub {
@ -2018,13 +2025,21 @@ sub config_preserve_hash($$)
{
my $config = shift || die;
my $prefix = shift || die;
return (
preserve_day_of_week => config_key($config, "preserve_day_of_week"),
preserve_daily => config_key($config, "${prefix}_preserve_daily"),
preserve_weekly => config_key($config, "${prefix}_preserve_weekly"),
preserve_monthly => config_key($config, "${prefix}_preserve_monthly"),
preserve_yearly => config_key($config, "${prefix}_preserve_yearly"),
);
my $ret = config_key($config, $prefix . "_preserve") // {};
my $preserve_all = config_key($config, $prefix . "_preserve_all");
if(defined($preserve_all)) {
$ret->{all} = $preserve_all; # used for raw schedule output
if($preserve_all eq 'forever') {
$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)
{
my $val;
next if($config_options{$key}->{deprecated});
next if($config_options{$key}->{shortcut});
if($opts{resolve}) {
$val = config_key($config, $key);
} else {
@ -2055,6 +2068,9 @@ sub config_dump_keys($;@)
next; # both undef, skip
}
}
if($config_options{$key}->{accept_preserve_matrix}) {
$val = format_preserve_matrix($val);
}
if(ref($val) eq "ARRAY") {
my $val2 = join(',', @$val);
$val = $val2;
@ -2097,18 +2113,24 @@ sub append_config_option($$$$;$)
}
if($opt->{accept_preserve_matrix}) {
# special case: preserve matrix of form: "[NNd] [NNw] [NNm] [NNy]"
my $s = $value;
TRACE "option \"$key=$value\" is preserve matrix, parsing...";
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"; }
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"; }
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"; }
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"; }
my %preserve;
my $s = ' ' . $value;
while($s =~ s/\s+(\*|[0-9]+)([hdwmyHDWMY])//) {
my $n = $1;
my $q = lc($2); # qw( h d w m y )
$n = 'all' if($n eq '*');
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 "") {
ERROR "Value \"$value\" failed input validation for option \"$key\"" . $config_file_statement;
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;
}
@ -2164,6 +2186,11 @@ sub append_config_option($$$$;$)
$value = $replace_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";
@ -2324,7 +2351,6 @@ sub init_config(@)
# set defaults
foreach (keys %config_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};
}
return \%config_root;
@ -2554,39 +2580,43 @@ sub schedule(@)
{
my %args = @_;
my $schedule = $args{schedule} || die;
my @today = @{$args{today}};
my $preserve_day_of_week = $args{preserve_day_of_week} || die;
my $preserve_daily = $args{preserve_daily} // die;
my $preserve_weekly = $args{preserve_weekly} // die;
my $preserve_monthly = $args{preserve_monthly} // die;
my $preserve_yearly = $args{preserve_yearly} // die;
my $preserve = $args{preserve} || 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,
w => $preserve_weekly,
m => $preserve_monthly,
y => $preserve_yearly,
dow => $preserve_day_of_week,
);
DEBUG "Schedule: " . format_preserve_matrix(%preserve_matrix, format => "debug_text");
my $preserve_day_of_week = $preserve->{dow} || die;
my $preserve_all_n = $preserve->{all_n};
my $preserve_all_q = $preserve->{all_q};
my $preserve_hourly = $preserve->{h};
my $preserve_daily = $preserve->{d};
my $preserve_weekly = $preserve->{w};
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
my @sorted_schedule = sort { cmp_date($a->{btrbk_date}, $b->{btrbk_date} ) } @$schedule;
# first, do our calendar calculations
# 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;
$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";
foreach my $href (@sorted_schedule)
{
my @date = @{$href->{btrbk_date}}[0..2]; # Date::Calc takes: @date = ( yy, mm, dd )
my $delta_days = Delta_Days(@date, @today);
my @date = (@{$href->{btrbk_date}}[0..4], 0); # btrbk_date: (y m d h m NN); @date: (y m d h m s)
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;
{
use integer; # do integer arithmetics
$href->{delta_hours} = ($dd * 24) + $dh;
$href->{delta_days} = $delta_days;
$href->{delta_weeks} = $delta_days_to_eow / 7;
$href->{delta_months} = ($today[0] - $date[0]) * 12 + ($today[1] - $date[1]);
@ -2604,33 +2634,65 @@ sub schedule(@)
$href->{preserve} ||= $preserve_latest;
}
# filter daily, weekly, monthly
my %last_in_delta_hours;
my %last_in_delta_days;
my %first_in_delta_weeks;
my %last_weekly_in_delta_months;
my %last_monthly_in_delta_years;
# filter "preserve all within N days/weeks/..."
foreach my $href (@sorted_schedule) {
if($preserve_daily && (($preserve_daily eq "all") || ($href->{delta_days} <= $preserve_daily))) {
$href->{preserve} ||= "preserved daily: $href->{delta_days} days ago";
if($preserve_all_n) {
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;
}
foreach (sort {$b <=> $a} keys %first_in_delta_weeks) {
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}";
}
$last_weekly_in_delta_months{$href->{delta_months}} = $href;
}
foreach (sort {$b <=> $a} keys %last_weekly_in_delta_months) {
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})";
}
$last_monthly_in_delta_years{$href->{delta_years}} = $href;
}
foreach (sort {$b <=> $a} keys %last_monthly_in_delta_years) {
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})";
}
}
@ -2638,8 +2700,8 @@ sub schedule(@)
# assemble results
my @delete;
my @preserve;
my %result_base = ( %preserve_matrix,
scheme => format_preserve_matrix(%preserve_matrix, format => "short"),
my %result_base = ( %$preserve,
scheme => format_preserve_matrix($preserve),
%$result_hints,
);
my $count_defined = 0;
@ -2671,40 +2733,49 @@ sub schedule(@)
}
sub format_preserve_matrix(@)
sub format_preserve_matrix($@)
{
my %args = @_;
my $dow = $args{dow} // $args{preserve_day_of_week};
my $d = $args{d} // $args{preserve_daily};
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";
my $preserve = shift || die;
my %opts = @_;
my $format = $opts{format} // "short";
if($format eq "debug_text") {
my $s = "preserving all within $d days";
unless($d eq 'all') {
$s .= "; first in week (starting on $dow), for $w weeks" if($w);
unless($w eq 'all') {
$s .= "; last weekly of month, for $m months" if($m);
unless($m eq 'all') {
$s .= "; last weekly of year, for $y years" if($y);
my @out;
my %trans = ( h => 'hours', d => 'days', w => 'weeks', m => 'months', y => 'years' );
if($preserve->{all_n} && $preserve->{all_n} eq 'forever') {
push @out, "preserving all forever";
}
else {
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$/\*/;
$w =~ s/^all$/\*/;
$m =~ s/^all$/\*/;
$y =~ s/^all$/\*/;
if($format eq "short") {
# short format
return sprintf("%2sd %2sw %2sm %2sy", $d, $w, $m, $y);
my $s = "";
if($preserve->{all_n} && ($preserve->{all_n} eq 'forever')) {
$s = '*d+';
}
# long format
return sprintf("%2sd %2sw %2sm %2sy ($dow)", $d, $w, $m, $y);
else {
$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);
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Quotekeys = 0;
my $start_time = time;
my @today_and_now = Today_and_Now();
my @today = @today_and_now[0..2];
@today_and_now = Today_and_Now();
my %config_override_opts;
my ($config_cmdline, $quiet, $verbose, $preserve_backups, $resume_only, $print_schedule);
unless(GetOptions(
'help|h' => sub { VERSION_MESSAGE(); HELP_MESSAGE(0); exit 0; },
@ -4037,7 +4107,7 @@ MAIN:
# find unique snapshot name
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]));
my @unconfirmed_target_name;
my @lookup = map { $_->{SUBVOL_PATH} } @{vinfo_subvol_list($sroot)};
@ -4134,9 +4204,8 @@ MAIN:
}
my ($preserve, undef) = schedule(
schedule => \@schedule,
today => \@today,
preserve => config_preserve_hash($droot, "target"),
preserve_latest => $preserve_latest,
config_preserve_hash($droot, "target"),
);
my @resume = grep defined, @$preserve; # remove entries with no value from list (target subvolumes)
$resume_total = scalar @resume;
@ -4231,8 +4300,7 @@ MAIN:
#
INFO "Cleaning backups of subvolume \"$svol->{PRINT}\": $droot->{PRINT}/$snapshot_basename.*";
unless(macro_delete($droot, "", $snapshot_basename, $droot,
{ today => \@today,
config_preserve_hash($droot, "target"),
{ preserve => config_preserve_hash($droot, "target"),
preserve_latest => $preserve_latest_backup,
results => $schedule_results,
result_hints => { topic => "backup", root_path => $droot->{PATH} },
@ -4258,8 +4326,7 @@ MAIN:
}
INFO "Cleaning snapshots: $sroot->{PRINT}/$snapdir_ts$snapshot_basename.*";
macro_delete($sroot, $snapdir_ts, $snapshot_basename, $svol,
{ today => \@today,
config_preserve_hash($svol, "snapshot"),
{ preserve => config_preserve_hash($svol, "snapshot"),
preserve_latest => $preserve_latest_snapshot,
results => $schedule_results,
result_hints => { topic => "snapshot", root_path => $sroot->{PATH} },

View File

@ -72,8 +72,8 @@ space-separated table format.
.PP
\fBtimestamp_format\fR short|long
.RS 4
Timestamp format used as postfix for new snapshot subvolume names.
Defaults to \[lq]short\[rq].
Timestamp format used as postfix for new snapshot subvolume
names. Defaults to \[lq]short\[rq].
.PP
.IP \fBshort\fR
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].
.RE
.PP
\fBtarget_preserve\fR [<daily>d] [<weekly>w] [<monthly>m] [<yearly>y]
\fBsnapshot_preserve\fR <retention_policy>
.RS 4
Shortcut to set \fItarget_preserve_daily\fR,
\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").
Set retention policy for snapshots (see RETENTION POLICY below).
.RE
.PP
\fBtarget_preserve_daily\fR all|<number>
\fBsnapshot_preserve_all\fR forever|no|<number>{h,d,w,m,y}
.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
.PP
\fBtarget_preserve_weekly\fR all|<number>
\fBtarget_preserve\fR <retention_policy>
.RS 4
Defines for how many weeks back weekly backups should be
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].
Set retention policy for backups (see RETENTION POLICY below).
.RE
.PP
\fBtarget_preserve_monthly\fR all|<number>
\fBtarget_preserve_all\fR forever|no|<number>{h,d,w,m,y}
.RS 4
Defines for how many months back monthly backups should be
preserved. Every last weekly backup in a month is considered a
monthly backup. Defaults to \[lq]all\[rq].
Preserve all backups 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]no\[rq].
.RE
.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
.RS 4
Defines on what day a backup/snapshot is considered as a weekly
@ -253,6 +225,65 @@ manipulated by hand (moved, deleted).
.PP
Lines that contain a hash character (#) in the first column are
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
.PP
\fBsend-receive\fR