2023-05-16 12:21:11 +02:00
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
let
|
|
|
|
cfg = config.clerie.backup;
|
|
|
|
|
|
|
|
jobTargetPairs = flatten (
|
|
|
|
mapAttrsToList (jobName: jobOptions:
|
|
|
|
let
|
|
|
|
jobTargets = if jobOptions.targets == null then cfg.targets
|
|
|
|
else
|
|
|
|
filterAttrs (targetName: targetOptions:
|
|
|
|
any (map (jobOptionTarget: jobOptionTarget == targetName) jobOptions.targets)
|
|
|
|
) cfg.targets;
|
|
|
|
in mapAttrsToList (targetName: targetOptions:
|
|
|
|
{
|
|
|
|
inherit jobName jobOptions targetName targetOptions;
|
|
|
|
}
|
|
|
|
) jobTargets
|
|
|
|
) cfg.jobs
|
|
|
|
);
|
|
|
|
|
|
|
|
backupServiceUnits = listToAttrs (map ({jobName, jobOptions, targetName, targetOptions}: let
|
2024-04-28 12:04:29 +02:00
|
|
|
jobPasswordFile = if jobOptions.passwordFile != null then jobOptions.passwordFile else
|
2024-05-10 16:23:41 +02:00
|
|
|
config.sops.secrets."clerie-backup-job-${jobName}".path;
|
2023-05-16 12:21:11 +02:00
|
|
|
repoPath = if jobOptions.repoPath == null then "/${config.networking.hostName}/${jobName}" else jobOptions.repoPath;
|
2024-04-28 12:04:29 +02:00
|
|
|
targetPasswordFile = if targetOptions.passwordFile != null then targetOptions.passwordFile else
|
2024-05-10 16:23:41 +02:00
|
|
|
config.sops.secrets."clerie-backup-target-${targetName}".path;
|
2023-05-16 12:21:11 +02:00
|
|
|
targetUsername = if targetOptions.username == null then config.networking.hostName else targetOptions.username;
|
|
|
|
in
|
|
|
|
nameValuePair "clerie-backup-${jobName}-${targetName}" {
|
|
|
|
requires = [ "network.target" "local-fs.target" ];
|
2024-03-19 18:51:17 +01:00
|
|
|
after = [ "network.target" "local-fs.target" ];
|
2023-05-16 12:21:11 +02:00
|
|
|
path = [ pkgs.restic ];
|
|
|
|
|
|
|
|
serviceConfig = {
|
|
|
|
Type = "oneshot";
|
|
|
|
};
|
|
|
|
|
|
|
|
script = ''
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
export RESTIC_PASSWORD_FILE=${jobPasswordFile}
|
|
|
|
export RESTIC_REPOSITORY="rest:https://${targetUsername}:$(cat ${targetPasswordFile})@${targetOptions.serverName}${repoPath}"
|
2023-06-28 18:09:03 +02:00
|
|
|
export RESTIC_PROGRESS_FPS=0.1
|
2023-07-01 13:08:49 +02:00
|
|
|
export RESTIC_CACHE_DIR=/var/cache/restic
|
2023-05-16 12:21:11 +02:00
|
|
|
|
2024-04-20 12:58:16 +02:00
|
|
|
restic snapshots --latest 1 || restic init
|
2023-05-16 12:21:11 +02:00
|
|
|
|
2023-06-28 18:09:03 +02:00
|
|
|
restic backup ${optionalString (jobOptions.exclude != []) "--exclude-file ${pkgs.writeText "clerie-backup-${jobName}-${targetName}-excludes" (concatStringsSep "\n" jobOptions.exclude)}"} ${escapeShellArgs jobOptions.paths}
|
2023-05-16 12:21:11 +02:00
|
|
|
|
2023-06-28 18:09:03 +02:00
|
|
|
${optionalString (config.clerie.monitoring.enable) ''
|
|
|
|
echo "clerie_backup_last_successful_run_time{backup_job=\"${jobName}\", backup_target=\"${targetName}\"} $(date +%s)" > /var/lib/prometheus-node-exporter/textfiles/clerie-backup-${jobName}-${targetName}.prom
|
|
|
|
''}
|
2023-05-16 12:21:11 +02:00
|
|
|
'';
|
|
|
|
}
|
|
|
|
) jobTargetPairs);
|
|
|
|
|
|
|
|
backupServiceTimers = listToAttrs (map ({jobName, jobOptions, targetName, targetOptions}:
|
|
|
|
nameValuePair "clerie-backup-${jobName}-${targetName}" {
|
|
|
|
wantedBy = [ "timers.target" ];
|
|
|
|
timerConfig = {
|
|
|
|
OnCalendar = "hourly";
|
|
|
|
RandomizedDelaySec = "1h";
|
|
|
|
};
|
2024-03-19 18:51:17 +01:00
|
|
|
requires = [ "network-online.target" ];
|
2023-05-16 12:21:11 +02:00
|
|
|
after = [ "network-online.target" ];
|
|
|
|
}
|
|
|
|
) jobTargetPairs);
|
|
|
|
|
2023-07-04 09:02:44 +02:00
|
|
|
backupCommands = map ({jobName, jobOptions, targetName, targetOptions}: let
|
2024-04-28 12:04:29 +02:00
|
|
|
jobPasswordFile = if jobOptions.passwordFile != null then jobOptions.passwordFile else
|
2024-05-10 16:23:41 +02:00
|
|
|
config.sops.secrets."clerie-backup-job-${jobName}".path;
|
2023-07-04 09:02:44 +02:00
|
|
|
repoPath = if jobOptions.repoPath == null then "/${config.networking.hostName}/${jobName}" else jobOptions.repoPath;
|
2024-04-28 12:04:29 +02:00
|
|
|
targetPasswordFile = if targetOptions.passwordFile != null then targetOptions.passwordFile else
|
2024-05-10 16:23:41 +02:00
|
|
|
config.sops.secrets."clerie-backup-target-${targetName}".path;
|
2023-07-04 09:02:44 +02:00
|
|
|
targetUsername = if targetOptions.username == null then config.networking.hostName else targetOptions.username;
|
|
|
|
in pkgs.writeShellApplication {
|
|
|
|
name = "clerie-backup-${jobName}-${targetName}";
|
|
|
|
|
|
|
|
runtimeInputs = [ pkgs.restic ];
|
|
|
|
|
|
|
|
text = ''
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
export RESTIC_PASSWORD_FILE=${jobPasswordFile}
|
|
|
|
export RESTIC_REPOSITORY="rest:https://${targetUsername}:$(cat ${targetPasswordFile})@${targetOptions.serverName}${repoPath}"
|
|
|
|
export RESTIC_PROGRESS_FPS=0.1
|
|
|
|
export RESTIC_CACHE_DIR=/var/cache/restic
|
|
|
|
|
|
|
|
restic "$@"
|
|
|
|
'';
|
|
|
|
|
|
|
|
checkPhase = "";
|
|
|
|
}
|
|
|
|
) jobTargetPairs;
|
|
|
|
|
2023-05-16 12:21:11 +02:00
|
|
|
targetOptions = { ... }: {
|
|
|
|
options = {
|
|
|
|
passwordFile = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
|
|
|
};
|
|
|
|
username = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
|
|
|
};
|
|
|
|
serverName = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
jobOptions = { ... }: {
|
|
|
|
options = {
|
|
|
|
passwordFile = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
|
|
|
};
|
|
|
|
repoPath = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
|
|
|
};
|
|
|
|
targets = mkOption {
|
|
|
|
type = with types; nullOr (listOf str);
|
|
|
|
default = null;
|
|
|
|
};
|
|
|
|
paths = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
};
|
2023-06-20 20:35:37 +02:00
|
|
|
exclude = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
|
|
|
};
|
2023-05-16 12:21:11 +02:00
|
|
|
};
|
|
|
|
};
|
|
|
|
in
|
|
|
|
|
|
|
|
{
|
|
|
|
options = {
|
|
|
|
clerie.backup = {
|
|
|
|
enable = mkEnableOption "clerie's Backup";
|
|
|
|
targets = mkOption {
|
|
|
|
type = with types; attrsOf (submodule targetOptions);
|
|
|
|
default = {};
|
|
|
|
};
|
|
|
|
jobs = mkOption {
|
|
|
|
type = with types; attrsOf (submodule jobOptions);
|
|
|
|
default = {};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
config = mkIf cfg.enable {
|
|
|
|
systemd.services = backupServiceUnits;
|
|
|
|
systemd.timers = backupServiceTimers;
|
2023-07-01 13:08:49 +02:00
|
|
|
systemd.tmpfiles.rules = [
|
|
|
|
"d /var/cache/restic - - - - -"
|
|
|
|
];
|
2023-07-04 09:02:44 +02:00
|
|
|
environment.systemPackages = backupCommands;
|
2023-05-16 12:21:11 +02:00
|
|
|
};
|
|
|
|
}
|