Loading nixos/modules/services/backup/borgbackup.nix +46 −13 Original line number Diff line number Diff line Loading @@ -89,19 +89,24 @@ let ${cfg.postInit} fi '' + '' + ( let import-tar = cfg.dumpCommand != null && cfg.dumpCommandProducesTar; in '' ( set -o pipefail ${lib.optionalString (cfg.dumpCommand != null) ''${lib.escapeShellArg cfg.dumpCommand} | \''} borgWrapper create "''${extraArgs[@]}" \ borgWrapper ${if import-tar then "import-tar" else "create"} "''${extraArgs[@]}" \ --compression ${cfg.compression} \ --exclude-from ${mkExcludeFile cfg} \ --patterns-from ${mkPatternsFile cfg} \ ${lib.optionalString (!import-tar) "--exclude-from ${mkExcludeFile cfg}"} \ ${lib.optionalString (!import-tar) "--patterns-from ${mkPatternsFile cfg}"} \ "''${extraCreateArgs[@]}" \ "::$archiveName$archiveSuffix" \ ${if cfg.paths == null then "-" else lib.escapeShellArgs cfg.paths} ) '' ) + lib.optionalString cfg.appendFailedSuffix '' borgWrapper rename "''${extraArgs[@]}" \ "::$archiveName$archiveSuffix" "$archiveName" Loading Loading @@ -310,6 +315,18 @@ let ''; }; mkCreateCommandAssertions = name: cfg: { assertion = (cfg.exclude == [ ] && cfg.patterns == [ ]) || cfg.dumpCommand == null || !cfg.dumpCommandProducesTar; message = '' Options borgbackup.jobs.${name}.exclude and borgbackup.jobs.${name}.patterns have no effect when dumpCommand is specified and dumpCommandProducesTar is true, as they are not supported by "borg import-tar". ''; }; mkRemovableDeviceAssertions = name: cfg: { assertion = !(isLocalPath cfg.repo) -> !cfg.removableDevice; message = '' Loading Loading @@ -398,6 +415,17 @@ in example = "/path/to/createZFSsend.sh"; }; dumpCommandProducesTar = lib.mkOption { type = lib.types.bool; default = false; description = '' Set to `true` to use {command}`borg import-tar` instead of {command}`borg create` when creating an archive. Has no effect when {option}`dumpCommand` is unset. ''; example = true; }; repo = lib.mkOption { type = lib.types.str; description = "Remote or local repository to back up to."; Loading Loading @@ -556,6 +584,8 @@ in description = '' Exclude paths matching any of the given patterns. See {command}`borg help patterns` for pattern syntax. Conflicts with {option}`dumpCommandProducesTar`. ''; default = [ ]; example = [ Loading @@ -571,6 +601,8 @@ in matching patterns is used, so if an include pattern (prefix `+`) matches before an exclude pattern (prefix `-`), the file is backed up. See [{command}`borg help patterns`](https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-patterns) for pattern syntax. Conflicts with {option}`dumpCommandProducesTar`. ''; default = [ ]; example = [ Loading Loading @@ -888,6 +920,7 @@ in lib.mapAttrsToList mkPassAssertion jobs ++ lib.mapAttrsToList mkKeysAssertion repos ++ lib.mapAttrsToList mkSourceAssertions jobs ++ lib.mapAttrsToList mkCreateCommandAssertions jobs ++ lib.mapAttrsToList mkRemovableDeviceAssertions jobs; systemd.tmpfiles.settings = lib.mapAttrs' mkTmpfiles jobs; Loading nixos/tests/borgbackup.nix +29 −1 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ let keepFile = "important_file"; keepFileData = "important_data"; localRepo = "/root/back:up"; localTarRepo = "/root/backup-tar"; # a repository on a file system which is not mounted automatically localRepoMount = "/noAutoMount"; archiveName = "my_archive"; Loading Loading @@ -47,7 +48,7 @@ in nodes = { client = { ... }: { lib, pkgs, ... }: { virtualisation.fileSystems.${localRepoMount} = { device = "tmpfs"; Loading Loading @@ -93,6 +94,20 @@ in startAt = [ ]; }; localTar = { dumpCommand = pkgs.writeScript "createTarArchive" '' ${lib.getExe pkgs.gnutar} cf - ${dataDir} ''; dumpCommandProducesTar = true; repo = localTarRepo; # Make sure in import-tar mode encryption flags are still respected. encryption = { mode = "repokey"; inherit passphrase; }; startAt = [ ]; # Do not run automatically }; remote = { paths = dataDir; repo = remoteRepo; Loading Loading @@ -231,6 +246,19 @@ in # Make sure disabling wrapper works client.fail("command -v borg-job-localMount") with subtest("localTar"): borg = "BORG_PASSPHRASE='${passphrase}' borg" client.systemctl("start --wait borgbackup-job-localTar") client.fail("systemctl is-failed borgbackup-job-localTar") archiveName, = client.succeed("{} list --format '{{archive}}{{NL}}' '${localTarRepo}'".format(borg)).strip().split("\n") # Since excludes are not supported by import-tar, we expect to find exclude file, too client.succeed( "{} list '${localTarRepo}::{}' | grep -qF '${excludeFile}'".format(borg, archiveName) ) # Make sure keepFile has the correct content client.succeed("{} extract '${localTarRepo}::{}'".format(borg, archiveName)) assert "${keepFileData}" in client.succeed("cat ${dataDir}/${keepFile}") with subtest("remote"): borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg" server.wait_for_unit("sshd.service") Loading Loading
nixos/modules/services/backup/borgbackup.nix +46 −13 Original line number Diff line number Diff line Loading @@ -89,19 +89,24 @@ let ${cfg.postInit} fi '' + '' + ( let import-tar = cfg.dumpCommand != null && cfg.dumpCommandProducesTar; in '' ( set -o pipefail ${lib.optionalString (cfg.dumpCommand != null) ''${lib.escapeShellArg cfg.dumpCommand} | \''} borgWrapper create "''${extraArgs[@]}" \ borgWrapper ${if import-tar then "import-tar" else "create"} "''${extraArgs[@]}" \ --compression ${cfg.compression} \ --exclude-from ${mkExcludeFile cfg} \ --patterns-from ${mkPatternsFile cfg} \ ${lib.optionalString (!import-tar) "--exclude-from ${mkExcludeFile cfg}"} \ ${lib.optionalString (!import-tar) "--patterns-from ${mkPatternsFile cfg}"} \ "''${extraCreateArgs[@]}" \ "::$archiveName$archiveSuffix" \ ${if cfg.paths == null then "-" else lib.escapeShellArgs cfg.paths} ) '' ) + lib.optionalString cfg.appendFailedSuffix '' borgWrapper rename "''${extraArgs[@]}" \ "::$archiveName$archiveSuffix" "$archiveName" Loading Loading @@ -310,6 +315,18 @@ let ''; }; mkCreateCommandAssertions = name: cfg: { assertion = (cfg.exclude == [ ] && cfg.patterns == [ ]) || cfg.dumpCommand == null || !cfg.dumpCommandProducesTar; message = '' Options borgbackup.jobs.${name}.exclude and borgbackup.jobs.${name}.patterns have no effect when dumpCommand is specified and dumpCommandProducesTar is true, as they are not supported by "borg import-tar". ''; }; mkRemovableDeviceAssertions = name: cfg: { assertion = !(isLocalPath cfg.repo) -> !cfg.removableDevice; message = '' Loading Loading @@ -398,6 +415,17 @@ in example = "/path/to/createZFSsend.sh"; }; dumpCommandProducesTar = lib.mkOption { type = lib.types.bool; default = false; description = '' Set to `true` to use {command}`borg import-tar` instead of {command}`borg create` when creating an archive. Has no effect when {option}`dumpCommand` is unset. ''; example = true; }; repo = lib.mkOption { type = lib.types.str; description = "Remote or local repository to back up to."; Loading Loading @@ -556,6 +584,8 @@ in description = '' Exclude paths matching any of the given patterns. See {command}`borg help patterns` for pattern syntax. Conflicts with {option}`dumpCommandProducesTar`. ''; default = [ ]; example = [ Loading @@ -571,6 +601,8 @@ in matching patterns is used, so if an include pattern (prefix `+`) matches before an exclude pattern (prefix `-`), the file is backed up. See [{command}`borg help patterns`](https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-patterns) for pattern syntax. Conflicts with {option}`dumpCommandProducesTar`. ''; default = [ ]; example = [ Loading Loading @@ -888,6 +920,7 @@ in lib.mapAttrsToList mkPassAssertion jobs ++ lib.mapAttrsToList mkKeysAssertion repos ++ lib.mapAttrsToList mkSourceAssertions jobs ++ lib.mapAttrsToList mkCreateCommandAssertions jobs ++ lib.mapAttrsToList mkRemovableDeviceAssertions jobs; systemd.tmpfiles.settings = lib.mapAttrs' mkTmpfiles jobs; Loading
nixos/tests/borgbackup.nix +29 −1 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ let keepFile = "important_file"; keepFileData = "important_data"; localRepo = "/root/back:up"; localTarRepo = "/root/backup-tar"; # a repository on a file system which is not mounted automatically localRepoMount = "/noAutoMount"; archiveName = "my_archive"; Loading Loading @@ -47,7 +48,7 @@ in nodes = { client = { ... }: { lib, pkgs, ... }: { virtualisation.fileSystems.${localRepoMount} = { device = "tmpfs"; Loading Loading @@ -93,6 +94,20 @@ in startAt = [ ]; }; localTar = { dumpCommand = pkgs.writeScript "createTarArchive" '' ${lib.getExe pkgs.gnutar} cf - ${dataDir} ''; dumpCommandProducesTar = true; repo = localTarRepo; # Make sure in import-tar mode encryption flags are still respected. encryption = { mode = "repokey"; inherit passphrase; }; startAt = [ ]; # Do not run automatically }; remote = { paths = dataDir; repo = remoteRepo; Loading Loading @@ -231,6 +246,19 @@ in # Make sure disabling wrapper works client.fail("command -v borg-job-localMount") with subtest("localTar"): borg = "BORG_PASSPHRASE='${passphrase}' borg" client.systemctl("start --wait borgbackup-job-localTar") client.fail("systemctl is-failed borgbackup-job-localTar") archiveName, = client.succeed("{} list --format '{{archive}}{{NL}}' '${localTarRepo}'".format(borg)).strip().split("\n") # Since excludes are not supported by import-tar, we expect to find exclude file, too client.succeed( "{} list '${localTarRepo}::{}' | grep -qF '${excludeFile}'".format(borg, archiveName) ) # Make sure keepFile has the correct content client.succeed("{} extract '${localTarRepo}::{}'".format(borg, archiveName)) assert "${keepFileData}" in client.succeed("cat ${dataDir}/${keepFile}") with subtest("remote"): borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg" server.wait_for_unit("sshd.service") Loading