nixos/borgbackup: allow dump scripts as stdin inputs

borg is able to process stdin during backups when backing up the special path -,
which can be very useful for backing up things that can be streamed (eg database
dumps, zfs snapshots).
This commit is contained in:
pennae 2021-10-11 15:28:56 +02:00 committed by tomberek
parent c47fcb70c6
commit 1fa5e13f30
2 changed files with 68 additions and 8 deletions

View file

@ -42,12 +42,16 @@ let
${cfg.postInit}
fi
'' + ''
borg create $extraArgs \
--compression ${cfg.compression} \
--exclude-from ${mkExcludeFile cfg} \
$extraCreateArgs \
"::$archiveName$archiveSuffix" \
${escapeShellArgs cfg.paths}
(
set -o pipefail
${optionalString (cfg.dumpCommand != null) ''${escapeShellArg cfg.dumpCommand} | \''}
borg create $extraArgs \
--compression ${cfg.compression} \
--exclude-from ${mkExcludeFile cfg} \
$extraCreateArgs \
"::$archiveName$archiveSuffix" \
${if cfg.paths == null then "-" else escapeShellArgs cfg.paths}
)
'' + optionalString cfg.appendFailedSuffix ''
borg rename $extraArgs \
"::$archiveName$archiveSuffix" "$archiveName"
@ -182,6 +186,14 @@ let
+ " without at least one public key";
};
mkSourceAssertions = name: cfg: {
assertion = count isNull [ cfg.dumpCommand cfg.paths ] == 1;
message = ''
Exactly one of borgbackup.jobs.${name}.paths or borgbackup.jobs.${name}.dumpCommand
must be set.
'';
};
mkRemovableDeviceAssertions = name: cfg: {
assertion = !(isLocalPath cfg.repo) -> !cfg.removableDevice;
message = ''
@ -240,11 +252,25 @@ in {
options = {
paths = mkOption {
type = with types; coercedTo str lib.singleton (listOf str);
description = "Path(s) to back up.";
type = with types; nullOr (coercedTo str lib.singleton (listOf str));
default = null;
description = ''
Path(s) to back up.
Mutually exclusive with <option>dumpCommand</option>.
'';
example = "/home/user";
};
dumpCommand = mkOption {
type = with types; nullOr path;
default = null;
description = ''
Backup the stdout of this program instead of filesystem paths.
Mutually exclusive with <option>paths</option>.
'';
example = "/path/to/createZFSsend.sh";
};
repo = mkOption {
type = types.str;
description = "Remote or local repository to back up to.";
@ -657,6 +683,7 @@ in {
assertions =
mapAttrsToList mkPassAssertion jobs
++ mapAttrsToList mkKeysAssertion repos
++ mapAttrsToList mkSourceAssertions jobs
++ mapAttrsToList mkRemovableDeviceAssertions jobs;
system.activationScripts = mapAttrs' mkActivationScript jobs;

View file

@ -81,6 +81,24 @@ in {
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly";
};
commandSuccess = {
dumpCommand = pkgs.writeScript "commandSuccess" ''
echo -n test
'';
repo = remoteRepo;
encryption.mode = "none";
startAt = [ ];
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
};
commandFail = {
dumpCommand = "${pkgs.coreutils}/bin/false";
repo = remoteRepo;
encryption.mode = "none";
startAt = [ ];
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
};
};
};
@ -171,5 +189,20 @@ in {
client.fail("{} list borg\@server:wrong".format(borg))
# TODO: Make sure that data is not actually deleted
with subtest("commandSuccess"):
server.wait_for_unit("sshd.service")
client.wait_for_unit("network.target")
client.systemctl("start --wait borgbackup-job-commandSuccess")
client.fail("systemctl is-failed borgbackup-job-commandSuccess")
id = client.succeed("borg-job-commandSuccess list | tail -n1 | cut -d' ' -f1").strip()
client.succeed(f"borg-job-commandSuccess extract ::{id} stdin")
assert "test" == client.succeed("cat stdin")
with subtest("commandFail"):
server.wait_for_unit("sshd.service")
client.wait_for_unit("network.target")
client.systemctl("start --wait borgbackup-job-commandFail")
client.succeed("systemctl is-failed borgbackup-job-commandFail")
'';
})