inherit functions in modules
This commit is contained in:
parent
455e0bd94c
commit
78072504ab
2 changed files with 86 additions and 78 deletions
|
|
@ -5,43 +5,47 @@ let
|
||||||
|
|
||||||
shblib = pkgs.callPackage ../../lib {};
|
shblib = pkgs.callPackage ../../lib {};
|
||||||
|
|
||||||
instanceOptions = {
|
inherit (lib) concatStringsSep filterAttrs flatten literalExpression optionals optionalString listToAttrs mapAttrsToList mkEnableOption mkOption mkMerge;
|
||||||
enable = lib.mkEnableOption "shb restic. A disabled instance will not backup data anymore but still provides the helper tool to introspect and rollback snapshots";
|
inherit (lib) generators hasPrefix mkIf nameValuePair optionalAttrs removePrefix;
|
||||||
|
inherit (lib.types) attrsOf enum int ints listOf oneOf nonEmptyListOf nonEmptyStr nullOr path str submodule;
|
||||||
|
|
||||||
passphraseFile = lib.mkOption {
|
instanceOptions = {
|
||||||
|
enable = mkEnableOption "shb restic. A disabled instance will not backup data anymore but still provides the helper tool to introspect and rollback snapshots";
|
||||||
|
|
||||||
|
passphraseFile = mkOption {
|
||||||
description = "Encryption key for the backups.";
|
description = "Encryption key for the backups.";
|
||||||
type = lib.types.path;
|
type = path;
|
||||||
};
|
};
|
||||||
|
|
||||||
user = lib.mkOption {
|
user = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Unix user doing the backups. Must be the user owning the files to be backed up.
|
Unix user doing the backups. Must be the user owning the files to be backed up.
|
||||||
'';
|
'';
|
||||||
type = lib.types.str;
|
type = str;
|
||||||
};
|
};
|
||||||
|
|
||||||
sourceDirectories = lib.mkOption {
|
sourceDirectories = mkOption {
|
||||||
description = "Source directories.";
|
description = "Source directories.";
|
||||||
type = lib.types.nonEmptyListOf lib.types.str;
|
type = nonEmptyListOf str;
|
||||||
};
|
};
|
||||||
|
|
||||||
excludePatterns = lib.mkOption {
|
excludePatterns = mkOption {
|
||||||
description = "Exclude patterns.";
|
description = "Exclude patterns.";
|
||||||
type = lib.types.listOf lib.types.str;
|
type = listOf str;
|
||||||
default = [];
|
default = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
repositories = lib.mkOption {
|
repositories = mkOption {
|
||||||
description = "Repositories to back this instance to.";
|
description = "Repositories to back this instance to.";
|
||||||
type = lib.types.nonEmptyListOf (lib.types.submodule {
|
type = nonEmptyListOf (submodule {
|
||||||
options = {
|
options = {
|
||||||
path = lib.mkOption {
|
path = mkOption {
|
||||||
type = lib.types.str;
|
type = str;
|
||||||
description = "Repository location";
|
description = "Repository location";
|
||||||
};
|
};
|
||||||
|
|
||||||
secrets = lib.mkOption {
|
secrets = mkOption {
|
||||||
type = lib.types.attrsOf shblib.secretFileType;
|
type = attrsOf shblib.secretFileType;
|
||||||
default = {};
|
default = {};
|
||||||
description = ''
|
description = ''
|
||||||
Secrets needed to access the repository where the backups will be stored.
|
Secrets needed to access the repository where the backups will be stored.
|
||||||
|
|
@ -50,7 +54,7 @@ let
|
||||||
and [list](https://restic.readthedocs.io/en/latest/040_backup.html#environment-variables) for the list of all secrets.
|
and [list](https://restic.readthedocs.io/en/latest/040_backup.html#environment-variables) for the list of all secrets.
|
||||||
|
|
||||||
'';
|
'';
|
||||||
example = lib.literalExpression ''
|
example = literalExpression ''
|
||||||
{
|
{
|
||||||
AWS_ACCESS_KEY_ID = <path/to/secret>;
|
AWS_ACCESS_KEY_ID = <path/to/secret>;
|
||||||
AWS_SECRET_ACCESS_KEY = <path/to/secret>;
|
AWS_SECRET_ACCESS_KEY = <path/to/secret>;
|
||||||
|
|
@ -58,8 +62,8 @@ let
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
timerConfig = lib.mkOption {
|
timerConfig = mkOption {
|
||||||
type = lib.types.attrsOf utils.systemdUtils.unitOptions.unitOption;
|
type = attrsOf utils.systemdUtils.unitOptions.unitOption;
|
||||||
default = {
|
default = {
|
||||||
OnCalendar = "daily";
|
OnCalendar = "daily";
|
||||||
Persistent = true;
|
Persistent = true;
|
||||||
|
|
@ -75,9 +79,9 @@ let
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
retention = lib.mkOption {
|
retention = mkOption {
|
||||||
description = "For how long to keep backup files.";
|
description = "For how long to keep backup files.";
|
||||||
type = lib.types.attrsOf (lib.types.oneOf [ lib.types.int lib.types.nonEmptyStr ]);
|
type = attrsOf (oneOf [ int nonEmptyStr ]);
|
||||||
default = {
|
default = {
|
||||||
keep_within = "1d";
|
keep_within = "1d";
|
||||||
keep_hourly = 24;
|
keep_hourly = 24;
|
||||||
|
|
@ -87,73 +91,73 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
hooks = lib.mkOption {
|
hooks = mkOption {
|
||||||
description = "Hooks to run before or after the backup.";
|
description = "Hooks to run before or after the backup.";
|
||||||
default = {};
|
default = {};
|
||||||
type = lib.types.submodule {
|
type = submodule {
|
||||||
options = {
|
options = {
|
||||||
before_backup = lib.mkOption {
|
before_backup = mkOption {
|
||||||
description = "Hooks to run before backup";
|
description = "Hooks to run before backup";
|
||||||
type = lib.types.listOf lib.types.str;
|
type = listOf str;
|
||||||
default = [];
|
default = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
after_backup = lib.mkOption {
|
after_backup = mkOption {
|
||||||
description = "Hooks to run after backup";
|
description = "Hooks to run after backup";
|
||||||
type = lib.types.listOf lib.types.str;
|
type = listOf str;
|
||||||
default = [];
|
default = [];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
limitUploadKiBs = lib.mkOption {
|
limitUploadKiBs = mkOption {
|
||||||
type = lib.types.nullOr lib.types.int;
|
type = nullOr int;
|
||||||
description = "Limit upload bandwidth to the given KiB/s amount.";
|
description = "Limit upload bandwidth to the given KiB/s amount.";
|
||||||
default = null;
|
default = null;
|
||||||
example = 8000;
|
example = 8000;
|
||||||
};
|
};
|
||||||
|
|
||||||
limitDownloadKiBs = lib.mkOption {
|
limitDownloadKiBs = mkOption {
|
||||||
type = lib.types.nullOr lib.types.int;
|
type = nullOr int;
|
||||||
description = "Limit download bandwidth to the given KiB/s amount.";
|
description = "Limit download bandwidth to the given KiB/s amount.";
|
||||||
default = null;
|
default = null;
|
||||||
example = 8000;
|
example = 8000;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
repoSlugName = name: builtins.replaceStrings ["/" ":"] ["_" "_"] (lib.strings.removePrefix "/" name);
|
repoSlugName = name: builtins.replaceStrings ["/" ":"] ["_" "_"] (removePrefix "/" name);
|
||||||
backupName = name: repository: "${name}_${repoSlugName repository.path}";
|
backupName = name: repository: "${name}_${repoSlugName repository.path}";
|
||||||
fullName = name: repository: "restic-backups-${name}_${repoSlugName repository.path}";
|
fullName = name: repository: "restic-backups-${name}_${repoSlugName repository.path}";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.shb.restic = {
|
options.shb.restic = {
|
||||||
instances = lib.mkOption {
|
instances = mkOption {
|
||||||
description = "Each instance is a backup setting";
|
description = "Each instance is a backup setting";
|
||||||
default = {};
|
default = {};
|
||||||
type = lib.types.attrsOf (lib.types.submodule {
|
type = attrsOf (submodule {
|
||||||
options = instanceOptions;
|
options = instanceOptions;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
# Taken from https://github.com/HubbeKing/restic-kubernetes/blob/73bfbdb0ba76939a4c52173fa2dbd52070710008/README.md?plain=1#L23
|
# Taken from https://github.com/HubbeKing/restic-kubernetes/blob/73bfbdb0ba76939a4c52173fa2dbd52070710008/README.md?plain=1#L23
|
||||||
performance = lib.mkOption {
|
performance = mkOption {
|
||||||
description = "Reduce performance impact of backup jobs.";
|
description = "Reduce performance impact of backup jobs.";
|
||||||
default = {};
|
default = {};
|
||||||
type = lib.types.submodule {
|
type = submodule {
|
||||||
options = {
|
options = {
|
||||||
niceness = lib.mkOption {
|
niceness = mkOption {
|
||||||
type = lib.types.ints.between (-20) 19;
|
type = ints.between (-20) 19;
|
||||||
description = "nice priority adjustment, defaults to 15 for ~20% CPU time of normal-priority process";
|
description = "nice priority adjustment, defaults to 15 for ~20% CPU time of normal-priority process";
|
||||||
default = 15;
|
default = 15;
|
||||||
};
|
};
|
||||||
ioSchedulingClass = lib.mkOption {
|
ioSchedulingClass = mkOption {
|
||||||
type = lib.types.enum [ "idle" "best-effort" "realtime" ];
|
type = enum [ "idle" "best-effort" "realtime" ];
|
||||||
description = "ionice scheduling class, defaults to best-effort IO. Only used for `restic backup`, `restic forget` and `restic check` commands.";
|
description = "ionice scheduling class, defaults to best-effort IO. Only used for `restic backup`, `restic forget` and `restic check` commands.";
|
||||||
default = "best-effort";
|
default = "best-effort";
|
||||||
};
|
};
|
||||||
ioPriority = lib.mkOption {
|
ioPriority = mkOption {
|
||||||
type = lib.types.nullOr (lib.types.ints.between 0 7);
|
type = nullOr (ints.between 0 7);
|
||||||
description = "ionice priority, defaults to 7 for lowest priority IO. Only used for `restic backup`, `restic forget` and `restic check` commands.";
|
description = "ionice priority, defaults to 7 for lowest priority IO. Only used for `restic backup`, `restic forget` and `restic check` commands.";
|
||||||
default = 7;
|
default = 7;
|
||||||
};
|
};
|
||||||
|
|
@ -162,22 +166,22 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf (cfg.instances != {}) (
|
config = mkIf (cfg.instances != {}) (
|
||||||
let
|
let
|
||||||
enabledInstances = lib.attrsets.filterAttrs (k: i: i.enable) cfg.instances;
|
enabledInstances = filterAttrs (k: i: i.enable) cfg.instances;
|
||||||
in lib.mkMerge [
|
in mkMerge [
|
||||||
{
|
{
|
||||||
environment.systemPackages = lib.optionals (enabledInstances != {}) [ pkgs.restic ];
|
environment.systemPackages = optionals (enabledInstances != {}) [ pkgs.restic ];
|
||||||
|
|
||||||
systemd.tmpfiles.rules =
|
systemd.tmpfiles.rules =
|
||||||
let
|
let
|
||||||
mkRepositorySettings = name: instance: repository: lib.optionals (lib.hasPrefix "/" repository.path) [
|
mkRepositorySettings = name: instance: repository: optionals (hasPrefix "/" repository.path) [
|
||||||
"d '${repository.path}' 0750 ${instance.user} root - -"
|
"d '${repository.path}' 0750 ${instance.user} root - -"
|
||||||
];
|
];
|
||||||
|
|
||||||
mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories;
|
mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories;
|
||||||
in
|
in
|
||||||
lib.flatten (lib.attrsets.mapAttrsToList mkSettings cfg.instances);
|
flatten (mapAttrsToList mkSettings cfg.instances);
|
||||||
|
|
||||||
services.restic.backups =
|
services.restic.backups =
|
||||||
let
|
let
|
||||||
|
|
@ -195,21 +199,21 @@ in
|
||||||
|
|
||||||
inherit (repository) timerConfig;
|
inherit (repository) timerConfig;
|
||||||
|
|
||||||
pruneOpts = lib.mapAttrsToList (name: value:
|
pruneOpts = mapAttrsToList (name: value:
|
||||||
"--${builtins.replaceStrings ["_"] ["-"] name} ${builtins.toString value}"
|
"--${builtins.replaceStrings ["_"] ["-"] name} ${builtins.toString value}"
|
||||||
) instance.retention;
|
) instance.retention;
|
||||||
|
|
||||||
backupPrepareCommand = lib.strings.concatStringsSep "\n" instance.hooks.before_backup;
|
backupPrepareCommand = concatStringsSep "\n" instance.hooks.before_backup;
|
||||||
|
|
||||||
backupCleanupCommand = lib.strings.concatStringsSep "\n" instance.hooks.after_backup;
|
backupCleanupCommand = concatStringsSep "\n" instance.hooks.after_backup;
|
||||||
} // lib.attrsets.optionalAttrs (builtins.length instance.excludePatterns > 0) {
|
} // optionalAttrs (builtins.length instance.excludePatterns > 0) {
|
||||||
exclude = instance.excludePatterns;
|
exclude = instance.excludePatterns;
|
||||||
|
|
||||||
extraBackupArgs =
|
extraBackupArgs =
|
||||||
(lib.optionals (instance.limitUploadKiBs != null) [
|
(optionals (instance.limitUploadKiBs != null) [
|
||||||
"--limit-upload=${toString instance.limitUploadKiBs}"
|
"--limit-upload=${toString instance.limitUploadKiBs}"
|
||||||
])
|
])
|
||||||
++ (lib.optionals (instance.limitDownloadKiBs != null) [
|
++ (optionals (instance.limitDownloadKiBs != null) [
|
||||||
"--limit-download=${toString instance.limitDownloadKiBs}"
|
"--limit-download=${toString instance.limitDownloadKiBs}"
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
@ -217,7 +221,7 @@ in
|
||||||
|
|
||||||
mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories;
|
mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories;
|
||||||
in
|
in
|
||||||
lib.mkMerge (lib.flatten (lib.attrsets.mapAttrsToList mkSettings enabledInstances));
|
mkMerge (flatten (mapAttrsToList mkSettings enabledInstances));
|
||||||
|
|
||||||
systemd.services =
|
systemd.services =
|
||||||
let
|
let
|
||||||
|
|
@ -226,7 +230,7 @@ in
|
||||||
serviceName = fullName name repository;
|
serviceName = fullName name repository;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
${serviceName} = lib.mkMerge [
|
${serviceName} = mkMerge [
|
||||||
{
|
{
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Nice = cfg.performance.niceness;
|
Nice = cfg.performance.niceness;
|
||||||
|
|
@ -235,7 +239,7 @@ in
|
||||||
BindReadOnlyPaths = instance.sourceDirectories;
|
BindReadOnlyPaths = instance.sourceDirectories;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
(lib.attrsets.optionalAttrs (repository.secrets != {})
|
(optionalAttrs (repository.secrets != {})
|
||||||
{
|
{
|
||||||
serviceConfig.EnvironmentFile = [
|
serviceConfig.EnvironmentFile = [
|
||||||
"/run/secrets_restic/${serviceName}"
|
"/run/secrets_restic/${serviceName}"
|
||||||
|
|
@ -245,12 +249,12 @@ in
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
"${serviceName}-pre" = lib.mkIf (repository.secrets != {})
|
"${serviceName}-pre" = mkIf (repository.secrets != {})
|
||||||
(let
|
(let
|
||||||
script = shblib.genConfigOutOfBandSystemd {
|
script = shblib.genConfigOutOfBandSystemd {
|
||||||
config = repository.secrets;
|
config = repository.secrets;
|
||||||
configLocation = "/run/secrets_restic/${serviceName}";
|
configLocation = "/run/secrets_restic/${serviceName}";
|
||||||
generator = name: v: pkgs.writeText "template" (lib.generators.toINIWithGlobalSection {} { globalSection = v; });
|
generator = name: v: pkgs.writeText "template" (generators.toINIWithGlobalSection {} { globalSection = v; });
|
||||||
user = instance.user;
|
user = instance.user;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|
@ -262,24 +266,24 @@ in
|
||||||
};
|
};
|
||||||
mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories;
|
mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories;
|
||||||
in
|
in
|
||||||
lib.mkMerge (lib.flatten (lib.attrsets.mapAttrsToList mkSettings enabledInstances));
|
mkMerge (flatten (mapAttrsToList mkSettings enabledInstances));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
system.activationScripts = let
|
system.activationScripts = let
|
||||||
mkEnv = name: instance: repository:
|
mkEnv = name: instance: repository:
|
||||||
lib.nameValuePair "${fullName name repository}_gen"
|
nameValuePair "${fullName name repository}_gen"
|
||||||
(shblib.replaceSecrets {
|
(shblib.replaceSecrets {
|
||||||
userConfig = repository.secrets // {
|
userConfig = repository.secrets // {
|
||||||
RESTIC_PASSWORD_FILE = instance.passphraseFile;
|
RESTIC_PASSWORD_FILE = instance.passphraseFile;
|
||||||
RESTIC_REPOSITORY = repository.path;
|
RESTIC_REPOSITORY = repository.path;
|
||||||
};
|
};
|
||||||
resultPath = "/run/secrets_restic_env/${fullName name repository}";
|
resultPath = "/run/secrets_restic_env/${fullName name repository}";
|
||||||
generator = name: v: pkgs.writeText (fullName name repository) (lib.generators.toINIWithGlobalSection {} { globalSection = v; });
|
generator = name: v: pkgs.writeText (fullName name repository) (generators.toINIWithGlobalSection {} { globalSection = v; });
|
||||||
user = instance.user;
|
user = instance.user;
|
||||||
});
|
});
|
||||||
mkSettings = name: instance: builtins.map (mkEnv name instance) instance.repositories;
|
mkSettings = name: instance: builtins.map (mkEnv name instance) instance.repositories;
|
||||||
in
|
in
|
||||||
lib.listToAttrs (lib.flatten (lib.attrsets.mapAttrsToList mkSettings cfg.instances));
|
listToAttrs (flatten (mapAttrsToList mkSettings cfg.instances));
|
||||||
|
|
||||||
environment.systemPackages = let
|
environment.systemPackages = let
|
||||||
mkResticBinary = name: instance: repository:
|
mkResticBinary = name: instance: repository:
|
||||||
|
|
@ -290,7 +294,7 @@ in
|
||||||
'';
|
'';
|
||||||
mkSettings = name: instance: builtins.map (mkResticBinary name instance) instance.repositories;
|
mkSettings = name: instance: builtins.map (mkResticBinary name instance) instance.repositories;
|
||||||
in
|
in
|
||||||
lib.flatten (lib.attrsets.mapAttrsToList mkSettings cfg.instances);
|
flatten (mapAttrsToList mkSettings cfg.instances);
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,42 @@
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
lib.types.submodule {
|
let
|
||||||
freeformType = lib.types.anything;
|
inherit (lib) mkOption;
|
||||||
|
inherit (lib.types) anything listOf nonEmptyListOf nullOr submodule str;
|
||||||
|
in
|
||||||
|
submodule {
|
||||||
|
freeformType = anything;
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
user = lib.mkOption {
|
user = mkOption {
|
||||||
description = "Unix user doing the backups.";
|
description = "Unix user doing the backups.";
|
||||||
type = lib.types.str;
|
type = str;
|
||||||
};
|
};
|
||||||
|
|
||||||
sourceDirectories = lib.mkOption {
|
sourceDirectories = mkOption {
|
||||||
description = "Directories to backup.";
|
description = "Directories to backup.";
|
||||||
type = lib.types.nonEmptyListOf lib.types.str;
|
type = nonEmptyListOf str;
|
||||||
};
|
};
|
||||||
|
|
||||||
excludePatterns = lib.mkOption {
|
excludePatterns = mkOption {
|
||||||
description = "Patterns to exclude.";
|
description = "Patterns to exclude.";
|
||||||
type = lib.types.listOf lib.types.str;
|
type = listOf str;
|
||||||
default = [];
|
default = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
hooks = lib.mkOption {
|
hooks = mkOption {
|
||||||
description = "Hooks to run around the backup.";
|
description = "Hooks to run around the backup.";
|
||||||
default = {};
|
default = {};
|
||||||
type = lib.types.submodule {
|
type = submodule {
|
||||||
options = {
|
options = {
|
||||||
before_backup = lib.mkOption {
|
before_backup = mkOption {
|
||||||
description = "Hooks to run before backup";
|
description = "Hooks to run before backup";
|
||||||
type = lib.types.listOf lib.types.str;
|
type = listOf str;
|
||||||
default = [];
|
default = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
after_backup = lib.mkOption {
|
after_backup = mkOption {
|
||||||
description = "Hooks to run after backup";
|
description = "Hooks to run after backup";
|
||||||
type = lib.types.listOf lib.types.str;
|
type = listOf str;
|
||||||
default = [];
|
default = [];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue