borgmatic: add option for pattern matching

Borgmatic has support for Borg's pattern matching. It is mutually
exclusive with the existing `sourceDirectories` option, so assertions
have been added to make sure that both are not set at the same
time (but also that at least one of them is). Additionally, tests have
been added to test the following configurations: `patterns` instead of
`sourceDirectories`, both at the same time, and neither.
This commit is contained in:
Liassica 2024-02-20 19:03:20 -06:00 committed by Robert Helgesson
parent 17431970b4
commit 16311f1d3c
No known key found for this signature in database
GPG key ID: 96E745BD17AA17ED
5 changed files with 150 additions and 3 deletions

View file

@ -76,12 +76,39 @@ let
(mkAfter [ (toString hmExcludeFile) ]);
options = {
location = {
sourceDirectories = mkOption {
sourceDirectories = mkNullableOption {
type = types.listOf types.str;
description = "Directories to backup.";
default = null;
description = ''
Directories to backup.
Mutually exclusive with [](#opt-programs.borgmatic.backups._name_.location.patterns).
'';
example = literalExpression "[config.home.homeDirectory]";
};
patterns = mkNullableOption {
type = types.listOf types.str;
default = null;
description = ''
Patterns to include/exclude.
See the output of `borg help patterns` for the syntax. Pattern paths
are relative to `/` even when a different recursion root is set.
Mutually exclusive with [](#opt-programs.borgmatic.backups._name_.location.sourceDirectories).
'';
example = literalExpression ''
[
"R /home/user"
"- home/user/.cache"
"- home/user/Downloads"
"+ home/user/Videos/Important Video"
"- home/user/Videos"
]
'';
};
repositories = mkOption {
type = types.listOf (types.either types.str repositoryOption);
apply = cleanRepositories;
@ -194,6 +221,7 @@ let
writeConfig = config:
generators.toYAML { } (removeNullValues ({
source_directories = config.location.sourceDirectories;
patterns = config.location.patterns;
repositories = config.location.repositories;
encryption_passcommand = config.storage.encryptionPasscommand;
keep_within = config.retention.keepWithin;
@ -247,7 +275,19 @@ in {
assertions = [
(lib.hm.assertions.assertPlatform "programs.borgmatic" pkgs
lib.platforms.linux)
];
] ++ (mapAttrsToList (backup: opts: {
assertion = opts.location.sourceDirectories == null
|| opts.location.patterns == null;
message = ''
Borgmatic backup configuration "${backup}" cannot specify both 'location.sourceDirectories' and 'location.patterns'.
'';
}) cfg.backups) ++ (mapAttrsToList (backup: opts: {
assertion = !(opts.location.sourceDirectories == null
&& opts.location.patterns == null);
message = ''
Borgmatic backup configuration "${backup}" must specify one of 'location.sourceDirectories' or 'location.patterns'.
'';
}) cfg.backups);
xdg.configFile = with lib.attrsets;
mapAttrs' (configName: config:

View file

@ -0,0 +1,26 @@
{ config, pkgs, ... }:
let
backups = config.programs.borgmatic.backups;
in {
programs.borgmatic = {
enable = true;
backups = {
main = {
location = {
sourceDirectories = [ "/my-stuff-to-backup" ];
patterns = [ "R /" "+ my-stuff-to-backup" ];
repositories = [ "/mnt/disk1" ];
};
};
};
};
test.stubs.borgmatic = { };
test.asserts.assertions.expected = [''
Borgmatic backup configuration "main" cannot specify both 'location.sourceDirectories' and 'location.patterns'.
''];
}

View file

@ -1,5 +1,10 @@
{
borgmatic-program-basic-configuration = ./basic-configuration.nix;
borgmatic-program-patterns-configuration = ./patterns-configuration.nix;
borgmatic-program-both-sourcedirectories-and-patterns =
./both-sourcedirectories-and-patterns.nix;
borgmatic-program-neither-sourcedirectories-nor-patterns =
./neither-sourcedirectories-nor-patterns.nix;
borgmatic-program-include-hm-symlinks = ./include-hm-symlinks.nix;
borgmatic-program-exclude-hm-symlinks = ./exclude-hm-symlinks.nix;
borgmatic-program-exclude-hm-symlinks-nothing-else =

View file

@ -0,0 +1,18 @@
{ config, pkgs, ... }:
let
backups = config.programs.borgmatic.backups;
in {
programs.borgmatic = {
enable = true;
backups = { main = { location = { repositories = [ "/mnt/disk1" ]; }; }; };
};
test.stubs.borgmatic = { };
test.asserts.assertions.expected = [''
Borgmatic backup configuration "main" must specify one of 'location.sourceDirectories' or 'location.patterns'.
''];
}

View file

@ -0,0 +1,58 @@
{ config, pkgs, ... }:
let
boolToString = bool: if bool then "true" else "false";
backups = config.programs.borgmatic.backups;
in {
programs.borgmatic = {
enable = true;
backups = {
main = {
location = {
patterns = [
"R /home/user"
"+ home/user/stuff-to-backup"
"+ home/user/junk/important-file"
"- home/user/junk"
];
repositories = [ "/mnt/backup-drive" ];
};
};
};
};
test.stubs.borgmatic = { };
nmt.script = ''
config_file=$TESTED/home-files/.config/borgmatic.d/main.yaml
assertFileExists $config_file
declare -A expectations
expectations[patterns[0]]="${
builtins.elemAt backups.main.location.patterns 0
}"
expectations[patterns[1]]="${
builtins.elemAt backups.main.location.patterns 1
}"
expectations[patterns[2]]="${
builtins.elemAt backups.main.location.patterns 2
}"
expectations[patterns[3]]="${
builtins.elemAt backups.main.location.patterns 3
}"
yq=${pkgs.yq-go}/bin/yq
for filter in "''${!expectations[@]}"; do
expected_value="''${expectations[$filter]}"
actual_value="$($yq ".$filter" $config_file)"
if [[ "$actual_value" != "$expected_value" ]]; then
fail "Expected '$filter' to be '$expected_value' but was '$actual_value'"
fi
done
'';
}