eae5eade56
I want to show how composable this project is. For example, you could just use the Authelia module to add SSO to any service, not just those provided here.
289 lines
9.9 KiB
Nix
289 lines
9.9 KiB
Nix
{ config, pkgs, lib, ... }:
|
|
|
|
let
|
|
cfg = config.shb.backup;
|
|
|
|
instanceOptions = {
|
|
enable = lib.mkEnableOption "shb backup instance";
|
|
|
|
backend = lib.mkOption {
|
|
description = "What program to use to make the backups.";
|
|
type = lib.types.enum [ "borgmatic" "restic" ];
|
|
example = "borgmatic";
|
|
};
|
|
|
|
keySopsFile = lib.mkOption {
|
|
description = "Sops file that holds this instance's Borgmatic repository key and passphrase.";
|
|
type = lib.types.path;
|
|
example = "secrets/backup.yaml";
|
|
};
|
|
|
|
sourceDirectories = lib.mkOption {
|
|
description = "Borgmatic source directories.";
|
|
type = lib.types.nonEmptyListOf lib.types.str;
|
|
};
|
|
|
|
excludePatterns = lib.mkOption {
|
|
description = "Borgmatic exclude patterns.";
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [];
|
|
};
|
|
|
|
secretName = lib.mkOption {
|
|
description = "Secret name, if null use the name of the backup instance.";
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
};
|
|
|
|
repositories = lib.mkOption {
|
|
description = lib.mdDoc "Repositories to back this instance to.";
|
|
type = lib.types.nonEmptyListOf lib.types.str;
|
|
};
|
|
|
|
retention = lib.mkOption {
|
|
description = "Retention options.";
|
|
type = lib.types.attrsOf (lib.types.oneOf [ lib.types.int lib.types.nonEmptyStr ]);
|
|
default = {
|
|
keep_within = "1d";
|
|
keep_hourly = 24;
|
|
keep_daily = 7;
|
|
keep_weekly = 4;
|
|
keep_monthly = 6;
|
|
};
|
|
};
|
|
|
|
consistency = lib.mkOption {
|
|
description = "Consistency frequency options. Only applicable for borgmatic";
|
|
type = lib.types.attrsOf lib.types.nonEmptyStr;
|
|
default = {};
|
|
example = {
|
|
repository = "2 weeks";
|
|
archives = "1 month";
|
|
};
|
|
};
|
|
|
|
hooks = lib.mkOption {
|
|
description = "Borgmatic hooks.";
|
|
default = {};
|
|
type = lib.types.submodule {
|
|
options = {
|
|
before_backup = lib.mkOption {
|
|
description = "Hooks to run before backup";
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [];
|
|
};
|
|
|
|
after_backup = lib.mkOption {
|
|
description = "Hooks to run after backup";
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
environmentFile = lib.mkOption {
|
|
type = lib.types.bool;
|
|
description = "Add environment file to be read by the systemd service.";
|
|
default = false;
|
|
example = true;
|
|
};
|
|
};
|
|
|
|
repoSlugName = name: builtins.replaceStrings ["/" ":"] ["_" "_"] (lib.strings.removePrefix "/" name);
|
|
|
|
in
|
|
{
|
|
options.shb.backup = {
|
|
onlyOnAC = lib.mkOption {
|
|
description = lib.mdDoc "Run backups only if AC power is plugged in.";
|
|
default = true;
|
|
example = false;
|
|
type = lib.types.bool;
|
|
};
|
|
|
|
user = lib.mkOption {
|
|
description = lib.mdDoc "Unix user doing the backups.";
|
|
type = lib.types.str;
|
|
default = "backup";
|
|
};
|
|
|
|
group = lib.mkOption {
|
|
description = lib.mdDoc "Unix group doing the backups.";
|
|
type = lib.types.str;
|
|
default = "backup";
|
|
};
|
|
|
|
instances = lib.mkOption {
|
|
description = lib.mdDoc "Each instance is a backup setting";
|
|
default = {};
|
|
type = lib.types.attrsOf (lib.types.submodule {
|
|
options = instanceOptions;
|
|
});
|
|
};
|
|
|
|
borgServer = lib.mkOption {
|
|
description = lib.mdDoc "Add borgbackup package so external backups can use this server as a remote.";
|
|
default = false;
|
|
example = true;
|
|
type = lib.types.bool;
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf (cfg.instances != {}) (
|
|
let
|
|
enabledInstances = lib.attrsets.filterAttrs (k: i: i.enable) cfg.instances;
|
|
borgmaticInstances = lib.attrsets.filterAttrs (k: i: i.backend == "borgmatic") enabledInstances;
|
|
resticInstances = lib.attrsets.filterAttrs (k: i: i.backend == "restic") enabledInstances;
|
|
in
|
|
{
|
|
users.users = {
|
|
${cfg.user} = {
|
|
name = cfg.user;
|
|
group = cfg.group;
|
|
home = "/var/lib/backup";
|
|
createHome = true;
|
|
isSystemUser = true;
|
|
extraGroups = [ "keys" ];
|
|
};
|
|
};
|
|
users.groups = {
|
|
${cfg.group} = {
|
|
name = cfg.group;
|
|
};
|
|
};
|
|
|
|
sops.secrets =
|
|
let
|
|
mkSopsSecret = name: instance: (
|
|
[
|
|
{
|
|
"${instance.backend}/passphrases/${if isNull instance.secretName then name else instance.secretName}" = {
|
|
sopsFile = instance.keySopsFile;
|
|
mode = "0440";
|
|
owner = cfg.user;
|
|
group = cfg.group;
|
|
};
|
|
}
|
|
] ++ lib.optional ((lib.filter (lib.strings.hasPrefix "s3") instance.repositories) != []) {
|
|
"${instance.backend}/environmentfiles/${if isNull instance.secretName then name else instance.secretName}" = {
|
|
sopsFile = instance.keySopsFile;
|
|
mode = "0440";
|
|
owner = cfg.user;
|
|
group = cfg.group;
|
|
};
|
|
} ++ lib.optionals (instance.backend == "borgmatic") (lib.flatten (map (repository: {
|
|
"${instance.backend}/keys/${repoSlugName repository}" = {
|
|
key = "${instance.backend}/keys/${if isNull instance.secretName then name else instance.secretName}";
|
|
sopsFile = instance.keySopsFile;
|
|
mode = "0440";
|
|
owner = cfg.user;
|
|
group = cfg.group;
|
|
};
|
|
}) instance.repositories))
|
|
);
|
|
in
|
|
lib.mkMerge (lib.flatten (lib.attrsets.mapAttrsToList mkSopsSecret enabledInstances));
|
|
|
|
systemd.timers.borgmatic = lib.mkIf (borgmaticInstances != {}) {
|
|
timerConfig = {
|
|
OnCalendar = "hourly";
|
|
};
|
|
};
|
|
|
|
systemd.services.borgmatic = lib.mkIf (borgmaticInstances != {}) {
|
|
serviceConfig = {
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
ExecStartPre = [ "" ]; # Do not sleep before starting.
|
|
ExecStart = [ "" "${pkgs.borgmatic}/bin/borgmatic --verbosity -1 --syslog-verbosity 1" ];
|
|
# For borgmatic, since we have only one service, we need to merge all environmentFile
|
|
# from all instances.
|
|
EnvironmentFile = lib.mapAttrsToList (name: value: value.environmentFile) enabledInstances;
|
|
};
|
|
};
|
|
|
|
systemd.packages = lib.mkIf (borgmaticInstances != {}) [ pkgs.borgmatic ];
|
|
environment.systemPackages = (
|
|
lib.optionals cfg.borgServer [ pkgs.borgbackup ]
|
|
++ lib.optionals (borgmaticInstances != {}) [ pkgs.borgbackup pkgs.borgmatic ]
|
|
++ lib.optionals (resticInstances != {}) [ pkgs.restic ]
|
|
);
|
|
|
|
services.restic.backups =
|
|
let
|
|
mkRepositorySettings = name: instance: repository: {
|
|
"${name}_${repoSlugName repository}" = {
|
|
inherit (cfg) user;
|
|
inherit repository;
|
|
|
|
paths = instance.sourceDirectories;
|
|
|
|
passwordFile = "/run/secrets/${instance.backend}/passphrases/${name}";
|
|
|
|
initialize = true;
|
|
|
|
timerConfig = {
|
|
OnCalendar = "00,12:00:00";
|
|
RandomizedDelaySec = "5m";
|
|
};
|
|
|
|
pruneOpts = lib.mapAttrsToList (name: value:
|
|
"--${builtins.replaceStrings ["_"] ["-"] name} ${builtins.toString value}"
|
|
) instance.retention;
|
|
|
|
backupPrepareCommand = lib.strings.concatStringsSep "\n" instance.hooks.before_backup;
|
|
|
|
backupCleanupCommand = lib.strings.concatStringsSep "\n" instance.hooks.after_backup;
|
|
} // lib.attrsets.optionalAttrs (instance.environmentFile) {
|
|
environmentFile = "/run/secrets/${instance.backend}/environmentfiles/${name}";
|
|
} // lib.attrsets.optionalAttrs (builtins.length instance.excludePatterns > 0) {
|
|
exclude = instance.excludePatterns;
|
|
};
|
|
};
|
|
|
|
mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories;
|
|
in
|
|
lib.mkMerge (lib.flatten (lib.attrsets.mapAttrsToList mkSettings resticInstances));
|
|
|
|
environment.etc =
|
|
let
|
|
mkSettings = name: instance: {
|
|
"borgmatic.d/${name}.yaml".text = lib.generators.toYAML {} {
|
|
location =
|
|
{
|
|
source_directories = instance.sourceDirectories;
|
|
repositories = instance.repositories;
|
|
}
|
|
// (lib.attrsets.optionalAttrs (builtins.length instance.excludePatterns > 0) {
|
|
excludePatterns = instance.excludePatterns;
|
|
});
|
|
|
|
storage = {
|
|
encryption_passcommand = "cat /run/secrets/borgmatic/passphrases/${if isNull instance.secretName then name else instance.secretName}";
|
|
borg_keys_directory = "/run/secrets/borgmatic/keys";
|
|
};
|
|
|
|
retention = instance.retention;
|
|
consistency.checks =
|
|
let
|
|
mkCheck = name: frequency: {
|
|
inherit name frequency;
|
|
};
|
|
in
|
|
lib.attrsets.mapAttrsToList mkCheck instance.consistency;
|
|
|
|
# hooks = lib.mkMerge [
|
|
# lib.optionalAttrs (builtins.length instance.hooks.before_backup > 0) {
|
|
# inherit (instance.hooks) before_backup;
|
|
# }
|
|
# lib.optionalAttrs (builtins.length instance.hooks.after_backup > 0) {
|
|
# inherit (instance.hooks) after_backup;
|
|
# }
|
|
# ];
|
|
};
|
|
};
|
|
in
|
|
lib.mkMerge (lib.attrsets.mapAttrsToList mkSettings borgmaticInstances);
|
|
});
|
|
}
|