Merge branch 'resume_only'

pull/30/head
Axel Burri 2015-05-20 21:52:30 +02:00
commit 725191583e
5 changed files with 234 additions and 114 deletions

View File

@ -4,6 +4,10 @@ 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 configuration option "snapshot_create", replacing option
"snapshot_create_always". This allows setups with multiple btrbk
instances on several hosts (closes: #18).
* 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.

View File

@ -156,11 +156,10 @@ Retention policy:
- `/mnt/btr_backup/mylaptop/rootfs.YYYYMMDD` - `/mnt/btr_backup/mylaptop/rootfs.YYYYMMDD`
- `/mnt/btr_backup/mylaptop/home.YYYYMMDD` - `/mnt/btr_backup/mylaptop/home.YYYYMMDD`
If you want the snapshots to be created even if the backup disk is not If you want the snapshots to be created only if the backup disk is
attached (when you're on the road), simply add the following line to attached, simply add the following line to the config:
the config:
snapshot_create_always yes snapshot_create ondemand
Example: host-initiated backup on fileserver Example: host-initiated backup on fileserver
@ -215,19 +214,51 @@ This will pull backups from alpha/beta.mydomain.com and locally create:
Example: local time-machine (daily snapshots) Example: local time-machine (daily snapshots)
--------------------------------------------- ---------------------------------------------
If all you want is a local time-machine of your home directory: If all you want is creating snapshots of your home directory on a
regular basis:
/etc/btrbk/btrbk-timemachine.conf: /etc/btrbk/btrbk.conf:
volume /mnt/btr_pool volume /mnt/btr_pool
snapshot_dir btrbk_snapshots
subvolume home subvolume home
snapshot_dir btrbk_snapshots
snapshot_create_always yes
/etc/cron.daily/btrbk: /etc/cron.daily/btrbk:
#!/bin/bash #!/bin/bash
/usr/sbin/btrbk -c /etc/btrbk/btrbk-timemachine.conf run /usr/sbin/btrbk run
Note that you can run btrbk more than once a day, e.g. by creating the
above script in `/etc/cron.hourly/btrbk`, or by calling `sudo btrbk
run` from the command line.
Example: multiple btrbk instances
---------------------------------
Let's say we have a host (at 192.168.0.42) running btrbk with the
setup of the time-machine example above, and we need a backup server
to only fetch the snapshots.
/etc/btrbk/btrbk.conf (on backup server):
volume ssh://192.168.0.42/mnt/btr_pool
subvolume home
snapshot_dir btrbk_snapshots
snapshot_preserve_daily all
snapshot_create no
resume_missing yes
target_preserve_daily 0
target_preserve_weekly 10
target_preserve_monthly all
target send-receive /mnt/btr_backup/my-laptop.com
If the server runs btrbk with this config, the latest snapshot (which
is *always* transferred) as well as 10 weeklies and all monthlies are
received from 192.168.0.42. The source filesystem is never altered
because of `snapshot_preserve_daily all`.
Setting up SSH Setting up SSH

255
btrbk
View File

@ -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";
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/>';
@ -62,9 +62,8 @@ my %config_options = (
# NOTE: keys "volume", "subvolume" and "target" are hardcoded # NOTE: keys "volume", "subvolume" and "target" are hardcoded
snapshot_dir => { default => undef, accept_file => { relative => 1 } }, snapshot_dir => { default => undef, accept_file => { relative => 1 } },
snapshot_name => { default => undef, accept_file => { name_only => 1 }, context => [ "subvolume" ] }, snapshot_name => { default => undef, accept_file => { name_only => 1 }, context => [ "subvolume" ] },
receive_log => { default => undef, accept => [ "sidecar", "no" ], accept_file => { absolute => 1 }, deprecated => "removed" }, snapshot_create => { default => "always", accept => [ "no", "always", "ondemand" ] },
incremental => { default => "yes", accept => [ "yes", "no", "strict" ] }, incremental => { default => "yes", accept => [ "yes", "no", "strict" ] },
snapshot_create_always => { default => undef, accept => [ "yes", "no" ] },
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 }, snapshot_preserve_daily => { default => "all", accept => [ "all" ], accept_numeric => 1 },
@ -77,6 +76,22 @@ my %config_options = (
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_-]*$/ },
btrfs_progs_compat => { default => undef, accept => [ "yes", "no" ] }, btrfs_progs_compat => { default => undef, accept => [ "yes", "no" ] },
# deprecated options
snapshot_create_always => { default => undef, accept => [ "yes", "no" ],
deprecated => { yes => { warn => "Please use \"snapshot_create always\"",
replace_key => "snapshot_create",
replace_value => "always",
},
no => { warn => "Please use \"snapshot_create no\" or \"snapshot_create ondemand\"",
replace_key => "snapshot_create",
replace_value => "ondemand",
}
},
},
receive_log => { default => undef, accept => [ "sidecar", "no" ], accept_file => { absolute => 1 },
deprecated => { DEFAULT => { warn => "ignoring" } },
}
); );
my @config_target_types = qw(send-receive); my @config_target_types = qw(send-receive);
@ -118,6 +133,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";
@ -322,8 +338,8 @@ sub config_key($$)
my $key = shift || die; my $key = shift || die;
TRACE "config_key: context=$node->{CONTEXT}, key=$key"; TRACE "config_key: context=$node->{CONTEXT}, key=$key";
while(not exists($node->{$key})) { while(not exists($node->{$key})) {
return undef unless($node->{PARENT}); # note: all config keys exist in root context (at least with default values)
$node = $node->{PARENT}; $node = $node->{PARENT} || die;
} }
TRACE "config_key: found value=" . ($node->{$key} // "<undef>"); TRACE "config_key: found value=" . ($node->{$key} // "<undef>");
return $node->{$key}; return $node->{$key};
@ -394,6 +410,7 @@ sub parse_config(@)
my $cur = $root; my $cur = $root;
# set defaults # set defaults
foreach (keys %config_options) { foreach (keys %config_options) {
next if $config_options{$_}->{deprecated}; # don't pollute hash with deprecated options
$root->{$_} = $config_options{$_}->{default}; $root->{$_} = $config_options{$_}->{default};
} }
@ -529,13 +546,21 @@ sub parse_config(@)
return undef; return undef;
} }
if($config_options{$key}->{deprecated}) {
WARN "Found deprecated option \"$key $value\" in \"$file\" line $.: " .
($config_options{$key}->{deprecated}->{$value}->{warn} // $config_options{$key}->{deprecated}->{DEFAULT}->{warn});
my $replace_key = $config_options{$key}->{deprecated}->{$value}->{replace_key};
my $replace_value = $config_options{$key}->{deprecated}->{$value}->{replace_value};
if(defined($replace_key)) {
$key = $replace_key;
$value = $replace_value;
WARN "Using \"$key $value\"";
}
}
TRACE "config: adding option \"$key=$value\" to $cur->{CONTEXT} context"; TRACE "config: adding option \"$key=$value\" to $cur->{CONTEXT} context";
$value = undef if($value eq "no"); # we don't want to check for "no" all the time $value = undef if($value eq "no"); # we don't want to check for "no" all the time
$cur->{$key} = $value; $cur->{$key} = $value;
if($config_options{$key}->{deprecated}) {
WARN "Found deprecated configuration option \"$key\" in \"$file\" line $.";
}
} }
else else
{ {
@ -1187,6 +1212,7 @@ sub schedule(@)
my $preserve_daily = $args{preserve_daily} // die; my $preserve_daily = $args{preserve_daily} // die;
my $preserve_weekly = $args{preserve_weekly} // die; my $preserve_weekly = $args{preserve_weekly} // die;
my $preserve_monthly = $args{preserve_monthly} // die; my $preserve_monthly = $args{preserve_monthly} // die;
my $preserve_latest = $args{preserve_latest} || 0;
my $log_verbose = $args{log_verbose}; my $log_verbose = $args{log_verbose};
if($log_verbose) { if($log_verbose) {
@ -1222,6 +1248,11 @@ sub schedule(@)
} }
} }
if($preserve_latest && (scalar @sorted_schedule)) {
my $href = $sorted_schedule[-1];
$href->{preserve} ||= "preserve forced: latest in list";
}
# filter daily, weekly, monthly # filter daily, weekly, monthly
my %first_in_delta_weeks; my %first_in_delta_weeks;
my %last_weekly_in_delta_months; my %last_weekly_in_delta_months;
@ -1277,7 +1308,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 +1328,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 +1761,89 @@ 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 $snapshot_create = config_key($config_subvol, "snapshot_create") // "no";
foreach my $config_target (@{$config_subvol->{TARGET}}) { if($snapshot_create eq "no") {
next if($config_target->{ABORTED}); DEBUG "Snapshot creation disabled: snapshot_create=no";
$create_snapshot = 1 if($config_target->{target_type} eq "send-receive");
}
unless($create_snapshot) {
$config_subvol->{ABORTED} = "No targets defined for subvolume: $svol->{PRINT}";
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; if($snapshot_create eq "always") {
push(@lookup, keys %{vinfo_subvol_list($droot)}); DEBUG "Snapshot creation enabled: snapshot_create=always";
} }
@lookup = grep /^\Q$snapshot_basename.$timestamp\E(_[0-9]+)?$/ ,@lookup; elsif($snapshot_create eq "ondemand") {
TRACE "Present snapshot names for \"$svol->{PRINT}\": " . join(', ', @lookup); my $snapshot_needed = 0;
@lookup = map { /_([0-9]+)$/ ? $1 : 0 } @lookup; foreach my $config_target (@{$config_subvol->{TARGET}}) {
@lookup = sort { $b <=> $a } @lookup; next if($config_target->{ABORTED});
my $postfix_counter = $lookup[0] // -1; if($config_target->{target_type} eq "send-receive") {
$postfix_counter++; $snapshot_needed = 1;
my $snapshot_name = $snapshot_basename . '.' . $timestamp . ($postfix_counter ? "_$postfix_counter" : ""); last;
}
}
if($snapshot_needed) {
DEBUG "Snapshot creation enabled: snapshot_create=ondemand, and at least one send-receive target is present";
}
else {
DEBUG "Snapshot creation disabled: snapshot_create=ondemand, but no send-receive target is present";
next;
}
}
else {
die "illegal value for snapshot_create configuration option: $snapshot_create";
}
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}";
}
} }
} }
} }
@ -1805,6 +1861,7 @@ MAIN:
my $svol = $config_subvol->{svol} || die; my $svol = $config_subvol->{svol} || die;
my $snapdir = config_key($config_subvol, "snapshot_dir") || ""; my $snapdir = config_key($config_subvol, "snapshot_dir") || "";
my $snapshot_basename = config_key($config_subvol, "snapshot_name") // die; my $snapshot_basename = config_key($config_subvol, "snapshot_name") // die;
my $preserve_latest = $config_subvol->{SNAPSHOT} ? 0 : 1;
foreach my $config_target (@{$config_subvol->{TARGET}}) foreach my $config_target (@{$config_subvol->{TARGET}})
{ {
@ -1814,11 +1871,9 @@ MAIN:
if($target_type eq "send-receive") if($target_type eq "send-receive")
{ {
if(config_key($config_target, "receive_log")) { #
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}/";
@ -1863,6 +1918,7 @@ MAIN:
preserve_daily => config_key($config_target, "target_preserve_daily"), preserve_daily => config_key($config_target, "target_preserve_daily"),
preserve_weekly => config_key($config_target, "target_preserve_weekly"), preserve_weekly => config_key($config_target, "target_preserve_weekly"),
preserve_monthly => config_key($config_target, "target_preserve_monthly"), preserve_monthly => config_key($config_target, "target_preserve_monthly"),
preserve_latest => $preserve_latest,
); );
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;
@ -1894,20 +1950,23 @@ MAIN:
} else { } else {
INFO "No missing backups found"; INFO "No missing backups found";
} }
} # /resume_missing
unless($resume_only)
{
# skip creation if resume_missing failed
next if($config_target->{ABORTED});
next unless($config_subvol->{SNAPSHOT});
# finally receive the previously created snapshot
INFO "Creating subvolume backup (send-receive) for: $svol->{PRINT}";
my ($latest_common_src, $latest_common_target) = get_latest_common($sroot, $svol, $droot);
macro_send_receive($config_target,
snapshot => $config_subvol->{SNAPSHOT},
target => $droot,
parent => $latest_common_src, # this is <undef> if no common found
);
} }
# skip creation if resume_missing failed
next if($config_target->{ABORTED});
die unless($config_subvol->{SNAPSHOT});
# finally receive the previously created snapshot
INFO "Creating subvolume backup (send-receive) for: $svol->{PRINT}";
my ($latest_common_src, $latest_common_target) = get_latest_common($sroot, $svol, $droot);
macro_send_receive($config_target,
snapshot => $config_subvol->{SNAPSHOT},
target => $droot,
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 +1980,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
{ {
@ -1936,7 +1995,9 @@ MAIN:
my $svol = $config_subvol->{svol} || die; my $svol = $config_subvol->{svol} || die;
my $snapdir = config_key($config_subvol, "snapshot_dir") || ""; my $snapdir = config_key($config_subvol, "snapshot_dir") || "";
my $snapshot_basename = config_key($config_subvol, "snapshot_name") // die; my $snapshot_basename = config_key($config_subvol, "snapshot_name") // die;
my $preserve_latest = $config_subvol->{SNAPSHOT} ? 0 : 1;
my $target_aborted = 0; my $target_aborted = 0;
foreach my $config_target (@{$config_subvol->{TARGET}}) foreach my $config_target (@{$config_subvol->{TARGET}})
{ {
if($config_target->{ABORTED}) { if($config_target->{ABORTED}) {
@ -1968,6 +2029,7 @@ MAIN:
preserve_daily => config_key($config_target, "target_preserve_daily"), preserve_daily => config_key($config_target, "target_preserve_daily"),
preserve_weekly => config_key($config_target, "target_preserve_weekly"), preserve_weekly => config_key($config_target, "target_preserve_weekly"),
preserve_monthly => config_key($config_target, "target_preserve_monthly"), preserve_monthly => config_key($config_target, "target_preserve_monthly"),
preserve_latest => $preserve_latest,
log_verbose => 1, log_verbose => 1,
); );
my $ret = btrfs_subvolume_delete($delete, commit => config_key($config_target, "btrfs_commit_delete")); my $ret = btrfs_subvolume_delete($delete, commit => config_key($config_target, "btrfs_commit_delete"));
@ -2003,6 +2065,7 @@ MAIN:
preserve_daily => config_key($config_subvol, "snapshot_preserve_daily"), preserve_daily => config_key($config_subvol, "snapshot_preserve_daily"),
preserve_weekly => config_key($config_subvol, "snapshot_preserve_weekly"), preserve_weekly => config_key($config_subvol, "snapshot_preserve_weekly"),
preserve_monthly => config_key($config_subvol, "snapshot_preserve_monthly"), preserve_monthly => config_key($config_subvol, "snapshot_preserve_monthly"),
preserve_latest => $preserve_latest,
log_verbose => 1, log_verbose => 1,
); );
my $ret = btrfs_subvolume_delete($delete, commit => config_key($config_subvol, "btrfs_commit_delete")); my $ret = btrfs_subvolume_delete($delete, commit => config_key($config_subvol, "btrfs_commit_delete"));
@ -2033,19 +2096,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 { $a->{PATH} cmp $b->{PATH} } @{$config_subvol->{SUBVOL_DELETED}});
} }
foreach my $config_target (@{$config_subvol->{TARGET}}) foreach my $config_target (@{$config_subvol->{TARGET}})
{ {
@ -2055,21 +2118,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 { $a->{PATH} cmp $b->{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 +2155,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";

View File

@ -22,14 +22,15 @@
snapshot_dir _btrbk_snap snapshot_dir _btrbk_snap
# Perform incremental backups (set to "strict" if you want to prevent # Perform incremental backups (set to "strict" if you want to prevent
# creation of initial backups if no parent is found). # creation of non-incremental backups if no parent is found).
incremental yes incremental yes
# Always create snapshots, even if the target volume is unreachable. # Always create snapshots (set to "ondemand" to only create snapshots
snapshot_create_always yes # if the target volume is reachable).
snapshot_create always
# Resume missing backups if the target volume is reachable again. # Resume missing backups if the target volume is reachable again (set
# Useful in conjunction with "snapshot_create_always". # to "no" if you don't want to resume missing backups).
resume_missing yes resume_missing yes
# ssh key for ssh volumes/targets # ssh key for ssh volumes/targets
@ -115,3 +116,15 @@ volume ssh://my-remote-host.com/mnt/btr_pool
snapshot_dir snapshots/btrbk snapshot_dir snapshots/btrbk
snapshot_name data_main snapshot_name data_main
target send-receive /mnt/btr_backup/_btrbk/my-remote-host.com target send-receive /mnt/btr_backup/_btrbk/my-remote-host.com
# Resume backups from remote host which runs its own btrbk instance
# creating snapshots for "home" in "/mnt/btr_pool/btrbk_snapshots".
volume ssh://my-remote-host.com/mnt/btr_pool
subvolume home
snapshot_dir btrbk_snapshots
snapshot_preserve_daily all
snapshot_create no
resume_missing yes
target send-receive /mnt/btr_backup/_btrbk/my-remote-host.com

View File

@ -56,14 +56,15 @@ Base name of the created snapshot (and backup). Defaults to
\fI<subvolume-name>\fR. This option is only valid in the \fItarget\fR \fI<subvolume-name>\fR. This option is only valid in the \fItarget\fR
section. section.
.TP .TP
\fBsnapshot_create_always\fR yes|no \fBsnapshot_create\fR always|ondemand|no
If set, the snapshots are always created, even if the backup subvolume If set to \[lq]ondemand\[rq], the snapshots are only created if the
cannot be created (e.g. if the target subvolume cannot be target subvolume is reachable (useful if you are tight on disk space
reached). Use in conjunction with the \fIresume_missing\fR option to and you only need btrbk for backups to an external disk which is not
make sure that the backups are created as soon as the target subvolume always connected). If set to \[lq]always\[rq], the snapshots are
is reachable again. Useful for laptop filesystems in order to make always created, regardless of the target reachability. If set to
sure the snapshots are created even if you are on the road. Defaults \[lq]no\[rq], the snapshots are never created (useful in conjunction
to \[lq]no\[rq]. with the \fIresume_missing\fR option if another instance of btrbk is
taking care of snapshot creation). Defaults to \[lq]always\[rq].
.TP .TP
\fBincremental\fR yes|no|strict \fBincremental\fR yes|no|strict
Perform incremental backups. Defaults to \[lq]yes\[rq]. If set to Perform incremental backups. Defaults to \[lq]yes\[rq]. If set to