Loading nixos/modules/services/backup/borgbackup.nix +62 −13 Original line number Diff line number Diff line Loading @@ -89,19 +89,24 @@ let ${cfg.postInit} fi '' + '' + ( let import-tar = cfg.createCommand == "import-tar"; in '' ( set -o pipefail ${lib.optionalString (cfg.dumpCommand != null) ''${lib.escapeShellArg cfg.dumpCommand} | \''} borgWrapper create "''${extraArgs[@]}" \ borgWrapper ${lib.escapeShellArg cfg.createCommand} "''${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,23 @@ let ''; }; mkCreateCommandImportTarDumpCommandAssertion = name: cfg: { assertion = cfg.createCommand != "import-tar" || cfg.dumpCommand != null; message = '' Option borgbackup.jobs.${name}.dumpCommand is required when createCommand is set to "import-tar". ''; }; mkCreateCommandImportTarExclusionsAssertion = name: cfg: { assertion = cfg.createCommand != "import-tar" || (cfg.exclude == [ ] && cfg.patterns == [ ]); message = '' Options borgbackup.jobs.${name}.exclude and borgbackup.jobs.${name}.patterns have no effect when createCommand is set to "import-tar". ''; }; mkRemovableDeviceAssertions = name: cfg: { assertion = !(isLocalPath cfg.repo) -> !cfg.removableDevice; message = '' Loading Loading @@ -377,6 +399,25 @@ in { name, config, ... }: { options = { createCommand = lib.mkOption { type = lib.types.enum [ "create" "import-tar" ]; description = '' Borg command to use for archive creation. The default (`create`) creates a regular Borg archive. Use `import-tar` to instead read a tar archive stream from {option}`dumpCommand` output and import its contents into the repository. `import-tar` can not be used together with {option}`exclude` or {option}`patterns`. ''; default = "create"; example = "import-tar"; }; paths = lib.mkOption { type = with lib.types; nullOr (coercedTo str lib.singleton (listOf str)); Loading Loading @@ -556,6 +597,9 @@ in description = '' Exclude paths matching any of the given patterns. See {command}`borg help patterns` for pattern syntax. Can not be set when {option}`createCommand` is set to `import-tar`. ''; default = [ ]; example = [ Loading @@ -571,6 +615,9 @@ 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. Can not be set when {option}`createCommand` is set to `import-tar`. ''; default = [ ]; example = [ Loading Loading @@ -888,6 +935,8 @@ in lib.mapAttrsToList mkPassAssertion jobs ++ lib.mapAttrsToList mkKeysAssertion repos ++ lib.mapAttrsToList mkSourceAssertions jobs ++ lib.mapAttrsToList mkCreateCommandImportTarDumpCommandAssertion jobs ++ lib.mapAttrsToList mkCreateCommandImportTarExclusionsAssertion 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} ''; createCommand = "import-tar"; 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 +62 −13 Original line number Diff line number Diff line Loading @@ -89,19 +89,24 @@ let ${cfg.postInit} fi '' + '' + ( let import-tar = cfg.createCommand == "import-tar"; in '' ( set -o pipefail ${lib.optionalString (cfg.dumpCommand != null) ''${lib.escapeShellArg cfg.dumpCommand} | \''} borgWrapper create "''${extraArgs[@]}" \ borgWrapper ${lib.escapeShellArg cfg.createCommand} "''${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,23 @@ let ''; }; mkCreateCommandImportTarDumpCommandAssertion = name: cfg: { assertion = cfg.createCommand != "import-tar" || cfg.dumpCommand != null; message = '' Option borgbackup.jobs.${name}.dumpCommand is required when createCommand is set to "import-tar". ''; }; mkCreateCommandImportTarExclusionsAssertion = name: cfg: { assertion = cfg.createCommand != "import-tar" || (cfg.exclude == [ ] && cfg.patterns == [ ]); message = '' Options borgbackup.jobs.${name}.exclude and borgbackup.jobs.${name}.patterns have no effect when createCommand is set to "import-tar". ''; }; mkRemovableDeviceAssertions = name: cfg: { assertion = !(isLocalPath cfg.repo) -> !cfg.removableDevice; message = '' Loading Loading @@ -377,6 +399,25 @@ in { name, config, ... }: { options = { createCommand = lib.mkOption { type = lib.types.enum [ "create" "import-tar" ]; description = '' Borg command to use for archive creation. The default (`create`) creates a regular Borg archive. Use `import-tar` to instead read a tar archive stream from {option}`dumpCommand` output and import its contents into the repository. `import-tar` can not be used together with {option}`exclude` or {option}`patterns`. ''; default = "create"; example = "import-tar"; }; paths = lib.mkOption { type = with lib.types; nullOr (coercedTo str lib.singleton (listOf str)); Loading Loading @@ -556,6 +597,9 @@ in description = '' Exclude paths matching any of the given patterns. See {command}`borg help patterns` for pattern syntax. Can not be set when {option}`createCommand` is set to `import-tar`. ''; default = [ ]; example = [ Loading @@ -571,6 +615,9 @@ 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. Can not be set when {option}`createCommand` is set to `import-tar`. ''; default = [ ]; example = [ Loading Loading @@ -888,6 +935,8 @@ in lib.mapAttrsToList mkPassAssertion jobs ++ lib.mapAttrsToList mkKeysAssertion repos ++ lib.mapAttrsToList mkSourceAssertions jobs ++ lib.mapAttrsToList mkCreateCommandImportTarDumpCommandAssertion jobs ++ lib.mapAttrsToList mkCreateCommandImportTarExclusionsAssertion 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} ''; createCommand = "import-tar"; 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