diff --git a/nixos/modules/services/backup/btrbk.nix b/nixos/modules/services/backup/btrbk.nix
index 0c00b9344050..e17761ffc3cb 100644
--- a/nixos/modules/services/backup/btrbk.nix
+++ b/nixos/modules/services/backup/btrbk.nix
@@ -1,18 +1,36 @@
{ config, pkgs, lib, ... }:
let
+ inherit (lib)
+ concatMapStringsSep
+ concatStringsSep
+ filterAttrs
+ flatten
+ isAttrs
+ isString
+ literalExpression
+ mapAttrs'
+ mapAttrsToList
+ mkIf
+ mkOption
+ optionalString
+ partition
+ typeOf
+ types
+ ;
+
cfg = config.services.btrbk;
sshEnabled = cfg.sshAccess != [ ];
serviceEnabled = cfg.instances != { };
attr2Lines = attr:
let
- pairs = lib.attrsets.mapAttrsToList (name: value: { inherit name value; }) attr;
+ pairs = mapAttrsToList (name: value: { inherit name value; }) attr;
isSubsection = value:
- if builtins.isAttrs value then true
- else if builtins.isString value then false
- else throw "invalid type in btrbk config ${builtins.typeOf value}";
- sortedPairs = lib.lists.partition (x: isSubsection x.value) pairs;
+ if isAttrs value then true
+ else if isString value then false
+ else throw "invalid type in btrbk config ${typeOf value}";
+ sortedPairs = partition (x: isSubsection x.value) pairs;
in
- lib.flatten (
+ flatten (
# non subsections go first
(
map (pair: [ "${pair.name} ${pair.value}" ]) sortedPairs.wrong
@@ -22,7 +40,7 @@ let
map
(
pair:
- lib.mapAttrsToList
+ mapAttrsToList
(
childname: value:
[ "${pair.name} ${childname}" ] ++ (map (x: " " + x) (attr2Lines value))
@@ -34,7 +52,7 @@ let
)
;
addDefaults = settings: { backend = "btrfs-progs-sudo"; } // settings;
- mkConfigFile = settings: lib.concatStringsSep "\n" (attr2Lines (addDefaults settings));
+ mkConfigFile = settings: concatStringsSep "\n" (attr2Lines (addDefaults settings));
mkTestedConfigFile = name: settings:
let
configFile = pkgs.writeText "btrbk-${name}.conf" (mkConfigFile settings);
@@ -51,37 +69,42 @@ let
'';
in
{
+ meta.maintainers = with lib.maintainers; [ oxalica ];
+
options = {
services.btrbk = {
- extraPackages = lib.mkOption {
+ extraPackages = mkOption {
description = "Extra packages for btrbk, like compression utilities for stream_compress";
- type = lib.types.listOf lib.types.package;
+ type = types.listOf types.package;
default = [ ];
- example = lib.literalExpression "[ pkgs.xz ]";
+ example = literalExpression "[ pkgs.xz ]";
};
- niceness = lib.mkOption {
+ niceness = mkOption {
description = "Niceness for local instances of btrbk. Also applies to remote ones connecting via ssh when positive.";
- type = lib.types.ints.between (-20) 19;
+ type = types.ints.between (-20) 19;
default = 10;
};
- ioSchedulingClass = lib.mkOption {
+ ioSchedulingClass = mkOption {
description = "IO scheduling class for btrbk (see ionice(1) for a quick description). Applies to local instances, and remote ones connecting by ssh if set to idle.";
- type = lib.types.enum [ "idle" "best-effort" "realtime" ];
+ type = types.enum [ "idle" "best-effort" "realtime" ];
default = "best-effort";
};
- instances = lib.mkOption {
+ instances = mkOption {
description = "Set of btrbk instances. The instance named btrbk is the default one.";
- type = with lib.types;
+ type = with types;
attrsOf (
submodule {
options = {
- onCalendar = lib.mkOption {
- type = lib.types.str;
+ onCalendar = mkOption {
+ type = types.nullOr types.str;
default = "daily";
- description = "How often this btrbk instance is started. See systemd.time(7) for more information about the format.";
+ description = ''
+ How often this btrbk instance is started. See systemd.time(7) for more information about the format.
+ Setting it to null disables the timer, thus this instance can only be started manually.
+ '';
};
- settings = lib.mkOption {
- type = let t = lib.types.attrsOf (lib.types.either lib.types.str (t // { description = "instances of this type recursively"; })); in t;
+ settings = mkOption {
+ type = let t = types.attrsOf (types.either types.str (t // { description = "instances of this type recursively"; })); in t;
default = { };
example = {
snapshot_preserve_min = "2d";
@@ -103,16 +126,16 @@ in
);
default = { };
};
- sshAccess = lib.mkOption {
+ sshAccess = mkOption {
description = "SSH keys that should be able to make or push snapshots on this system remotely with btrbk";
- type = with lib.types; listOf (
+ type = with types; listOf (
submodule {
options = {
- key = lib.mkOption {
+ key = mkOption {
type = str;
description = "SSH public key allowed to login as user btrbk to run remote backups.";
};
- roles = lib.mkOption {
+ roles = mkOption {
type = listOf (enum [ "info" "source" "target" "delete" "snapshot" "send" "receive" ]);
example = [ "source" "info" "send" ];
description = "What actions can be performed with this SSH key. See ssh_filter_btrbk(1) for details";
@@ -125,7 +148,7 @@ in
};
};
- config = lib.mkIf (sshEnabled || serviceEnabled) {
+ config = mkIf (sshEnabled || serviceEnabled) {
environment.systemPackages = [ pkgs.btrbk ] ++ cfg.extraPackages;
security.sudo.extraRules = [
{
@@ -152,14 +175,14 @@ in
(
v:
let
- options = lib.concatMapStringsSep " " (x: "--" + x) v.roles;
+ options = concatMapStringsSep " " (x: "--" + x) v.roles;
ioniceClass = {
"idle" = 3;
"best-effort" = 2;
"realtime" = 1;
}.${cfg.ioSchedulingClass};
in
- ''command="${pkgs.util-linux}/bin/ionice -t -c ${toString ioniceClass} ${lib.optionalString (cfg.niceness >= 1) "${pkgs.coreutils}/bin/nice -n ${toString cfg.niceness}"} ${pkgs.btrbk}/share/btrbk/scripts/ssh_filter_btrbk.sh --sudo ${options}" ${v.key}''
+ ''command="${pkgs.util-linux}/bin/ionice -t -c ${toString ioniceClass} ${optionalString (cfg.niceness >= 1) "${pkgs.coreutils}/bin/nice -n ${toString cfg.niceness}"} ${pkgs.btrbk}/share/btrbk/scripts/ssh_filter_btrbk.sh --sudo ${options}" ${v.key}''
)
cfg.sshAccess;
};
@@ -169,7 +192,7 @@ in
"d /var/lib/btrbk/.ssh 0700 btrbk btrbk"
"f /var/lib/btrbk/.ssh/config 0700 btrbk btrbk - StrictHostKeyChecking=accept-new"
];
- environment.etc = lib.mapAttrs'
+ environment.etc = mapAttrs'
(
name: instance: {
name = "btrbk/${name}.conf";
@@ -177,7 +200,7 @@ in
}
)
cfg.instances;
- systemd.services = lib.mapAttrs'
+ systemd.services = mapAttrs'
(
name: _: {
name = "btrbk-${name}";
@@ -199,7 +222,7 @@ in
)
cfg.instances;
- systemd.timers = lib.mapAttrs'
+ systemd.timers = mapAttrs'
(
name: instance: {
name = "btrbk-${name}";
@@ -214,7 +237,8 @@ in
};
}
)
- cfg.instances;
+ (filterAttrs (name: instance: instance.onCalendar != null)
+ cfg.instances);
};
}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index e86dda9cb3d2..84433806b48c 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -62,6 +62,7 @@ in
breitbandmessung = handleTest ./breitbandmessung.nix {};
brscan5 = handleTest ./brscan5.nix {};
btrbk = handleTest ./btrbk.nix {};
+ btrbk-no-timer = handleTest ./btrbk-no-timer.nix {};
buildbot = handleTest ./buildbot.nix {};
buildkite-agents = handleTest ./buildkite-agents.nix {};
caddy = handleTest ./caddy.nix {};
diff --git a/nixos/tests/btrbk-no-timer.nix b/nixos/tests/btrbk-no-timer.nix
new file mode 100644
index 000000000000..4fcab8839c89
--- /dev/null
+++ b/nixos/tests/btrbk-no-timer.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+ {
+ name = "btrbk-no-timer";
+ meta.maintainers = with lib.maintainers; [ oxalica ];
+
+ nodes.machine = { ... }: {
+ environment.systemPackages = with pkgs; [ btrfs-progs ];
+ services.btrbk.instances.local = {
+ onCalendar = null;
+ settings.volume."/mnt" = {
+ snapshot_dir = "btrbk/local";
+ subvolume = "to_backup";
+ };
+ };
+ };
+
+ testScript = ''
+ start_all()
+
+ # Create btrfs partition at /mnt
+ machine.succeed("truncate --size=128M /data_fs")
+ machine.succeed("mkfs.btrfs /data_fs")
+ machine.succeed("mkdir /mnt")
+ machine.succeed("mount /data_fs /mnt")
+ machine.succeed("btrfs subvolume create /mnt/to_backup")
+ machine.succeed("mkdir -p /mnt/btrbk/local")
+
+ # The service should not have any triggering timer.
+ unit = machine.get_unit_info('btrbk-local.service')
+ assert "TriggeredBy" not in unit
+
+ # Manually starting the service should still work.
+ machine.succeed("echo foo > /mnt/to_backup/bar")
+ machine.start_job("btrbk-local.service")
+ machine.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
+ '';
+ })