mirror of https://github.com/digint/btrbk
btrbk: refactoring of config file semantics (allow tree-style configuration): implemented new parser
parent
eadc6c80e2
commit
231203e44e
174
btrbk
174
btrbk
|
@ -61,6 +61,23 @@ my %uuid_info;
|
||||||
my $dryrun;
|
my $dryrun;
|
||||||
my $loglevel = 1;
|
my $loglevel = 1;
|
||||||
|
|
||||||
|
my %config_defaults = (
|
||||||
|
snapshot_dir => "_btrbk_snap",
|
||||||
|
incremental => undef,
|
||||||
|
init => undef,
|
||||||
|
create => undef,
|
||||||
|
log => undef,
|
||||||
|
snapshot_create_always => 0, # TODO: honor this
|
||||||
|
snapshot_preserve_all => 1, # TODO: honor this
|
||||||
|
snapshot_preserve_days => undef,
|
||||||
|
snapshot_preserve_weekly => undef,
|
||||||
|
target_preserve_all => 1, # TODO: honor this
|
||||||
|
target_preserve_days => undef,
|
||||||
|
target_preserve_weekly => undef,
|
||||||
|
);
|
||||||
|
|
||||||
|
my @config_target_types = qw(send-receive);
|
||||||
|
|
||||||
|
|
||||||
sub VERSION_MESSAGE
|
sub VERSION_MESSAGE
|
||||||
{
|
{
|
||||||
|
@ -184,70 +201,145 @@ sub btr_subvolume_detail($)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub config_key($$)
|
||||||
|
{
|
||||||
|
my $node = shift;
|
||||||
|
my $key = shift;
|
||||||
|
TRACE "config_key: $node->{CONTEXT}";
|
||||||
|
while(not defined($node->{$key})) {
|
||||||
|
return undef unless($node->{PARENT});
|
||||||
|
$node = $node->{PARENT};
|
||||||
|
}
|
||||||
|
TRACE "config_key: returning $node->{$key}";
|
||||||
|
return $node->{$key};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
sub parse_config($)
|
sub parse_config($)
|
||||||
{
|
{
|
||||||
my $file = shift;
|
my $file = shift;
|
||||||
my @jobs;
|
my $root = { CONTEXT => "root",
|
||||||
|
%config_defaults
|
||||||
|
};
|
||||||
|
my $cur = $root;
|
||||||
|
|
||||||
unless(-r "$file") {
|
unless(-r "$file") {
|
||||||
WARN "Configuration file not found: $file";
|
WARN "Configuration file not found: $file";
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
TRACE "parsing config file: $file";
|
DEBUG "config: parsing file: $file";
|
||||||
open(FILE, '<', $file) or die $!;
|
open(FILE, '<', $file) or die $!;
|
||||||
while (<FILE>) {
|
while (<FILE>) {
|
||||||
chomp;
|
chomp;
|
||||||
next if /^\s*#/; # ignore comments
|
next if /^\s*#/; # ignore comments
|
||||||
next if /^\s*$/; # ignore empty lines
|
next if /^\s*$/; # ignore empty lines
|
||||||
TRACE "parse_config: parsing line: $_";
|
TRACE "config: parsing line $. with context=$cur->{CONTEXT}: \"$_\"";
|
||||||
if(/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*$/)
|
if(/^(\s*)([a-zA-Z_]+)\s+(.*)$/)
|
||||||
{
|
{
|
||||||
my %job = ( type => "subvol_backup",
|
my ($indent, $key, $value) = (length($1), lc($2), $3);
|
||||||
sroot => $1,
|
$value =~ s/\s*$//;
|
||||||
svol => $2,
|
# NOTE: we do not perform checks on indentation!
|
||||||
droot => $3,
|
|
||||||
);
|
|
||||||
my @options = split(/,/, $4);
|
|
||||||
|
|
||||||
$job{sroot} =~ s/\/+$//; # remove trailing slash
|
if($key eq "volume")
|
||||||
$job{sroot} =~ s/^\/+/\//; # sanitize leading slash
|
{
|
||||||
$job{svol} =~ s/\/+$//; # remove trailing slash
|
$cur = $root;
|
||||||
$job{svol} =~ s/^\/+//; # remove leading slash
|
DEBUG "config: context forced to: $cur->{CONTEXT}";
|
||||||
if($job{svol} =~ /\//) {
|
DEBUG "config: adding volume \"$value\" to root context";
|
||||||
ERROR "src_subvol contains slashes: $job{svol}";
|
$value =~ s/\/+$//; # remove trailing slash
|
||||||
|
$value =~ s/^\/+/\//; # sanitize leading slash
|
||||||
|
my $volume = { CONTEXT => "volume",
|
||||||
|
PARENT => $cur,
|
||||||
|
sroot => $value,
|
||||||
|
};
|
||||||
|
$cur->{VOLUME} //= [];
|
||||||
|
push(@{$cur->{VOLUME}}, $volume);
|
||||||
|
$cur = $volume;
|
||||||
|
}
|
||||||
|
elsif($key eq "subvolume")
|
||||||
|
{
|
||||||
|
while($cur->{CONTEXT} ne "volume") {
|
||||||
|
if(($cur->{CONTEXT} eq "root") || (not $cur->{PARENT})) {
|
||||||
|
ERROR "subvolume keyword outside volume context, in \"$file\" line $.";
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
$cur = $cur->{PARENT} || die;
|
||||||
|
DEBUG "config: context changed to: $cur->{CONTEXT}";
|
||||||
|
}
|
||||||
|
$value =~ s/\/+$//; # remove trailing slash
|
||||||
|
$value =~ s/^\/+//; # remove leading slash
|
||||||
|
if($value =~ /\//) {
|
||||||
|
ERROR "subvolume contains slashes: \"$value\" in \"$file\" line $.";
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG "config: adding subvolume \"$value\" to volume context: $cur->{sroot}";
|
||||||
|
my $subvolume = { CONTEXT => "subvolume",
|
||||||
|
PARENT => $cur,
|
||||||
|
svol => $value,
|
||||||
|
};
|
||||||
|
$cur->{SUBVOLUME} //= [];
|
||||||
|
push(@{$cur->{SUBVOLUME}}, $subvolume);
|
||||||
|
$cur = $subvolume;
|
||||||
|
}
|
||||||
|
elsif($key eq "target")
|
||||||
|
{
|
||||||
|
if($cur->{CONTEXT} eq "target") {
|
||||||
|
$cur = $cur->{PARENT} || die;
|
||||||
|
DEBUG "config: context changed to: $cur->{CONTEXT}";
|
||||||
|
}
|
||||||
|
if($cur->{CONTEXT} ne "subvolume") {
|
||||||
|
ERROR "target keyword outside subvolume context, in \"$file\" line $.";
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
if($value =~ /^(\S+)\s+(\S+)$/)
|
||||||
|
{
|
||||||
|
my ($target_type, $droot) = ($1, $2);
|
||||||
|
unless(grep(/^$target_type$/, @config_target_types)) {
|
||||||
|
ERROR "unknown target type \"$target_type\" in \"$file\" line $.";
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
$droot =~ s/\/+$//; # remove trailing slash
|
||||||
|
$droot =~ s/^\/+/\//; # sanitize leading slash
|
||||||
|
DEBUG "config: adding target \"$droot\" (type=$target_type) to subvolume context: $cur->{PARENT}->{sroot}/$cur->{svol}";
|
||||||
|
my $target = { CONTEXT => "target",
|
||||||
|
PARENT => $cur,
|
||||||
|
target_type => $target_type,
|
||||||
|
droot => $droot,
|
||||||
|
};
|
||||||
|
$cur->{TARGET} //= [];
|
||||||
|
push(@{$cur->{TARGET}}, $target);
|
||||||
|
$cur = $target;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ERROR "Ambiguous target configuration, in \"$file\" line $.";
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elsif(grep(/^$key$/, keys %config_defaults)) # accept only keys listed in %config_default
|
||||||
|
{
|
||||||
|
DEBUG "config: adding option \"$key=$value\" to $cur->{CONTEXT} context";
|
||||||
|
$cur->{$key} = $value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ERROR "Unknown option \"$key\" in \"$file\" line $.";
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
$job{droot} =~ s/\/+$//; # remove trailing slash
|
TRACE "line processed: new context=$cur->{CONTEXT}";
|
||||||
$job{droot} =~ s/^\/+/\//; # sanitize leading slash
|
|
||||||
|
|
||||||
$job{mountpoint} = $job{sroot}; # TODO: honor this, automount
|
|
||||||
|
|
||||||
foreach(@options) {
|
|
||||||
if ($_ eq "incremental") { $job{options}->{incremental} = 1; }
|
|
||||||
elsif($_ eq "init") { $job{options}->{init} = 1; }
|
|
||||||
elsif($_ eq "create") { $job{options}->{create} = 1; }
|
|
||||||
elsif($_ eq "log") { $job{options}->{log} = 1; }
|
|
||||||
elsif($_ =~ /^log=(\S+)$/) { $job{options}->{log} = 1; $job{options}->{logfile} = $1; }
|
|
||||||
elsif($_ =~ /^preserve=[dD]([0-9]+)[wW]([0-9]+)$/) { $job{options}->{preserve} = {daily => $1, weekly => $2}; }
|
|
||||||
else {
|
|
||||||
ERROR "Ambiguous option=\"$_\": $file line $.";
|
|
||||||
return undef; # be very strict here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TRACE "parse_config: adding job \"$job{type}\": $job{sroot}/$job{svol} -> $job{droot}/";
|
|
||||||
push @jobs, \%job;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ERROR "Ambiguous configuration: $file line $.";
|
ERROR "Parse error in \"$file\" line $.";
|
||||||
return undef; # be very strict here
|
return undef;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close FILE;
|
|
||||||
TRACE "jobs: " . Dumper(\@jobs);
|
TRACE(Data::Dumper->Dump([$root], ["config_root"]));
|
||||||
return \@jobs;
|
exit 0;
|
||||||
|
return $root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
60
btrbk.conf
60
btrbk.conf
|
@ -16,24 +16,54 @@
|
||||||
# log=<logfile> append log to specified logfile
|
# log=<logfile> append log to specified logfile
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# old:
|
||||||
|
# /mnt/btr_system root_gentoo /mnt/btr_ext/_btrbk incremental,init,preserve=d14w10
|
||||||
|
|
||||||
/mnt/btr_system root_gentoo /mnt/btr_ext/_btrbk incremental,init,preserve=d14w10
|
snapshot_dir _btrbk_snap
|
||||||
/mnt/btr_system root_gentoo /mnt/btr_backup/_btrbk incremental,init,log
|
snapshot_create_always yes
|
||||||
/mnt/btr_system kvm /mnt/btr_ext/_btrbk incremental,init
|
|
||||||
/mnt/btr_system kvm /mnt/btr_backup/_btrbk incremental,init,log
|
|
||||||
|
|
||||||
/mnt/btr_data home /mnt/btr_backup/_btrbk incremental,init,log
|
# TODO: incremental = {yes|no|strict}
|
||||||
/mnt/btr_data sdms.data /mnt/btr_backup/_btrbk incremental,init,log
|
incremental strict
|
||||||
|
init yes
|
||||||
|
|
||||||
/mnt/btr_ext data /mnt/btr_backup/_btrbk incremental,init,log
|
snapshot_preserve_days 14
|
||||||
# TODO: these monthly
|
snapshot_preserve_weekly 0
|
||||||
#/mnt/btr_ext video /mnt/btr_backup/_btrbk incremental,init,log
|
|
||||||
#/mnt/btr_ext audio /mnt/btr_backup/_btrbk incremental,init,log
|
|
||||||
|
|
||||||
# TODO: these monthly
|
target_preserve_days 28
|
||||||
#/mnt/btr_boot boot /mnt/btr_ext/_btrbk incremental,init,log
|
target_preserve_weekly 10
|
||||||
#/mnt/btr_boot boot /mnt/btr_backup/_btrbk incremental
|
|
||||||
|
|
||||||
|
|
||||||
# non-incremental, create a new snapshot at every invocation!
|
volume /mnt/btr_system
|
||||||
##/mnt/btr_boot boot /mnt/btr_backup/_btrbk create
|
subvolume root_gentoo
|
||||||
|
target send-receive /mnt/btr_ext/_btrbk
|
||||||
|
target send-receive /mnt/btr_backup/_btrbk
|
||||||
|
log sidecar
|
||||||
|
|
||||||
|
subvolume kvm
|
||||||
|
target_preserve_days 7
|
||||||
|
target_preserve_weekly 4
|
||||||
|
|
||||||
|
target send-receive /mnt/btr_ext/_btrbk
|
||||||
|
target_preserve_weekly 0
|
||||||
|
|
||||||
|
target send-receive /mnt/btr_backup/_btrbk
|
||||||
|
log sidecar
|
||||||
|
|
||||||
|
|
||||||
|
volume /mnt/btr_data
|
||||||
|
subvolume home
|
||||||
|
target send-receive /mnt/btr_backup/_btrbk
|
||||||
|
|
||||||
|
|
||||||
|
volume /mnt/btr_ext
|
||||||
|
subvolume data
|
||||||
|
target send-receive /mnt/btr_backup/_btrbk
|
||||||
|
|
||||||
|
|
||||||
|
volume /mnt/btr_boot
|
||||||
|
# schedule weekly
|
||||||
|
incremental yes
|
||||||
|
|
||||||
|
subvolume boot
|
||||||
|
target send-receive /mnt/btr_ext/_btrbk
|
||||||
|
target send-receive /mnt/btr_backup/_btrbk
|
||||||
|
|
Loading…
Reference in New Issue