diff --git a/modules/programs/borgmatic.nix b/modules/programs/borgmatic.nix index a8817c5f..75bb2b8e 100644 --- a/modules/programs/borgmatic.nix +++ b/modules/programs/borgmatic.nix @@ -43,7 +43,10 @@ let }; }; - configModule = types.submodule { + configModule = types.submodule ({ config, ... }: { + config.location.extraConfig.exclude_from = + mkIf config.location.excludeHomeManagerSymlinks + (mkAfter [ (toString hmExcludeFile) ]); options = { location = { sourceDirectories = mkOption { @@ -59,6 +62,18 @@ let literalExpression ''["ssh://myuser@myrepo.myserver.com/./repo"]''; }; + excludeHomeManagerSymlinks = mkOption { + type = types.bool; + description = '' + Whether to exclude Home Manager generated symbolic links from + the backups. This facilitates restoring the whole home + directory when the Nix store doesn't contain the latest + Home Manager generation. + ''; + default = false; + example = true; + }; + extraConfig = extraConfigOption; }; @@ -120,10 +135,18 @@ let extraConfig = extraConfigOption; }; }; - }; + }); removeNullValues = attrSet: filterAttrs (key: value: value != null) attrSet; + hmFiles = builtins.attrValues config.home.file; + hmSymlinks = (lib.filter (file: !file.recursive) hmFiles); + hmExcludePattern = file: '' + ${config.home.homeDirectory}/${file.target} + ''; + hmExcludePatterns = lib.concatMapStrings hmExcludePattern hmSymlinks; + hmExcludeFile = pkgs.writeText "hm-symlinks.txt" hmExcludePatterns; + writeConfig = config: generators.toYAML { } { location = removeNullValues { diff --git a/tests/modules/programs/borgmatic/default.nix b/tests/modules/programs/borgmatic/default.nix index 721c4823..53ea9be8 100644 --- a/tests/modules/programs/borgmatic/default.nix +++ b/tests/modules/programs/borgmatic/default.nix @@ -1 +1,7 @@ -{ borgmatic-program-basic-configuration = ./basic-configuration.nix; } +{ + borgmatic-program-basic-configuration = ./basic-configuration.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 = + ./exclude-hm-symlinks-nothing-else.nix; +} diff --git a/tests/modules/programs/borgmatic/exclude-hm-symlinks-nothing-else.nix b/tests/modules/programs/borgmatic/exclude-hm-symlinks-nothing-else.nix new file mode 100644 index 00000000..3556cf5a --- /dev/null +++ b/tests/modules/programs/borgmatic/exclude-hm-symlinks-nothing-else.nix @@ -0,0 +1,40 @@ +{ config, pkgs, ... }: + +let + backups = config.programs.borgmatic.backups; + excludeFile = pkgs.writeText "excludeFile.txt" "/foo/bar"; +in { + config = { + programs.borgmatic = { + enable = true; + backups = { + main = { + location = { + sourceDirectories = [ "/my-stuff-to-backup" ]; + repositories = [ "/mnt/disk1" ]; + excludeHomeManagerSymlinks = true; + }; + }; + }; + }; + + test.stubs.borgmatic = { }; + + nmt.script = '' + config_file=$TESTED/home-files/.config/borgmatic.d/main.yaml + assertFileExists $config_file + + yq=${pkgs.yq-go}/bin/yq + + hmExclusionsFile=$($yq '.location.exclude_from[0]' $config_file) + expected_content='/home/hm-user/.config/borgmatic.d/main.yaml' + + grep --quiet "$expected_content" "$hmExclusionsFile" + + if [[ $? -ne 0 ]]; then + echo "Expected to find $expected_content in file $hmExclusionsFile but didn't" >&2 + exit 1 + fi + ''; + }; +} diff --git a/tests/modules/programs/borgmatic/exclude-hm-symlinks.nix b/tests/modules/programs/borgmatic/exclude-hm-symlinks.nix new file mode 100644 index 00000000..ba46e3ff --- /dev/null +++ b/tests/modules/programs/borgmatic/exclude-hm-symlinks.nix @@ -0,0 +1,54 @@ +{ config, pkgs, ... }: + +let + backups = config.programs.borgmatic.backups; + excludeFile = pkgs.writeText "excludeFile.txt" "/foo/bar"; +in { + config = { + programs.borgmatic = { + enable = true; + backups = { + main = { + location = { + sourceDirectories = [ "/my-stuff-to-backup" ]; + repositories = [ "/mnt/disk1" ]; + excludeHomeManagerSymlinks = true; + extraConfig = { exclude_from = [ (toString excludeFile) ]; }; + }; + }; + }; + }; + + test.stubs.borgmatic = { }; + + nmt.script = '' + config_file=$TESTED/home-files/.config/borgmatic.d/main.yaml + assertFileExists $config_file + + declare -A expectations + + expectations[location.exclude_from[0]]="${excludeFile}" + + 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 + + hmExclusionsFile=$($yq '.location.exclude_from[1]' $config_file) + expected_content='/home/hm-user/.config/borgmatic.d/main.yaml' + + grep --quiet "$expected_content" "$hmExclusionsFile" + + if [[ $? -ne 0 ]]; then + echo "Expected to find $expected_content in file $hmExclusionsFile but didn't" >&2 + exit 1 + fi + ''; + }; +} diff --git a/tests/modules/programs/borgmatic/include-hm-symlinks.nix b/tests/modules/programs/borgmatic/include-hm-symlinks.nix new file mode 100644 index 00000000..1d6163bf --- /dev/null +++ b/tests/modules/programs/borgmatic/include-hm-symlinks.nix @@ -0,0 +1,45 @@ +{ config, pkgs, ... }: + +let + backups = config.programs.borgmatic.backups; + excludeFile = pkgs.writeText "excludeFile.txt" "/foo/bar"; +in { + config = { + programs.borgmatic = { + enable = true; + backups = { + main = { + location = { + sourceDirectories = [ "/my-stuff-to-backup" ]; + repositories = [ "/mnt/disk1" ]; + excludeHomeManagerSymlinks = false; + extraConfig = { exclude_from = [ (toString excludeFile) ]; }; + }; + }; + }; + }; + + test.stubs.borgmatic = { }; + + nmt.script = '' + config_file=$TESTED/home-files/.config/borgmatic.d/main.yaml + assertFileExists $config_file + + declare -A expectations + + expectations[location.exclude_from[0]]="${excludeFile}" + + 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 + + ''; + }; +}