mirror of https://github.com/digint/btrbk
btrbk: rename action "clone" to "archive" (should have been like this from the beginning)
parent
4bd68a2e35
commit
de05b99757
60
btrbk
60
btrbk
|
@ -245,7 +245,7 @@ sub HELP_MESSAGE
|
||||||
print STDERR " --progress show progress bar on send-receive operation\n";
|
print STDERR " --progress show progress bar on send-receive operation\n";
|
||||||
print STDERR "\n";
|
print STDERR "\n";
|
||||||
print STDERR "commands:\n";
|
print STDERR "commands:\n";
|
||||||
print STDERR " run perform backup operations as defined in the config file\n";
|
print STDERR " run perform backup operations as defined in the config\n";
|
||||||
print STDERR " dryrun don't run btrfs commands; show what would be executed\n";
|
print STDERR " dryrun don't run btrfs commands; show what would be executed\n";
|
||||||
print STDERR " stats print snapshot/backup statistics\n";
|
print STDERR " stats print snapshot/backup statistics\n";
|
||||||
print STDERR " list <subcommand> available subcommands are:\n";
|
print STDERR " list <subcommand> available subcommands are:\n";
|
||||||
|
@ -257,10 +257,10 @@ sub HELP_MESSAGE
|
||||||
print STDERR " volume configured volume sections\n";
|
print STDERR " volume configured volume sections\n";
|
||||||
print STDERR " target configured targets\n";
|
print STDERR " target configured targets\n";
|
||||||
print STDERR " clean delete incomplete (garbled) backups\n";
|
print STDERR " clean delete incomplete (garbled) backups\n";
|
||||||
print STDERR " clone <src> <dst> recursively copy all subvolumes (experimental)\n";
|
print STDERR " archive <src> <dst> recursively copy all subvolumes (experimental)\n";
|
||||||
print STDERR " usage print filesystem usage\n";
|
print STDERR " usage print filesystem usage\n";
|
||||||
print STDERR " origin <subvol> print origin information for subvolume\n";
|
print STDERR " origin <subvol> print origin information for subvolume\n";
|
||||||
print STDERR " diff <from> <to> shows new files since subvolume <from> for subvolume <to>\n";
|
print STDERR " diff <from> <to> shows new files between related subvolumes\n";
|
||||||
print STDERR "\n";
|
print STDERR "\n";
|
||||||
print STDERR "For additional information, see $PROJECT_HOME\n";
|
print STDERR "For additional information, see $PROJECT_HOME\n";
|
||||||
}
|
}
|
||||||
|
@ -2634,7 +2634,7 @@ sub macro_delete($$$$$;@)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sub macro_clone_target($$$;$)
|
sub macro_archive_target($$$;$)
|
||||||
{
|
{
|
||||||
my $sroot = shift || die;
|
my $sroot = shift || die;
|
||||||
my $droot = shift || die;
|
my $droot = shift || die;
|
||||||
|
@ -3181,7 +3181,7 @@ MAIN:
|
||||||
WARN 'Found option "--progress", but "pv" is not present: (please install "pv")';
|
WARN 'Found option "--progress", but "pv" is not present: (please install "pv")';
|
||||||
$show_progress = 0;
|
$show_progress = 0;
|
||||||
}
|
}
|
||||||
my ($action_run, $action_usage, $action_resolve, $action_diff, $action_origin, $action_config_print, $action_list, $action_clean, $action_clone);
|
my ($action_run, $action_usage, $action_resolve, $action_diff, $action_origin, $action_config_print, $action_list, $action_clean, $action_archive);
|
||||||
my @filter_args;
|
my @filter_args;
|
||||||
my $args_allow_group = 1;
|
my $args_allow_group = 1;
|
||||||
my $args_expected_min = 0;
|
my $args_expected_min = 0;
|
||||||
|
@ -3196,8 +3196,8 @@ MAIN:
|
||||||
$action_clean = 1;
|
$action_clean = 1;
|
||||||
@filter_args = @ARGV;
|
@filter_args = @ARGV;
|
||||||
}
|
}
|
||||||
elsif ($command eq "clone") {
|
elsif ($command eq "archive") {
|
||||||
$action_clone = 1;
|
$action_archive = 1;
|
||||||
$args_expected_min = $args_expected_max = 2;
|
$args_expected_min = $args_expected_max = 2;
|
||||||
$args_allow_group = 0;
|
$args_allow_group = 0;
|
||||||
@filter_args = @ARGV;
|
@filter_args = @ARGV;
|
||||||
|
@ -3412,10 +3412,10 @@ MAIN:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if($action_clone)
|
if($action_archive)
|
||||||
{
|
{
|
||||||
#
|
#
|
||||||
# clone (archive) tree
|
# archive (clone) tree
|
||||||
#
|
#
|
||||||
# NOTE: This is intended to work without a config file! The only
|
# NOTE: This is intended to work without a config file! The only
|
||||||
# thing used from the configuration is the SSH and transaction log
|
# thing used from the configuration is the SSH and transaction log
|
||||||
|
@ -3429,9 +3429,9 @@ MAIN:
|
||||||
# FIXME: add command line options for preserve logic
|
# FIXME: add command line options for preserve logic
|
||||||
$config->{SUBSECTION} = []; # clear configured subsections, we build them dynamically
|
$config->{SUBSECTION} = []; # clear configured subsections, we build them dynamically
|
||||||
|
|
||||||
my $clone_src_root = vinfo($src_url, $config);
|
my $src_root = vinfo($src_url, $config);
|
||||||
unless(vinfo_init_root($clone_src_root, resolve_subdir => 1)) {
|
unless(vinfo_init_root($src_root, resolve_subdir => 1)) {
|
||||||
ERROR "Failed to fetch subvolume detail for '$clone_src_root->{PRINT}'" . ($err ? ": $err" : "");
|
ERROR "Failed to fetch subvolume detail for '$src_root->{PRINT}'" . ($err ? ": $err" : "");
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
my $target_root = vinfo($target_url, $config);
|
my $target_root = vinfo($target_url, $config);
|
||||||
|
@ -3441,7 +3441,7 @@ MAIN:
|
||||||
}
|
}
|
||||||
|
|
||||||
my %name_uniq;
|
my %name_uniq;
|
||||||
my @subvol_list = @{vinfo_subvol_list($clone_src_root)};
|
my @subvol_list = @{vinfo_subvol_list($src_root)};
|
||||||
my @sorted = sort { ($a->{subtree_depth} <=> $b->{subtree_depth}) || ($a->{SUBVOL_DIR} cmp $b->{SUBVOL_DIR}) } @subvol_list;
|
my @sorted = sort { ($a->{subtree_depth} <=> $b->{subtree_depth}) || ($a->{SUBVOL_DIR} cmp $b->{SUBVOL_DIR}) } @subvol_list;
|
||||||
foreach my $vol (@sorted) {
|
foreach my $vol (@sorted) {
|
||||||
next unless($vol->{node}{readonly});
|
next unless($vol->{node}{readonly});
|
||||||
|
@ -3455,34 +3455,34 @@ MAIN:
|
||||||
$name_uniq{"$subvol_dir/$snapshot_name"} = 1;
|
$name_uniq{"$subvol_dir/$snapshot_name"} = 1;
|
||||||
my $droot_url = $target_url . ($subvol_dir eq "" ? "" : "/$subvol_dir");
|
my $droot_url = $target_url . ($subvol_dir eq "" ? "" : "/$subvol_dir");
|
||||||
my $sroot_url = $src_url . ($subvol_dir eq "" ? "" : "/$subvol_dir");
|
my $sroot_url = $src_url . ($subvol_dir eq "" ? "" : "/$subvol_dir");
|
||||||
my $config_clone_src = { CONTEXT => "clone_source",
|
my $config_sroot = { CONTEXT => "archive_source",
|
||||||
PARENT => $config,
|
PARENT => $config,
|
||||||
url => $sroot_url, # ABORTED() needs this
|
url => $sroot_url, # ABORTED() needs this
|
||||||
snapshot_name => $snapshot_name,
|
snapshot_name => $snapshot_name,
|
||||||
};
|
};
|
||||||
my $config_target = { CONTEXT => "target",
|
my $config_droot = { CONTEXT => "target",
|
||||||
PARENT => $config_clone_src,
|
PARENT => $config_sroot,
|
||||||
target_type => "send-receive", # macro_send_receive checks this
|
target_type => "send-receive", # macro_send_receive checks this
|
||||||
url => $droot_url, # ABORTED() needs this
|
url => $droot_url, # ABORTED() needs this
|
||||||
};
|
};
|
||||||
$config_clone_src->{SUBSECTION} = [ $config_target ];
|
$config_sroot->{SUBSECTION} = [ $config_droot ];
|
||||||
push(@{$config->{SUBSECTION}}, $config_clone_src);
|
push(@{$config->{SUBSECTION}}, $config_sroot);
|
||||||
|
|
||||||
my $sroot = vinfo($sroot_url, $config_clone_src);
|
my $sroot = vinfo($sroot_url, $config_sroot);
|
||||||
vinfo_assign_config($sroot, $config_clone_src);
|
vinfo_assign_config($sroot, $config_sroot);
|
||||||
unless(vinfo_init_root($sroot, resolve_subdir => 1)) {
|
unless(vinfo_init_root($sroot, resolve_subdir => 1)) {
|
||||||
ABORTED($sroot, "Failed to fetch subvolume detail" . ($err ? ": $err" : ""));
|
ABORTED($sroot, "Failed to fetch subvolume detail" . ($err ? ": $err" : ""));
|
||||||
WARN "Skipping clone source \"$sroot->{PRINT}\": $abrt";
|
WARN "Skipping archive source \"$sroot->{PRINT}\": $abrt";
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $droot = vinfo($droot_url, $config_target);
|
my $droot = vinfo($droot_url, $config_droot);
|
||||||
vinfo_assign_config($droot, $config_target);
|
vinfo_assign_config($droot, $config_droot);
|
||||||
unless(vinfo_init_root($droot, resolve_subdir => 1)) {
|
unless(vinfo_init_root($droot, resolve_subdir => 1)) {
|
||||||
DEBUG("Failed to fetch subvolume detail" . ($err ? ": $err" : ""));
|
DEBUG("Failed to fetch subvolume detail" . ($err ? ": $err" : ""));
|
||||||
unless(system_mkdir($droot)) {
|
unless(system_mkdir($droot)) {
|
||||||
ABORTED($droot, "Failed to create directory: $droot->{PRINT}/");
|
ABORTED($droot, "Failed to create directory: $droot->{PRINT}/");
|
||||||
WARN "Skipping clone target \"$droot->{PRINT}\": $abrt";
|
WARN "Skipping archive target \"$droot->{PRINT}\": $abrt";
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
if($dryrun) {
|
if($dryrun) {
|
||||||
|
@ -3494,7 +3494,7 @@ MAIN:
|
||||||
# after directory is created, try to init again
|
# after directory is created, try to init again
|
||||||
unless(vinfo_init_root($droot, resolve_subdir => 1)) {
|
unless(vinfo_init_root($droot, resolve_subdir => 1)) {
|
||||||
ABORTED($droot, "Failed to fetch subvolume detail" . ($err ? ": $err" : ""));
|
ABORTED($droot, "Failed to fetch subvolume detail" . ($err ? ": $err" : ""));
|
||||||
WARN "Skipping clone target \"$droot->{PRINT}\": $abrt";
|
WARN "Skipping archive target \"$droot->{PRINT}\": $abrt";
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3506,11 +3506,11 @@ MAIN:
|
||||||
}
|
}
|
||||||
|
|
||||||
my $schedule_results = [];
|
my $schedule_results = [];
|
||||||
foreach my $sroot (vinfo_subsection($config, 'clone_source')) {
|
foreach my $sroot (vinfo_subsection($config, 'archive_source')) {
|
||||||
foreach my $droot (vinfo_subsection($sroot, 'target')) {
|
foreach my $droot (vinfo_subsection($sroot, 'target')) {
|
||||||
my $snapshot_name = config_key($droot, "snapshot_name") // die;
|
my $snapshot_name = config_key($droot, "snapshot_name") // die;
|
||||||
INFO "Archiving subvolumes: $sroot->{PRINT}/${snapshot_name}.*";
|
INFO "Archiving subvolumes: $sroot->{PRINT}/${snapshot_name}.*";
|
||||||
macro_clone_target($sroot, $droot, $snapshot_name, { results => $schedule_results });
|
macro_archive_target($sroot, $droot, $snapshot_name, { results => $schedule_results });
|
||||||
if(ABORTED($droot)) {
|
if(ABORTED($droot)) {
|
||||||
# also abort $sroot
|
# also abort $sroot
|
||||||
ABORTED($sroot, "At least one target aborted");
|
ABORTED($sroot, "At least one target aborted");
|
||||||
|
@ -3536,7 +3536,7 @@ MAIN:
|
||||||
# print scheduling results
|
# print scheduling results
|
||||||
if($print_schedule) {
|
if($print_schedule) {
|
||||||
my @data = map { { %$_, vinfo_prefixed_keys("", $_->{value}) }; } @$schedule_results;
|
my @data = map { { %$_, vinfo_prefixed_keys("", $_->{value}) }; } @$schedule_results;
|
||||||
print_formatted("schedule", \@data, title => "CLONE SCHEDULE");
|
print_formatted("schedule", \@data, title => "ARCHIVE SCHEDULE");
|
||||||
print "\n";
|
print "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3546,7 +3546,7 @@ MAIN:
|
||||||
{
|
{
|
||||||
my @unrecoverable;
|
my @unrecoverable;
|
||||||
my @out;
|
my @out;
|
||||||
foreach my $sroot (vinfo_subsection($config, 'clone_source')) {
|
foreach my $sroot (vinfo_subsection($config, 'archive_source')) {
|
||||||
foreach my $droot (vinfo_subsection($sroot, 'target', 1)) {
|
foreach my $droot (vinfo_subsection($sroot, 'target', 1)) {
|
||||||
my @subvol_out;
|
my @subvol_out;
|
||||||
foreach(@{$droot->{SUBVOL_RECEIVED} // []}) {
|
foreach(@{$droot->{SUBVOL_RECEIVED} // []}) {
|
||||||
|
@ -3567,7 +3567,7 @@ MAIN:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print_header(title => "Clone Summary",
|
print_header(title => "Archive Summary",
|
||||||
time => $start_time,
|
time => $start_time,
|
||||||
legend => [
|
legend => [
|
||||||
"--- deleted subvolume",
|
"--- deleted subvolume",
|
||||||
|
|
47
doc/FAQ.md
47
doc/FAQ.md
|
@ -199,7 +199,23 @@ data physically, either to the datacenter or to your safe in the
|
||||||
basement.
|
basement.
|
||||||
|
|
||||||
|
|
||||||
### Answer 1: Use external storage as "stream-fifo"
|
### Answer 1: Use "btrbk archive"
|
||||||
|
|
||||||
|
A robust approach is to use external disks as archives (secondary
|
||||||
|
backups), and regularly run "btrbk archive" on them. As a nice side
|
||||||
|
effect, this also detects possible read-errors on your backup targets
|
||||||
|
(Note that a "btrfs scrub" is still more effective for that purpose).
|
||||||
|
|
||||||
|
See **btrbk archive** command in [btrbk(1)] for more details.
|
||||||
|
|
||||||
|
**Note that kernels >=4.1 and <4.4 have a bug when re-sending
|
||||||
|
subvolumes**, make sure you run a recent/patched kernel or step 3 will
|
||||||
|
fail. Read
|
||||||
|
[this thread on gmane](http://thread.gmane.org/gmane.comp.file-systems.btrfs/48798)
|
||||||
|
(the patch provided is confirmed working on kernels 4.2.x and 4.3.x).
|
||||||
|
|
||||||
|
|
||||||
|
### Answer 2: Use external storage as "stream-fifo"
|
||||||
|
|
||||||
This example uses a USB disk as "stream-fifo" for transferring
|
This example uses a USB disk as "stream-fifo" for transferring
|
||||||
(cloning) of btrfs subvolumes:
|
(cloning) of btrfs subvolumes:
|
||||||
|
@ -217,32 +233,3 @@ This approach has the advantage that you don't need to reformat your
|
||||||
USB disk. This works fine, but be aware that you may run into trouble
|
USB disk. This works fine, but be aware that you may run into trouble
|
||||||
if a single stream gets corrupted, making all subsequent streams
|
if a single stream gets corrupted, making all subsequent streams
|
||||||
unusable.
|
unusable.
|
||||||
|
|
||||||
|
|
||||||
### Answer 2: Clone btrfs subvolumes
|
|
||||||
|
|
||||||
A more robust approach is to use the USB disk as secondary backup.
|
|
||||||
This has the advantage that possible errors can already be detected by
|
|
||||||
btrfs on the source side:
|
|
||||||
|
|
||||||
1. Initialize USB disk:
|
|
||||||
|
|
||||||
`mkfs.btrfs /dev/usbX`
|
|
||||||
|
|
||||||
2. For all source subvolumes (in order of generation):
|
|
||||||
|
|
||||||
`btrfs send /source/subvolX -p PARENT | btrfs receive /usbdisk/`
|
|
||||||
|
|
||||||
3. At the target location (in order of generation):
|
|
||||||
|
|
||||||
`btrfs send /usbdisk/subvolX -p PARENT | btrfs receive /target`
|
|
||||||
|
|
||||||
If you simply want to have a clone of the source disk, skip step 3 and
|
|
||||||
store your USB disk in a safe. You will be able to use it for
|
|
||||||
restoring backups later, or *as a replacement for your backup disks*.
|
|
||||||
|
|
||||||
**Note that kernels >=4.1 and <4.4 have a bug when re-sending
|
|
||||||
subvolumes**, make sure you run a recent/patched kernel or step 3 will
|
|
||||||
fail. Read
|
|
||||||
[this thread on gmane](http://thread.gmane.org/gmane.comp.file-systems.btrfs/48798)
|
|
||||||
(the patch provided is confirmed working on kernels 4.2.x and 4.3.x).
|
|
||||||
|
|
19
doc/btrbk.1
19
doc/btrbk.1
|
@ -167,26 +167,27 @@ by the \fBrun\fR command. Use in conjunction with \fI\-l debug\fR to
|
||||||
see the btrfs commands that would be executed.
|
see the btrfs commands that would be executed.
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
.B clone
|
.B archive
|
||||||
<source> <target>
|
<source> <target>
|
||||||
.I *experimental*
|
.I *experimental*
|
||||||
.RS 4
|
.RS 4
|
||||||
Recursively copy all subvolumes created by btrbk from <source> to
|
Recursively copy all subvolumes created by btrbk from <source> to
|
||||||
<target> directory, optionally rescheduled using
|
<target> directory, optionally rescheduled using
|
||||||
\fIarchive_preserve_*\fR configuration options. Useful for creating
|
\fIarchive_preserve_*\fR configuration options. Also creates directory
|
||||||
extra archive copies (clones) from your backup disks. Note that you
|
tree on <target> (see bugs below). Useful for creating extra archive
|
||||||
can continue using btrbk after swapping your backup disk with the
|
copies (clones) from your backup disks. Note that you can continue
|
||||||
cloned disk.
|
using btrbk after swapping your backup disk with the archive disk.
|
||||||
.PP
|
.PP
|
||||||
Note that this feature needs a \fBlinux kernel >=4.4\fR to work
|
Note that this feature needs a \fBlinux kernel >=4.4\fR to work
|
||||||
correctly! Kernels >=4.1 and <4.4 have a bug when re-sending
|
correctly! Kernels >=4.1 and <4.4 have a bug when re-sending
|
||||||
subvolumes (the cloned subvolumes will have incorrect received_uuid,
|
subvolumes (the archived subvolumes will have incorrect received_uuid,
|
||||||
see <http://thread.gmane.org/gmane.comp.file-systems.btrfs/48798>), so
|
see <http://thread.gmane.org/gmane.comp.file-systems.btrfs/48798>), so
|
||||||
make sure you run a recent kernel.
|
make sure you run a recent kernel.
|
||||||
.PP
|
.PP
|
||||||
Known bugs: On the target filesystem, you have to create all
|
Known bugs: If you want to use nested subvolumes on the target
|
||||||
directories by hand, or "btrbk clone" will complain: "WARNING:
|
filesystem, you need to create them by hand (e.g. by running "btrfs
|
||||||
Skipping clone target <...>: Failed to fetch subvolume detail".
|
subvolume create <target>/dir"). Check the output of --dry-run if
|
||||||
|
unsure.
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
.B stats
|
.B stats
|
||||||
|
|
Loading…
Reference in New Issue