diff --git a/modules/programs/borgmatic.nix b/modules/programs/borgmatic.nix index 19670775a..ecc6f5cf3 100644 --- a/modules/programs/borgmatic.nix +++ b/modules/programs/borgmatic.nix @@ -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: diff --git a/tests/modules/programs/borgmatic/both-sourcedirectories-and-patterns.nix b/tests/modules/programs/borgmatic/both-sourcedirectories-and-patterns.nix new file mode 100644 index 000000000..106a0c18c --- /dev/null +++ b/tests/modules/programs/borgmatic/both-sourcedirectories-and-patterns.nix @@ -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'. + '']; +} diff --git a/tests/modules/programs/borgmatic/default.nix b/tests/modules/programs/borgmatic/default.nix index 53ea9be89..8cc0f924d 100644 --- a/tests/modules/programs/borgmatic/default.nix +++ b/tests/modules/programs/borgmatic/default.nix @@ -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 = diff --git a/tests/modules/programs/borgmatic/neither-sourcedirectories-nor-patterns.nix b/tests/modules/programs/borgmatic/neither-sourcedirectories-nor-patterns.nix new file mode 100644 index 000000000..596fdf1cd --- /dev/null +++ b/tests/modules/programs/borgmatic/neither-sourcedirectories-nor-patterns.nix @@ -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'. + '']; +} diff --git a/tests/modules/programs/borgmatic/patterns-configuration.nix b/tests/modules/programs/borgmatic/patterns-configuration.nix new file mode 100644 index 000000000..098708cad --- /dev/null +++ b/tests/modules/programs/borgmatic/patterns-configuration.nix @@ -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 + ''; +}