mirror of https://github.com/digint/btrbk
btrbk: added command line option -r (resume only)
parent
09e214acf4
commit
84e41727b9
|
@ -4,6 +4,7 @@ btrbk-current
|
||||||
* Set PATH variable instead of using absolute "/sbin/btrfs" for
|
* Set PATH variable instead of using absolute "/sbin/btrfs" for
|
||||||
compatibility with all linux distros out there, which all install
|
compatibility with all linux distros out there, which all install
|
||||||
'btrfs' in different locations (closes: #20).
|
'btrfs' in different locations (closes: #20).
|
||||||
|
* Added command line option -r (resume only).
|
||||||
* Catch and display errors from "btrfs subvolume show".
|
* Catch and display errors from "btrfs subvolume show".
|
||||||
* Include systemd service and timer unit for daily backups.
|
* Include systemd service and timer unit for daily backups.
|
||||||
|
|
||||||
|
|
177
btrbk
177
btrbk
|
@ -47,7 +47,7 @@ use Date::Calc qw(Today Delta_Days Day_of_Week);
|
||||||
use Getopt::Std;
|
use Getopt::Std;
|
||||||
use Data::Dumper;
|
use Data::Dumper;
|
||||||
|
|
||||||
our $VERSION = "0.17.2-dev";
|
our $VERSION = "0.18.0-dev-resume_only";
|
||||||
our $AUTHOR = 'Axel Burri <axel@tty0.ch>';
|
our $AUTHOR = 'Axel Burri <axel@tty0.ch>';
|
||||||
our $PROJECT_HOME = '<http://www.digint.ch/btrbk/>';
|
our $PROJECT_HOME = '<http://www.digint.ch/btrbk/>';
|
||||||
|
|
||||||
|
@ -118,6 +118,7 @@ sub HELP_MESSAGE
|
||||||
print STDERR " --version display version information\n";
|
print STDERR " --version display version information\n";
|
||||||
print STDERR " -c FILE specify configuration file\n";
|
print STDERR " -c FILE specify configuration file\n";
|
||||||
print STDERR " -p preserve all backups (do not delete any old targets)\n";
|
print STDERR " -p preserve all backups (do not delete any old targets)\n";
|
||||||
|
print STDERR " -r resume only (no new snapshots, resume all missing backups)\n";
|
||||||
print STDERR " -v be verbose (set loglevel=info)\n";
|
print STDERR " -v be verbose (set loglevel=info)\n";
|
||||||
print STDERR " -q be quiet (do not print summary at end of \"run\" command)\n";
|
print STDERR " -q be quiet (do not print summary at end of \"run\" command)\n";
|
||||||
print STDERR " -l LEVEL set loglevel (warn, info, debug, trace)\n";
|
print STDERR " -l LEVEL set loglevel (warn, info, debug, trace)\n";
|
||||||
|
@ -1277,7 +1278,7 @@ MAIN:
|
||||||
my @today = Today();
|
my @today = Today();
|
||||||
|
|
||||||
my %opts;
|
my %opts;
|
||||||
unless(getopts('hc:vql:p', \%opts)) {
|
unless(getopts('hc:prvql:', \%opts)) {
|
||||||
VERSION_MESSAGE();
|
VERSION_MESSAGE();
|
||||||
HELP_MESSAGE(0);
|
HELP_MESSAGE(0);
|
||||||
exit 1;
|
exit 1;
|
||||||
|
@ -1297,6 +1298,7 @@ MAIN:
|
||||||
@config_src = ( $opts{c} ) if($opts{c});
|
@config_src = ( $opts{c} ) if($opts{c});
|
||||||
my $quiet = $opts{q};
|
my $quiet = $opts{q};
|
||||||
my $preserve_backups = $opts{p};
|
my $preserve_backups = $opts{p};
|
||||||
|
my $resume_only = $opts{r};
|
||||||
|
|
||||||
# check command line options
|
# check command line options
|
||||||
if($opts{h} || (not $command)) {
|
if($opts{h} || (not $command)) {
|
||||||
|
@ -1729,65 +1731,71 @@ MAIN:
|
||||||
|
|
||||||
if($action_run)
|
if($action_run)
|
||||||
{
|
{
|
||||||
#
|
if($resume_only) {
|
||||||
# create snapshots
|
INFO "Skipping snapshot creation (option \"-r\" present)";
|
||||||
#
|
}
|
||||||
my $timestamp = sprintf("%04d%02d%02d", @today);
|
else
|
||||||
foreach my $config_vol (@{$config->{VOLUME}})
|
|
||||||
{
|
{
|
||||||
next if($config_vol->{ABORTED});
|
#
|
||||||
my $sroot = $config_vol->{sroot} || die;
|
# create snapshots
|
||||||
foreach my $config_subvol (@{$config_vol->{SUBVOLUME}})
|
#
|
||||||
|
my $timestamp = sprintf("%04d%02d%02d", @today);
|
||||||
|
foreach my $config_vol (@{$config->{VOLUME}})
|
||||||
{
|
{
|
||||||
next if($config_subvol->{ABORTED});
|
next if($config_vol->{ABORTED});
|
||||||
my $svol = $config_subvol->{svol} || die;
|
my $sroot = $config_vol->{sroot} || die;
|
||||||
my $snapdir = config_key($config_subvol, "snapshot_dir") || "";
|
foreach my $config_subvol (@{$config_vol->{SUBVOLUME}})
|
||||||
my $snapshot_basename = config_key($config_subvol, "snapshot_name") // die;
|
{
|
||||||
|
next if($config_subvol->{ABORTED});
|
||||||
|
my $svol = $config_subvol->{svol} || die;
|
||||||
|
my $snapdir = config_key($config_subvol, "snapshot_dir") || "";
|
||||||
|
my $snapshot_basename = config_key($config_subvol, "snapshot_name") // die;
|
||||||
|
|
||||||
# check if we need to create a snapshot
|
# check if we need to create a snapshot
|
||||||
my $create_snapshot = config_key($config_subvol, "snapshot_create_always");
|
my $create_snapshot = config_key($config_subvol, "snapshot_create_always");
|
||||||
foreach my $config_target (@{$config_subvol->{TARGET}}) {
|
foreach my $config_target (@{$config_subvol->{TARGET}}) {
|
||||||
next if($config_target->{ABORTED});
|
next if($config_target->{ABORTED});
|
||||||
$create_snapshot = 1 if($config_target->{target_type} eq "send-receive");
|
$create_snapshot = 1 if($config_target->{target_type} eq "send-receive");
|
||||||
}
|
}
|
||||||
unless($create_snapshot) {
|
unless($create_snapshot) {
|
||||||
$config_subvol->{ABORTED} = "No targets defined for subvolume: $svol->{PRINT}";
|
$config_subvol->{ABORTED} = "No targets defined for subvolume: $svol->{PRINT}";
|
||||||
WARN "Skipping subvolume section: $config_subvol->{ABORTED}";
|
WARN "Skipping subvolume section: $config_subvol->{ABORTED}";
|
||||||
next;
|
|
||||||
}
|
|
||||||
|
|
||||||
# find unique snapshot name
|
|
||||||
my @unconfirmed_target_name;
|
|
||||||
my @lookup = keys %{vinfo_subvol_list($sroot)};
|
|
||||||
@lookup = grep s/^\Q$snapdir\E\/// , @lookup;
|
|
||||||
foreach my $config_target (@{$config_subvol->{TARGET}}) {
|
|
||||||
if($config_target->{ABORTED}) {
|
|
||||||
push(@unconfirmed_target_name, vinfo($config_target->{url}, $config_target));
|
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
my $droot = $config_target->{droot} || die;
|
|
||||||
push(@lookup, keys %{vinfo_subvol_list($droot)});
|
|
||||||
}
|
|
||||||
@lookup = grep /^\Q$snapshot_basename.$timestamp\E(_[0-9]+)?$/ ,@lookup;
|
|
||||||
TRACE "Present snapshot names for \"$svol->{PRINT}\": " . join(', ', @lookup);
|
|
||||||
@lookup = map { /_([0-9]+)$/ ? $1 : 0 } @lookup;
|
|
||||||
@lookup = sort { $b <=> $a } @lookup;
|
|
||||||
my $postfix_counter = $lookup[0] // -1;
|
|
||||||
$postfix_counter++;
|
|
||||||
my $snapshot_name = $snapshot_basename . '.' . $timestamp . ($postfix_counter ? "_$postfix_counter" : "");
|
|
||||||
|
|
||||||
if(@unconfirmed_target_name) {
|
# find unique snapshot name
|
||||||
INFO "Failed to check all targets, assuming non-present subvolume \"$snapshot_name\" in: " . join(", ", map { "\"$_->{PRINT}\"" } @unconfirmed_target_name);
|
my @unconfirmed_target_name;
|
||||||
}
|
my @lookup = keys %{vinfo_subvol_list($sroot)};
|
||||||
|
@lookup = grep s/^\Q$snapdir\E\/// , @lookup;
|
||||||
|
foreach my $config_target (@{$config_subvol->{TARGET}}) {
|
||||||
|
if($config_target->{ABORTED}) {
|
||||||
|
push(@unconfirmed_target_name, vinfo($config_target->{url}, $config_target));
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
my $droot = $config_target->{droot} || die;
|
||||||
|
push(@lookup, keys %{vinfo_subvol_list($droot)});
|
||||||
|
}
|
||||||
|
@lookup = grep /^\Q$snapshot_basename.$timestamp\E(_[0-9]+)?$/ ,@lookup;
|
||||||
|
TRACE "Present snapshot names for \"$svol->{PRINT}\": " . join(', ', @lookup);
|
||||||
|
@lookup = map { /_([0-9]+)$/ ? $1 : 0 } @lookup;
|
||||||
|
@lookup = sort { $b <=> $a } @lookup;
|
||||||
|
my $postfix_counter = $lookup[0] // -1;
|
||||||
|
$postfix_counter++;
|
||||||
|
my $snapshot_name = $snapshot_basename . '.' . $timestamp . ($postfix_counter ? "_$postfix_counter" : "");
|
||||||
|
|
||||||
# finally create the snapshot
|
if(@unconfirmed_target_name) {
|
||||||
INFO "Creating subvolume snapshot for: $svol->{PRINT}";
|
INFO "Failed to check all targets, assuming non-present subvolume \"$snapshot_name\" in: " . join(", ", map { "\"$_->{PRINT}\"" } @unconfirmed_target_name);
|
||||||
if(btrfs_subvolume_snapshot($svol, "$sroot->{PATH}/$snapdir/$snapshot_name")) {
|
}
|
||||||
$config_subvol->{SNAPSHOT} = vinfo_child($sroot, "$snapdir/$snapshot_name");
|
|
||||||
}
|
# finally create the snapshot
|
||||||
else {
|
INFO "Creating subvolume snapshot for: $svol->{PRINT}";
|
||||||
$config_subvol->{ABORTED} = "Failed to create snapshot: $svol->{PRINT} -> $sroot->{PRINT}/$snapdir/$snapshot_name";
|
if(btrfs_subvolume_snapshot($svol, "$sroot->{PATH}/$snapdir/$snapshot_name")) {
|
||||||
WARN "Skipping subvolume section: $config_subvol->{ABORTED}";
|
$config_subvol->{SNAPSHOT} = vinfo_child($sroot, "$snapdir/$snapshot_name");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$config_subvol->{ABORTED} = "Failed to create snapshot: $svol->{PRINT} -> $sroot->{PRINT}/$snapdir/$snapshot_name";
|
||||||
|
WARN "Skipping subvolume section: $config_subvol->{ABORTED}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1818,7 +1826,9 @@ MAIN:
|
||||||
WARN "Ignoring deprecated option \"receive_log\" for target: $droot->{PRINT}"
|
WARN "Ignoring deprecated option \"receive_log\" for target: $droot->{PRINT}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#
|
||||||
# resume missing backups (resume_missing)
|
# resume missing backups (resume_missing)
|
||||||
|
#
|
||||||
if(config_key($config_target, "resume_missing"))
|
if(config_key($config_target, "resume_missing"))
|
||||||
{
|
{
|
||||||
INFO "Checking for missing backups of subvolume \"$svol->{PRINT}\" in: $droot->{PRINT}/";
|
INFO "Checking for missing backups of subvolume \"$svol->{PRINT}\" in: $droot->{PRINT}/";
|
||||||
|
@ -1896,18 +1906,21 @@ MAIN:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# skip creation if resume_missing failed
|
unless($resume_only)
|
||||||
next if($config_target->{ABORTED});
|
{
|
||||||
die unless($config_subvol->{SNAPSHOT});
|
# skip creation if resume_missing failed
|
||||||
|
next if($config_target->{ABORTED});
|
||||||
|
die unless($config_subvol->{SNAPSHOT});
|
||||||
|
|
||||||
# finally receive the previously created snapshot
|
# finally receive the previously created snapshot
|
||||||
INFO "Creating subvolume backup (send-receive) for: $svol->{PRINT}";
|
INFO "Creating subvolume backup (send-receive) for: $svol->{PRINT}";
|
||||||
my ($latest_common_src, $latest_common_target) = get_latest_common($sroot, $svol, $droot);
|
my ($latest_common_src, $latest_common_target) = get_latest_common($sroot, $svol, $droot);
|
||||||
macro_send_receive($config_target,
|
macro_send_receive($config_target,
|
||||||
snapshot => $config_subvol->{SNAPSHOT},
|
snapshot => $config_subvol->{SNAPSHOT},
|
||||||
target => $droot,
|
target => $droot,
|
||||||
parent => $latest_common_src, # this is <undef> if no common found
|
parent => $latest_common_src, # this is <undef> if no common found
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ERROR "Unknown target type \"$target_type\", skipping: $svol->{PRINT}";
|
ERROR "Unknown target type \"$target_type\", skipping: $svol->{PRINT}";
|
||||||
|
@ -1921,8 +1934,8 @@ MAIN:
|
||||||
#
|
#
|
||||||
# remove backups following a preserve daily/weekly/monthly scheme
|
# remove backups following a preserve daily/weekly/monthly scheme
|
||||||
#
|
#
|
||||||
if($preserve_backups) {
|
if($preserve_backups || $resume_only) {
|
||||||
INFO "Preserving all backups (option \"-p\" present)";
|
INFO "Preserving all backups (option \"-p\" or \"-r\" present)";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -2033,19 +2046,19 @@ MAIN:
|
||||||
my $sroot = $config_vol->{sroot} || vinfo($config_vol->{url}, $config_vol);
|
my $sroot = $config_vol->{sroot} || vinfo($config_vol->{url}, $config_vol);
|
||||||
foreach my $config_subvol (@{$config_vol->{SUBVOLUME}})
|
foreach my $config_subvol (@{$config_vol->{SUBVOLUME}})
|
||||||
{
|
{
|
||||||
|
my @subvol_out;
|
||||||
my $svol = $config_subvol->{svol} || vinfo_child($sroot, $config_subvol->{rel_path});
|
my $svol = $config_subvol->{svol} || vinfo_child($sroot, $config_subvol->{rel_path});
|
||||||
push @out, "$svol->{PRINT}";
|
|
||||||
if($config_vol->{ABORTED}) {
|
if($config_vol->{ABORTED}) {
|
||||||
push @out, "!!! $sroot->{PRINT}: ABORTED: $config_vol->{ABORTED}";
|
push @subvol_out, "!!! $sroot->{PRINT}: ABORTED: $config_vol->{ABORTED}";
|
||||||
$err_count++ unless($config_vol->{ABORTED_NOERR});
|
$err_count++ unless($config_vol->{ABORTED_NOERR});
|
||||||
}
|
}
|
||||||
if($config_subvol->{ABORTED}) {
|
if($config_subvol->{ABORTED}) {
|
||||||
push @out, "!!! Subvolume \"$svol->{PRINT}\" aborted: $config_subvol->{ABORTED}";
|
push @subvol_out, "!!! Subvolume \"$svol->{PRINT}\" aborted: $config_subvol->{ABORTED}";
|
||||||
$err_count++ unless($config_subvol->{ABORTED_NOERR});
|
$err_count++ unless($config_subvol->{ABORTED_NOERR});
|
||||||
}
|
}
|
||||||
push @out, "+++ $config_subvol->{SNAPSHOT}->{PRINT}" if($config_subvol->{SNAPSHOT});
|
push @subvol_out, "+++ $config_subvol->{SNAPSHOT}->{PRINT}" if($config_subvol->{SNAPSHOT});
|
||||||
if($config_subvol->{SUBVOL_DELETED}) {
|
if($config_subvol->{SUBVOL_DELETED}) {
|
||||||
push @out, "--- $_->{PRINT}" foreach(sort { $b->{PATH} cmp $a->{PATH} } @{$config_subvol->{SUBVOL_DELETED}});
|
push @subvol_out, "--- $_->{PRINT}" foreach(sort { $b->{PATH} cmp $a->{PATH} } @{$config_subvol->{SUBVOL_DELETED}});
|
||||||
}
|
}
|
||||||
foreach my $config_target (@{$config_subvol->{TARGET}})
|
foreach my $config_target (@{$config_subvol->{TARGET}})
|
||||||
{
|
{
|
||||||
|
@ -2055,21 +2068,26 @@ MAIN:
|
||||||
$create_mode = ">>>" if($_->{parent});
|
$create_mode = ">>>" if($_->{parent});
|
||||||
# substr($create_mode, 0, 1, '%') if($_->{resume});
|
# substr($create_mode, 0, 1, '%') if($_->{resume});
|
||||||
$create_mode = "!!!" if($_->{ERROR});
|
$create_mode = "!!!" if($_->{ERROR});
|
||||||
push @out, "$create_mode $_->{received_subvolume}->{PRINT}";
|
push @subvol_out, "$create_mode $_->{received_subvolume}->{PRINT}";
|
||||||
}
|
}
|
||||||
|
|
||||||
if($config_target->{SUBVOL_DELETED}) {
|
if($config_target->{SUBVOL_DELETED}) {
|
||||||
push @out, "--- $_->{PRINT}" foreach(sort { $b->{PATH} cmp $a->{PATH} } @{$config_target->{SUBVOL_DELETED}});
|
push @subvol_out, "--- $_->{PRINT}" foreach(sort { $b->{PATH} cmp $a->{PATH} } @{$config_target->{SUBVOL_DELETED}});
|
||||||
}
|
}
|
||||||
|
|
||||||
if($config_target->{ABORTED}) {
|
if($config_target->{ABORTED}) {
|
||||||
push @out, "!!! Target \"$droot->{PRINT}\" aborted: $config_target->{ABORTED}";
|
push @subvol_out, "!!! Target \"$droot->{PRINT}\" aborted: $config_target->{ABORTED}";
|
||||||
$err_count++ unless($config_target->{ABORTED_NOERR});
|
$err_count++ unless($config_target->{ABORTED_NOERR});
|
||||||
}
|
}
|
||||||
|
|
||||||
push(@unrecoverable, $config_target->{UNRECOVERABLE}) if($config_target->{UNRECOVERABLE});
|
push(@unrecoverable, $config_target->{UNRECOVERABLE}) if($config_target->{UNRECOVERABLE});
|
||||||
}
|
}
|
||||||
push @out, "";
|
if(@subvol_out) {
|
||||||
|
push @out, "$svol->{PRINT}", @subvol_out, "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
push @out, "$svol->{PRINT}", "<no_action>", "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2087,8 +2105,11 @@ MAIN:
|
||||||
|
|
||||||
print join("\n", @out);
|
print join("\n", @out);
|
||||||
|
|
||||||
if($preserve_backups) {
|
if($resume_only) {
|
||||||
print "\nNOTE: Preserved all backups (option -p present)\n";
|
print "\nNOTE: No snapshots created (option -r present)\n";
|
||||||
|
}
|
||||||
|
if($preserve_backups || $resume_only) {
|
||||||
|
print "\nNOTE: Preserved all backups (option -p or -r present)\n";
|
||||||
}
|
}
|
||||||
if($err_count) {
|
if($err_count) {
|
||||||
print "\nNOTE: Some errors occurred, which may result in missing backups!\n";
|
print "\nNOTE: Some errors occurred, which may result in missing backups!\n";
|
||||||
|
|
Loading…
Reference in New Issue