diff --git a/btrbk b/btrbk index 4e285c1..55e19bc 100755 --- a/btrbk +++ b/btrbk @@ -80,6 +80,7 @@ my %config_options = ( snapshot_create => { default => "always", accept => [ "no", "always", "ondemand", "onchange" ] }, incremental => { default => "yes", accept => [ "yes", "no", "strict" ] }, preserve_day_of_week => { default => "sunday", accept => [ (keys %day_of_week_map) ] }, + preserve_hour_of_day => { default => 0, accept_numeric => 1 }, snapshot_preserve => { default => undef, accept => [ "no" ], accept_preserve_matrix => 1, context => [ "root", "volume", "subvolume" ], }, snapshot_preserve_min => { default => "all", accept => [ "all", "latest" ], accept_regexp => qr/^[1-9][0-9]*[hdwmy]$/, context => [ "root", "volume", "subvolume" ], }, target_preserve => { default => undef, accept => [ "no" ], accept_preserve_matrix => 1 }, @@ -189,7 +190,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 min h d w m y) ], + raw => [ qw( topic action url host path hod dow min h d w m y) ], }, usage => { table => [ qw( host path size used free ) ], @@ -3012,7 +3013,7 @@ sub config_preserve_hash($$;@) my $prefix = shift || die; my %opts = @_; if($opts{wipe}) { - return { dow => 'sunday', min => 'latest', min_q => 'latest' }; + return { hod => 0, dow => 'sunday', min => 'latest', min_q => 'latest' }; } my $ret = config_key($config, $prefix . "_preserve") // {}; my $preserve_min = config_key($config, $prefix . "_preserve_min"); @@ -3027,6 +3028,7 @@ sub config_preserve_hash($$;@) } else { die; } } + $ret->{hod} = config_key($config, "preserve_hour_of_day"); $ret->{dow} = config_key($config, "preserve_day_of_week"); return $ret; } @@ -3696,6 +3698,7 @@ sub schedule(@) my $result_delete_action_text = $args{result_delete_action_text} // 'delete'; my $preserve_day_of_week = $preserve->{dow} || die; + my $preserve_hour_of_day = $preserve->{hod}; my $preserve_min_n = $preserve->{min_n}; my $preserve_min_q = $preserve->{min_q}; my $preserve_hourly = $preserve->{h}; @@ -3719,20 +3722,22 @@ sub schedule(@) # first, do our calendar calculations # - weeks start on $preserve_day_of_week + # - days start on $preserve_hour_of_day # - leap hours are NOT taken into account for $delta_hours my $now_h = timegm_nocheck( 0, 0, $tm_now[2], $tm_now[3], $tm_now[4], $tm_now[5] ); # use timelocal() here (and below) if you want to honor leap hours - my $now_d = timegm_nocheck( 0, 0, 0, $tm_now[3], $tm_now[4], $tm_now[5] ); foreach my $href (@sorted_schedule) { my $time = $href->{btrbk_date}->[0]; my @tm = localtime($time); + my $delta_hours_from_hod = $tm[2] - $preserve_hour_of_day; + $delta_hours_from_hod += 24 if($delta_hours_from_hod < 0); my $delta_days_from_eow = $tm[6] - $day_of_week_map{$preserve_day_of_week}; $delta_days_from_eow += 7 if($delta_days_from_eow < 0); # check timegm: ignores leap hours - my $delta_days = int(($now_d - timegm_nocheck( 0, 0, 0, $tm[3], $tm[4], $tm[5] ) ) / (60 * 60 * 24)); my $delta_hours = int(($now_h - timegm_nocheck( 0, 0, $tm[2], $tm[3], $tm[4], $tm[5] ) ) / (60 * 60)); + my $delta_days = int(($delta_hours + $delta_hours_from_hod) / 24); # days from beginning of day my $delta_weeks = int(($delta_days + $delta_days_from_eow) / 7); # weeks from beginning of week my $delta_years = ($tm_now[5] - $tm[5]); my $delta_months = $delta_years * 12 + ($tm_now[4] - $tm[4]); @@ -3748,6 +3753,7 @@ sub schedule(@) my $year_month = "${year}-" . ($tm[4] < 9 ? '0' : "") . ($tm[4] + 1); $href->{year_month} = $year_month; $href->{year} = $year; + $href->{err_hours} = $delta_hours_from_hod . "h after $preserve_hour_of_day o'clock"; $href->{err_days} = ($delta_days_from_eow ? "+$delta_days_from_eow days after " : "on ") . "$preserve_day_of_week"; if($preserve_date_in_future && ($href->{delta_hours} < 0)) { @@ -3796,7 +3802,7 @@ sub schedule(@) foreach (sort {$b <=> $a} keys %first_in_delta_days) { my $href = $first_in_delta_days{$_} || die; if($preserve_daily && (($preserve_daily eq 'all') || ($href->{delta_days} <= $preserve_daily))) { - $href->{preserve} = "preserve daily: first of day, $href->{delta_days} days ago"; + $href->{preserve} = "preserve daily: first of day, $href->{delta_days} days ago, $href->{err_hours}"; } $first_in_delta_weeks{$href->{delta_weeks}} //= $href; } @@ -3804,21 +3810,21 @@ sub schedule(@) 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))) { - $href->{preserve} = "preserve weekly: $href->{delta_weeks} weeks ago, $href->{err_days}"; + $href->{preserve} = "preserve weekly: $href->{delta_weeks} weeks ago, $href->{err_days}, $href->{err_hours}"; } $first_weekly_in_delta_months{$href->{delta_months}} //= $href; } foreach (sort {$b <=> $a} keys %first_weekly_in_delta_months) { my $href = $first_weekly_in_delta_months{$_} || die; if($preserve_monthly && (($preserve_monthly eq 'all') || ($href->{delta_months} <= $preserve_monthly))) { - $href->{preserve} = "preserve monthly: first weekly of month $href->{year_month} ($href->{delta_months} months ago, $href->{err_days})"; + $href->{preserve} = "preserve monthly: first weekly of month $href->{year_month} ($href->{delta_months} months ago, $href->{err_days}, $href->{err_hours})"; } $first_monthly_in_delta_years{$href->{delta_years}} //= $href; } foreach (sort {$b <=> $a} keys %first_monthly_in_delta_years) { my $href = $first_monthly_in_delta_years{$_} || die; if($preserve_yearly && (($preserve_yearly eq 'all') || ($href->{delta_years} <= $preserve_yearly))) { - $href->{preserve} = "preserve yearly: first weekly of year $href->{year} ($href->{delta_years} years ago, $href->{err_days})"; + $href->{preserve} = "preserve yearly: first weekly of year $href->{year} ($href->{delta_years} years ago, $href->{err_days}, $href->{err_hours})"; } } @@ -3871,7 +3877,7 @@ sub format_preserve_matrix($@) else { push @out, "latest" if($preserve->{min_q} && ($preserve->{min_q} eq 'latest')); push @out, "all within $preserve->{min_n} $trans{$preserve->{min_q}}" if($preserve->{min_n} && $preserve->{min_q}); - push @out, "first of day for $preserve->{d} days" if($preserve->{d}); + push @out, "first of day (starting at $preserve->{hod} o'clock) 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')) { @@ -3898,7 +3904,10 @@ sub format_preserve_matrix($@) $val = '*' if($val eq 'all'); $s .= ($s ? ' ' : '') . $val . $_; } - $s .= " ($preserve->{dow})" if($preserve->{dow} && ($preserve->{w} || $preserve->{m} || $preserve->{y})); + my $t = ""; + $t .= "$preserve->{dow}" if($preserve->{dow} && ($preserve->{w} || $preserve->{m} || $preserve->{y})); + $t .= ((($t eq "") ? "" : " ") . "at $preserve->{hod}") if($preserve->{hod} && ($preserve->{d} || $preserve->{w} || $preserve->{m} || $preserve->{y})); + $s .= ($t eq "") ? "" : " ($t)"; } return $s; } diff --git a/btrbk.conf.example b/btrbk.conf.example index 85208f8..a5194a9 100644 --- a/btrbk.conf.example +++ b/btrbk.conf.example @@ -33,6 +33,10 @@ snapshot_dir _btrbk_snap # creation of non-incremental backups if no parent is found). #incremental yes +# Specify after what time (in full hours after midnight) backups/ +# snapshots are considered as a daily backup/snapshot +#preserve_hour_of_day 0 + # Specify on which day of week weekly/monthly backups are to be # preserved. #preserve_day_of_week sunday diff --git a/doc/btrbk.conf.5.asciidoc b/doc/btrbk.conf.5.asciidoc index e8b2ffc..a4a3391 100644 --- a/doc/btrbk.conf.5.asciidoc +++ b/doc/btrbk.conf.5.asciidoc @@ -151,6 +151,11 @@ Note that using ``long-iso'' has implications on the scheduling, see === Retention Policy Options +*preserve_hour_of_day* :: + Defines after what time (in full hours since midnight) a + backup/snapshot is considered as a daily backup. + Defaults to ``0''. + *preserve_day_of_week* monday|tuesday|...|sunday:: Defines on what day a backup/snapshot is considered as a weekly backup. Defaults to ``sunday''.