diff --git a/modules/blocks/restic.nix b/modules/blocks/restic.nix index 5b0cbd2..a5b209f 100644 --- a/modules/blocks/restic.nix +++ b/modules/blocks/restic.nix @@ -5,43 +5,47 @@ let shblib = pkgs.callPackage ../../lib {}; - instanceOptions = { - 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) concatStringsSep filterAttrs flatten literalExpression optionals optionalString listToAttrs mapAttrsToList mkEnableOption mkOption mkMerge; + 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."; - type = lib.types.path; + type = path; }; - user = lib.mkOption { + user = mkOption { description = '' 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."; - type = lib.types.nonEmptyListOf lib.types.str; + type = nonEmptyListOf str; }; - excludePatterns = lib.mkOption { + excludePatterns = mkOption { description = "Exclude patterns."; - type = lib.types.listOf lib.types.str; + type = listOf str; default = []; }; - repositories = lib.mkOption { + repositories = mkOption { description = "Repositories to back this instance to."; - type = lib.types.nonEmptyListOf (lib.types.submodule { + type = nonEmptyListOf (submodule { options = { - path = lib.mkOption { - type = lib.types.str; + path = mkOption { + type = str; description = "Repository location"; }; - secrets = lib.mkOption { - type = lib.types.attrsOf shblib.secretFileType; + secrets = mkOption { + type = attrsOf shblib.secretFileType; default = {}; description = '' 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. ''; - example = lib.literalExpression '' + example = literalExpression '' { AWS_ACCESS_KEY_ID = ; AWS_SECRET_ACCESS_KEY = ; @@ -58,8 +62,8 @@ let ''; }; - timerConfig = lib.mkOption { - type = lib.types.attrsOf utils.systemdUtils.unitOptions.unitOption; + timerConfig = mkOption { + type = attrsOf utils.systemdUtils.unitOptions.unitOption; default = { OnCalendar = "daily"; Persistent = true; @@ -75,9 +79,9 @@ let }); }; - retention = lib.mkOption { + retention = mkOption { 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 = { keep_within = "1d"; keep_hourly = 24; @@ -87,73 +91,73 @@ let }; }; - hooks = lib.mkOption { + hooks = mkOption { description = "Hooks to run before or after the backup."; default = {}; - type = lib.types.submodule { + type = submodule { options = { - before_backup = lib.mkOption { + before_backup = mkOption { description = "Hooks to run before backup"; - type = lib.types.listOf lib.types.str; + type = listOf str; default = []; }; - after_backup = lib.mkOption { + after_backup = mkOption { description = "Hooks to run after backup"; - type = lib.types.listOf lib.types.str; + type = listOf str; default = []; }; }; }; }; - limitUploadKiBs = lib.mkOption { - type = lib.types.nullOr lib.types.int; + limitUploadKiBs = mkOption { + type = nullOr int; description = "Limit upload bandwidth to the given KiB/s amount."; default = null; example = 8000; }; - limitDownloadKiBs = lib.mkOption { - type = lib.types.nullOr lib.types.int; + limitDownloadKiBs = mkOption { + type = nullOr int; description = "Limit download bandwidth to the given KiB/s amount."; default = null; example = 8000; }; }; - repoSlugName = name: builtins.replaceStrings ["/" ":"] ["_" "_"] (lib.strings.removePrefix "/" name); + repoSlugName = name: builtins.replaceStrings ["/" ":"] ["_" "_"] (removePrefix "/" name); backupName = name: repository: "${name}_${repoSlugName repository.path}"; fullName = name: repository: "restic-backups-${name}_${repoSlugName repository.path}"; in { options.shb.restic = { - instances = lib.mkOption { + instances = mkOption { description = "Each instance is a backup setting"; default = {}; - type = lib.types.attrsOf (lib.types.submodule { + type = attrsOf (submodule { options = instanceOptions; }); }; # 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."; default = {}; - type = lib.types.submodule { + type = submodule { options = { - niceness = lib.mkOption { - type = lib.types.ints.between (-20) 19; + niceness = mkOption { + type = ints.between (-20) 19; description = "nice priority adjustment, defaults to 15 for ~20% CPU time of normal-priority process"; default = 15; }; - ioSchedulingClass = lib.mkOption { - type = lib.types.enum [ "idle" "best-effort" "realtime" ]; + ioSchedulingClass = mkOption { + 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."; default = "best-effort"; }; - ioPriority = lib.mkOption { - type = lib.types.nullOr (lib.types.ints.between 0 7); + ioPriority = mkOption { + 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."; default = 7; }; @@ -162,22 +166,22 @@ in }; }; - config = lib.mkIf (cfg.instances != {}) ( + config = mkIf (cfg.instances != {}) ( let - enabledInstances = lib.attrsets.filterAttrs (k: i: i.enable) cfg.instances; - in lib.mkMerge [ + enabledInstances = filterAttrs (k: i: i.enable) cfg.instances; + in mkMerge [ { - environment.systemPackages = lib.optionals (enabledInstances != {}) [ pkgs.restic ]; + environment.systemPackages = optionals (enabledInstances != {}) [ pkgs.restic ]; systemd.tmpfiles.rules = 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 - -" ]; mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories; in - lib.flatten (lib.attrsets.mapAttrsToList mkSettings cfg.instances); + flatten (mapAttrsToList mkSettings cfg.instances); services.restic.backups = let @@ -195,21 +199,21 @@ in inherit (repository) timerConfig; - pruneOpts = lib.mapAttrsToList (name: value: + pruneOpts = mapAttrsToList (name: value: "--${builtins.replaceStrings ["_"] ["-"] name} ${builtins.toString value}" ) 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; - } // lib.attrsets.optionalAttrs (builtins.length instance.excludePatterns > 0) { + backupCleanupCommand = concatStringsSep "\n" instance.hooks.after_backup; + } // optionalAttrs (builtins.length instance.excludePatterns > 0) { exclude = instance.excludePatterns; extraBackupArgs = - (lib.optionals (instance.limitUploadKiBs != null) [ + (optionals (instance.limitUploadKiBs != null) [ "--limit-upload=${toString instance.limitUploadKiBs}" ]) - ++ (lib.optionals (instance.limitDownloadKiBs != null) [ + ++ (optionals (instance.limitDownloadKiBs != null) [ "--limit-download=${toString instance.limitDownloadKiBs}" ]); }; @@ -217,7 +221,7 @@ in mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories; in - lib.mkMerge (lib.flatten (lib.attrsets.mapAttrsToList mkSettings enabledInstances)); + mkMerge (flatten (mapAttrsToList mkSettings enabledInstances)); systemd.services = let @@ -226,7 +230,7 @@ in serviceName = fullName name repository; in { - ${serviceName} = lib.mkMerge [ + ${serviceName} = mkMerge [ { serviceConfig = { Nice = cfg.performance.niceness; @@ -235,7 +239,7 @@ in BindReadOnlyPaths = instance.sourceDirectories; }; } - (lib.attrsets.optionalAttrs (repository.secrets != {}) + (optionalAttrs (repository.secrets != {}) { serviceConfig.EnvironmentFile = [ "/run/secrets_restic/${serviceName}" @@ -245,12 +249,12 @@ in }) ]; - "${serviceName}-pre" = lib.mkIf (repository.secrets != {}) + "${serviceName}-pre" = mkIf (repository.secrets != {}) (let script = shblib.genConfigOutOfBandSystemd { config = repository.secrets; 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; }; in @@ -262,24 +266,24 @@ in }; mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories; in - lib.mkMerge (lib.flatten (lib.attrsets.mapAttrsToList mkSettings enabledInstances)); + mkMerge (flatten (mapAttrsToList mkSettings enabledInstances)); } { system.activationScripts = let mkEnv = name: instance: repository: - lib.nameValuePair "${fullName name repository}_gen" + nameValuePair "${fullName name repository}_gen" (shblib.replaceSecrets { userConfig = repository.secrets // { RESTIC_PASSWORD_FILE = instance.passphraseFile; RESTIC_REPOSITORY = repository.path; }; 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; }); mkSettings = name: instance: builtins.map (mkEnv name instance) instance.repositories; in - lib.listToAttrs (lib.flatten (lib.attrsets.mapAttrsToList mkSettings cfg.instances)); + listToAttrs (flatten (mapAttrsToList mkSettings cfg.instances)); environment.systemPackages = let mkResticBinary = name: instance: repository: @@ -290,7 +294,7 @@ in ''; mkSettings = name: instance: builtins.map (mkResticBinary name instance) instance.repositories; in - lib.flatten (lib.attrsets.mapAttrsToList mkSettings cfg.instances); + flatten (mapAttrsToList mkSettings cfg.instances); } ]); } diff --git a/modules/contracts/backup.nix b/modules/contracts/backup.nix index 2cb6d10..cee22ca 100644 --- a/modules/contracts/backup.nix +++ b/modules/contracts/backup.nix @@ -1,38 +1,42 @@ { lib, ... }: -lib.types.submodule { - freeformType = lib.types.anything; +let + inherit (lib) mkOption; + inherit (lib.types) anything listOf nonEmptyListOf nullOr submodule str; +in +submodule { + freeformType = anything; options = { - user = lib.mkOption { + user = mkOption { description = "Unix user doing the backups."; - type = lib.types.str; + type = str; }; - sourceDirectories = lib.mkOption { + sourceDirectories = mkOption { description = "Directories to backup."; - type = lib.types.nonEmptyListOf lib.types.str; + type = nonEmptyListOf str; }; - excludePatterns = lib.mkOption { + excludePatterns = mkOption { description = "Patterns to exclude."; - type = lib.types.listOf lib.types.str; + type = listOf str; default = []; }; - hooks = lib.mkOption { + hooks = mkOption { description = "Hooks to run around the backup."; default = {}; - type = lib.types.submodule { + type = submodule { options = { - before_backup = lib.mkOption { + before_backup = mkOption { description = "Hooks to run before backup"; - type = lib.types.listOf lib.types.str; + type = listOf str; default = []; }; - after_backup = lib.mkOption { + after_backup = mkOption { description = "Hooks to run after backup"; - type = lib.types.listOf lib.types.str; + type = listOf str; default = []; }; };