|
|
|
@ -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); |
|
|
|
@ -55,38 +73,38 @@ in |
|
|
|
|
|
|
|
|
|
options = { |
|
|
|
|
services.btrbk = { |
|
|
|
|
extraPackages = lib.mkOption { |
|
|
|
|
extraPackages = mkOption { |
|
|
|
|
description = "Extra packages for btrbk, like compression utilities for <literal>stream_compress</literal>"; |
|
|
|
|
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 <literal>btrbk</literal> is the default one."; |
|
|
|
|
type = with lib.types; |
|
|
|
|
type = with types; |
|
|
|
|
attrsOf ( |
|
|
|
|
submodule { |
|
|
|
|
options = { |
|
|
|
|
onCalendar = lib.mkOption { |
|
|
|
|
type = lib.types.nullOr 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. |
|
|
|
|
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"; |
|
|
|
@ -108,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 <literal>btrbk</literal> 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"; |
|
|
|
@ -130,7 +148,7 @@ in |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
config = lib.mkIf (sshEnabled || serviceEnabled) { |
|
|
|
|
config = mkIf (sshEnabled || serviceEnabled) { |
|
|
|
|
environment.systemPackages = [ pkgs.btrbk ] ++ cfg.extraPackages; |
|
|
|
|
security.sudo.extraRules = [ |
|
|
|
|
{ |
|
|
|
@ -157,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; |
|
|
|
|
}; |
|
|
|
@ -174,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"; |
|
|
|
@ -182,7 +200,7 @@ in |
|
|
|
|
} |
|
|
|
|
) |
|
|
|
|
cfg.instances; |
|
|
|
|
systemd.services = lib.mapAttrs' |
|
|
|
|
systemd.services = mapAttrs' |
|
|
|
|
( |
|
|
|
|
name: _: { |
|
|
|
|
name = "btrbk-${name}"; |
|
|
|
@ -204,7 +222,7 @@ in |
|
|
|
|
) |
|
|
|
|
cfg.instances; |
|
|
|
|
|
|
|
|
|
systemd.timers = lib.mapAttrs' |
|
|
|
|
systemd.timers = mapAttrs' |
|
|
|
|
( |
|
|
|
|
name: instance: { |
|
|
|
|
name = "btrbk-${name}"; |
|
|
|
@ -219,7 +237,7 @@ in |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
) |
|
|
|
|
(lib.filterAttrs (name: instance: instance.onCalendar != null) |
|
|
|
|
(filterAttrs (name: instance: instance.onCalendar != null) |
|
|
|
|
cfg.instances); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|