clan-core/clanModules/borgbackup/default.nix

162 lines
5.1 KiB
Nix
Raw Normal View History

2024-03-17 18:48:49 +00:00
{
config,
lib,
pkgs,
...
}:
let
cfg = config.clan.borgbackup;
preBackupScript = ''
declare -A preCommandErrors
${lib.concatMapStringsSep "\n" (
state:
lib.optionalString (state.preBackupCommand != null) ''
echo "Running pre-backup command for ${state.name}"
if ! /run/current-system/sw/bin/${state.preBackupCommand}; then
preCommandErrors["${state.name}"]=1
fi
''
2024-06-17 10:42:28 +00:00
) (lib.attrValues config.clan.core.state)}
if [[ ''${#preCommandErrors[@]} -gt 0 ]]; then
echo "pre-backup commands failed for the following services:"
for state in "''${!preCommandErrors[@]}"; do
echo " $state"
done
exit 1
fi
'';
in
{
2024-03-08 10:12:34 +00:00
options.clan.borgbackup.destinations = lib.mkOption {
2024-03-17 18:48:49 +00:00
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
options = {
name = lib.mkOption {
type = lib.types.strMatching "^[a-zA-Z0-9._-]+$";
2024-03-17 18:48:49 +00:00
default = name;
description = "the name of the backup job";
};
repo = lib.mkOption {
type = lib.types.str;
description = "the borgbackup repository to backup to";
};
rsh = lib.mkOption {
type = lib.types.str;
default = "ssh -i ${
2024-06-17 10:42:28 +00:00
config.clan.core.facts.services.borgbackup.secret."borgbackup.ssh".path
} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=Yes";
2024-06-17 10:42:28 +00:00
defaultText = "ssh -i \${config.clan.core.facts.services.borgbackup.secret.\"borgbackup.ssh\".path} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null";
2024-03-17 18:48:49 +00:00
description = "the rsh to use for the backup";
};
};
}
)
);
2024-03-08 10:12:34 +00:00
default = { };
description = ''
destinations where the machine should be backuped to
'';
};
2024-03-08 10:12:34 +00:00
2024-03-17 18:48:49 +00:00
imports = [
(lib.mkRemovedOptionModule [
"clan"
"borgbackup"
"enable"
] "Just define clan.borgbackup.destinations to enable it")
];
2024-03-08 10:12:34 +00:00
2024-03-12 12:17:04 +00:00
config = lib.mkIf (cfg.destinations != { }) {
systemd.services = lib.mapAttrs' (
_: dest:
lib.nameValuePair "borgbackup-job-${dest.name}" {
# since borgbackup mounts the system read-only, we need to run in a ExecStartPre script, so we can generate additional files.
serviceConfig.ExecStartPre = [
(''+${pkgs.writeShellScript "borgbackup-job-${dest.name}-pre-backup-commands" preBackupScript}'')
];
}
) cfg.destinations;
2024-03-17 18:48:49 +00:00
services.borgbackup.jobs = lib.mapAttrs (_: dest: {
2024-05-31 14:36:37 +00:00
paths = lib.unique (
2024-06-17 10:42:28 +00:00
lib.flatten (map (state: state.folders) (lib.attrValues config.clan.core.state))
2024-05-31 14:36:37 +00:00
);
2024-03-17 18:48:49 +00:00
exclude = [ "*.pyc" ];
repo = dest.repo;
environment.BORG_RSH = dest.rsh;
compression = "auto,zstd";
startAt = "*-*-* 01:00:00";
persistentTimer = true;
2024-03-17 18:48:49 +00:00
encryption = {
mode = "repokey";
2024-06-17 10:42:28 +00:00
passCommand = "cat ${config.clan.core.facts.services.borgbackup.secret."borgbackup.repokey".path}";
2024-03-17 18:48:49 +00:00
};
2024-02-22 13:50:07 +00:00
2024-03-17 18:48:49 +00:00
prune.keep = {
within = "1d"; # Keep all archives from the last day
daily = 7;
weekly = 4;
monthly = 0;
};
}) cfg.destinations;
2024-06-17 10:42:28 +00:00
clan.core.facts.services.borgbackup = {
2024-03-28 09:30:37 +00:00
public."borgbackup.ssh.pub" = { };
secret."borgbackup.ssh" = { };
secret."borgbackup.repokey" = { };
2024-03-17 18:48:49 +00:00
generator.path = [
pkgs.openssh
pkgs.coreutils
pkgs.xkcdpass
];
2023-12-06 16:12:38 +00:00
generator.script = ''
ssh-keygen -t ed25519 -N "" -f "$secrets"/borgbackup.ssh
mv "$secrets"/borgbackup.ssh.pub "$facts"/borgbackup.ssh.pub
2024-02-22 13:50:07 +00:00
xkcdpass -n 4 -d - > "$secrets"/borgbackup.repokey
2023-12-06 16:12:38 +00:00
'';
};
environment.systemPackages = [
(pkgs.writeShellScriptBin "borgbackup-create" ''
set -efu -o pipefail
${lib.concatMapStringsSep "\n" (dest: ''
systemctl start borgbackup-job-${dest.name}
'') (lib.attrValues cfg.destinations)}
'')
(pkgs.writeShellScriptBin "borgbackup-list" ''
2024-03-19 16:25:31 +00:00
set -efu
2024-03-19 18:23:14 +00:00
(${
lib.concatMapStringsSep "\n" (
dest:
# we need yes here to skip the changed url verification
''yes y | borg-job-${dest.name} list --json | jq '[.archives[] | {"name": ("${dest.name}::${dest.repo}::" + .name)}]' ''
) (lib.attrValues cfg.destinations)
}) | ${pkgs.jq}/bin/jq -s 'add'
'')
(pkgs.writeShellScriptBin "borgbackup-restore" ''
set -efux
2023-12-08 17:40:18 +00:00
cd /
2024-06-04 14:27:53 +00:00
IFS=':' read -ra FOLDER <<< "$FOLDERS"
job_name=$(echo "$NAME" | ${pkgs.gawk}/bin/awk -F'::' '{print $1}')
backup_name=''${NAME#"$job_name"::}
if ! command -v borg-job-"$job_name" &> /dev/null; then
echo "borg-job-$job_name not found: Backup name is invalid" >&2
exit 1
fi
yes y | borg-job-"$job_name" extract --list "$backup_name" "''${FOLDER[@]}"
'')
];
2024-06-17 10:42:28 +00:00
clan.core.backups.providers.borgbackup = {
list = "borgbackup-list";
create = "borgbackup-create";
restore = "borgbackup-restore";
};
};
}