2024-01-06 01:19:35 +01:00
|
|
|
|
# SPDX-FileCopyrightText: 2020-2023 Simon Bruder <simon@sbruder.de>
|
|
|
|
|
#
|
|
|
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
|
|
2021-02-28 12:07:18 +01:00
|
|
|
|
{ pkgs, config, lib, ... }:
|
2020-08-22 17:44:39 +02:00
|
|
|
|
let
|
2021-02-28 12:21:04 +01:00
|
|
|
|
cfg = config.sbruder.restic.system;
|
2020-12-05 14:19:34 +01:00
|
|
|
|
|
2022-08-25 17:18:43 +02:00
|
|
|
|
sftpTarget = "u313368-sub4@u313368-sub4.your-storagebox.de";
|
|
|
|
|
sftpPort = 23;
|
|
|
|
|
repository = "sftp://${sftpTarget}:${toString sftpPort}/personal";
|
2020-08-22 17:44:39 +02:00
|
|
|
|
excludes = [
|
2020-12-21 13:08:22 +01:00
|
|
|
|
# Caches
|
2020-08-22 17:44:39 +02:00
|
|
|
|
"/home/*/Downloads/"
|
|
|
|
|
"/home/*/.cache/"
|
|
|
|
|
"/home/*/**/cache/"
|
2020-12-21 13:08:22 +01:00
|
|
|
|
"/home/*/.local/share/Trash" # some gui applications use it
|
2022-04-23 21:10:55 +02:00
|
|
|
|
"/root/.cache"
|
2020-12-21 13:08:22 +01:00
|
|
|
|
"/data/cache/"
|
2022-06-27 14:19:06 +02:00
|
|
|
|
"/var/cache/"
|
2020-08-22 17:44:39 +02:00
|
|
|
|
|
|
|
|
|
# Rust
|
|
|
|
|
"/home/*/.rustup/toolchains/"
|
|
|
|
|
"/home/*/.cargo"
|
|
|
|
|
|
2020-12-21 13:08:22 +01:00
|
|
|
|
# Misc
|
2020-08-22 17:44:39 +02:00
|
|
|
|
"/home/*/mount"
|
2022-05-15 11:14:34 +02:00
|
|
|
|
"/home/*/mounts"
|
2020-08-22 17:44:39 +02:00
|
|
|
|
|
2020-12-21 13:08:22 +01:00
|
|
|
|
# Docker (state should be kept somewhere else)
|
2020-08-22 17:44:39 +02:00
|
|
|
|
"/var/lib/docker/"
|
2021-04-06 10:47:05 +02:00
|
|
|
|
|
|
|
|
|
# Static configuration (generated from this repository)
|
|
|
|
|
"/etc/static/"
|
2020-12-21 13:09:25 +01:00
|
|
|
|
] ++ cfg.extraExcludes;
|
2021-02-28 11:55:58 +01:00
|
|
|
|
excludesFile = pkgs.writeText "excludes.txt" (lib.concatStringsSep "\n" excludes);
|
2020-12-21 12:54:33 +01:00
|
|
|
|
|
|
|
|
|
# script to use restic as user without dealing with authentication
|
|
|
|
|
authScript = pkgs.writeShellScriptBin "restic-auth" ''
|
|
|
|
|
${pkgs.restic}/bin/restic \
|
2021-05-27 14:38:33 +02:00
|
|
|
|
--password-command="pass data/backup/restic-nixos" \
|
2020-12-21 12:54:33 +01:00
|
|
|
|
--repo "${repository}" \
|
|
|
|
|
$@
|
|
|
|
|
'';
|
2023-10-07 22:32:10 +02:00
|
|
|
|
|
|
|
|
|
# HACK: NixOS’ nftables implementation runs nft -c inside the build sandbox,
|
|
|
|
|
# where the target host’s cgroups are not available,
|
|
|
|
|
# and therefore fails.
|
|
|
|
|
# This is there to allow my home router to put backup traffic into the right qdisc,
|
|
|
|
|
# as the ip address and port are also used for other things.
|
|
|
|
|
# This is somewhat of an abuse of the DSCP mark.
|
|
|
|
|
qosRules = pkgs.writeText "restic-qos.nft" ''
|
|
|
|
|
table inet restic
|
|
|
|
|
delete table inet restic
|
|
|
|
|
|
|
|
|
|
table inet restic {
|
|
|
|
|
chain output {
|
|
|
|
|
type filter hook output priority mangle
|
|
|
|
|
ip version 4 socket cgroupv2 level 1 "restic.slice" ip dscp set af12 return
|
|
|
|
|
ip6 version 6 socket cgroupv2 level 1 "restic.slice" ip6 dscp set af12 return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
'';
|
2020-08-22 17:44:39 +02:00
|
|
|
|
in
|
|
|
|
|
{
|
2021-02-28 12:21:04 +01:00
|
|
|
|
options.sbruder.restic.system = {
|
2020-12-05 14:19:34 +01:00
|
|
|
|
enable = lib.mkEnableOption "restic";
|
2021-02-28 12:07:18 +01:00
|
|
|
|
timerConfig = lib.mkOption {
|
|
|
|
|
type = with lib.types; attrsOf str;
|
|
|
|
|
default = {
|
2022-01-22 10:32:51 +01:00
|
|
|
|
OnCalendar = "18:00";
|
2021-02-28 12:07:18 +01:00
|
|
|
|
RandomizedDelaySec = "2h";
|
2020-12-21 12:33:46 +01:00
|
|
|
|
};
|
2021-02-28 12:07:18 +01:00
|
|
|
|
};
|
2020-12-21 13:09:25 +01:00
|
|
|
|
extraPaths = lib.mkOption {
|
|
|
|
|
type = lib.types.listOf lib.types.str;
|
|
|
|
|
default = [ ];
|
|
|
|
|
example = [ "/data" ];
|
|
|
|
|
};
|
|
|
|
|
extraExcludes = lib.mkOption {
|
|
|
|
|
type = lib.types.listOf lib.types.str;
|
|
|
|
|
default = [ ];
|
|
|
|
|
};
|
2021-03-08 18:43:48 +01:00
|
|
|
|
uploadLimit = lib.mkOption {
|
|
|
|
|
type = lib.types.nullOr lib.types.int;
|
2022-08-25 23:21:35 +02:00
|
|
|
|
default = null;
|
2021-03-08 18:43:48 +01:00
|
|
|
|
};
|
2023-10-07 22:32:10 +02:00
|
|
|
|
qos = (lib.mkEnableOption "QoS marking (DSCP AF12) of outgoing packets") // { default = !(lib.isNull cfg.uploadLimit); };
|
2021-05-28 14:54:45 +02:00
|
|
|
|
prune = lib.mkEnableOption "pruning";
|
2020-08-22 17:44:39 +02:00
|
|
|
|
};
|
2020-10-17 09:58:44 +02:00
|
|
|
|
|
2020-12-05 14:19:34 +01:00
|
|
|
|
config = lib.mkIf cfg.enable {
|
2021-03-01 15:27:18 +01:00
|
|
|
|
sops.secrets = {
|
2021-01-06 13:09:29 +01:00
|
|
|
|
restic-password = { };
|
2022-08-25 17:18:43 +02:00
|
|
|
|
restic-repository = { };
|
|
|
|
|
} // lib.optionalAttrs cfg.prune {
|
|
|
|
|
restic-ssh-key = {
|
|
|
|
|
sopsFile = ../../machines/${config.networking.hostName}/secrets.yaml;
|
|
|
|
|
};
|
2021-01-06 13:09:29 +01:00
|
|
|
|
};
|
|
|
|
|
|
2021-01-08 21:33:45 +01:00
|
|
|
|
services.restic.backups.system = {
|
2021-02-28 12:07:18 +01:00
|
|
|
|
inherit (cfg) timerConfig;
|
2022-08-25 17:18:43 +02:00
|
|
|
|
repositoryFile = config.sops.secrets.restic-repository.path;
|
2021-03-01 15:27:18 +01:00
|
|
|
|
passwordFile = config.sops.secrets.restic-password.path;
|
2020-12-21 13:09:25 +01:00
|
|
|
|
paths = [
|
2021-04-06 10:47:05 +02:00
|
|
|
|
"/etc"
|
2020-12-21 13:09:25 +01:00
|
|
|
|
"/home"
|
2021-04-06 10:47:05 +02:00
|
|
|
|
"/root"
|
2020-12-21 13:09:25 +01:00
|
|
|
|
"/srv"
|
|
|
|
|
"/var"
|
|
|
|
|
] ++ cfg.extraPaths;
|
2020-12-05 14:19:34 +01:00
|
|
|
|
extraBackupArgs = [
|
2022-12-13 09:59:31 +01:00
|
|
|
|
"--compression auto"
|
2020-12-05 14:19:34 +01:00
|
|
|
|
"--exclude-caches"
|
|
|
|
|
"--exclude-file=${excludesFile}"
|
2021-02-28 12:21:04 +01:00
|
|
|
|
"--tag system"
|
2020-12-05 14:19:34 +01:00
|
|
|
|
"--verbose"
|
2021-03-08 18:43:48 +01:00
|
|
|
|
] ++ lib.optional (cfg.uploadLimit != null) "--limit-upload=${toString cfg.uploadLimit}";
|
2023-12-02 18:54:23 +01:00
|
|
|
|
backupPrepareCommand = ''
|
|
|
|
|
${pkgs.nftables}/bin/nft -f ${qosRules}
|
|
|
|
|
'';
|
|
|
|
|
backupCleanupCommand = ''
|
|
|
|
|
${pkgs.nftables}/bin/nft delete table inet restic
|
|
|
|
|
'';
|
2020-12-05 14:19:34 +01:00
|
|
|
|
};
|
|
|
|
|
|
2021-01-08 21:33:45 +01:00
|
|
|
|
systemd.services."restic-backups-system".serviceConfig = {
|
2020-12-05 14:19:34 +01:00
|
|
|
|
"Nice" = 10;
|
|
|
|
|
"IOSchedulingClass" = "best-effort";
|
|
|
|
|
"IOSchedulingPriority" = 7;
|
2023-10-07 22:32:10 +02:00
|
|
|
|
Slice = "restic.slice";
|
2020-12-05 14:19:34 +01:00
|
|
|
|
};
|
2020-12-21 12:54:33 +01:00
|
|
|
|
|
2021-05-28 14:54:45 +02:00
|
|
|
|
services.restic.backups.system-prune = lib.mkIf cfg.prune {
|
|
|
|
|
inherit repository;
|
|
|
|
|
passwordFile = config.sops.secrets.restic-password.path;
|
|
|
|
|
timerConfig = {
|
|
|
|
|
OnCalendar = "*-1/2-07 03:00:00";
|
|
|
|
|
RandomizedDelaySec = "4h";
|
|
|
|
|
};
|
|
|
|
|
paths = [ ];
|
2022-08-25 17:18:43 +02:00
|
|
|
|
extraOptions = [
|
|
|
|
|
"-o"
|
|
|
|
|
"sftp.command='ssh -i ${config.sops.secrets.restic-ssh-key.path} -p ${toString sftpPort} ${sftpTarget} -s sftp'"
|
|
|
|
|
];
|
2021-05-28 14:54:45 +02:00
|
|
|
|
pruneOpts = [
|
2022-12-13 09:59:31 +01:00
|
|
|
|
"--compression auto"
|
2021-05-28 14:54:45 +02:00
|
|
|
|
"--keep-daily 7"
|
|
|
|
|
"--keep-monthly 12"
|
|
|
|
|
"--keep-weekly 5"
|
|
|
|
|
"--keep-yearly 10"
|
|
|
|
|
"--tag system"
|
|
|
|
|
"--verbose"
|
|
|
|
|
] ++ lib.optional (cfg.uploadLimit != null) "--limit-upload=${toString cfg.uploadLimit}";
|
|
|
|
|
};
|
|
|
|
|
|
2020-12-21 12:54:33 +01:00
|
|
|
|
environment.systemPackages = [
|
|
|
|
|
authScript
|
|
|
|
|
];
|
2020-10-17 09:58:44 +02:00
|
|
|
|
};
|
2020-08-22 17:44:39 +02:00
|
|
|
|
}
|