# SPDX-FileCopyrightText: 2020-2024 Simon Bruder # # SPDX-License-Identifier: AGPL-3.0-or-later { pkgs, config, lib, ... }: let cfg = config.sbruder.restic.backups.system; excludes = [ # Caches "/home/*/Downloads/" "/home/*/.cache/" "/home/*/**/cache/" "/home/*/.local/share/Trash" # some gui applications use it "/root/.cache" "/data/cache/" "/var/cache/" # Rust "/home/*/.rustup/toolchains/" "/home/*/.cargo" # Misc "/home/*/mount" "/home/*/mounts" # Docker (state should be kept somewhere else) "/home/*/.local/share/containers" # podman "/var/lib/containers/" "/var/lib/docker/" # Static configuration (generated from this repository) "/etc/static/" ] ++ cfg.extraExcludes; excludesFile = pkgs.writeText "excludes.txt" (lib.concatStringsSep "\n" excludes); # 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 } } ''; in { options.sbruder.restic.backups.system = { enable = lib.mkEnableOption "restic system backup"; timerConfig = lib.mkOption { type = with lib.types; attrsOf str; default = { OnCalendar = "18:00"; RandomizedDelaySec = "2h"; }; }; extraPaths = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; example = [ "/data" ]; }; extraExcludes = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; }; uploadLimit = lib.mkOption { type = lib.types.nullOr lib.types.int; default = null; }; qos = (lib.mkEnableOption "QoS marking (DSCP AF12) of outgoing packets") // { default = !(isNull cfg.uploadLimit); }; }; config = lib.mkIf cfg.enable { services.restic.backups.system = { inherit (cfg) timerConfig; repositoryFile = config.sops.secrets.restic-repository.path; passwordFile = config.sops.secrets.restic-password.path; paths = [ "/etc" "/home" "/root" "/srv" "/var" ] ++ cfg.extraPaths; extraBackupArgs = [ "--compression auto" "--exclude-caches" "--exclude-file=${excludesFile}" "--tag system" "--verbose" ] ++ lib.optional (cfg.uploadLimit != null) "--limit-upload=${toString cfg.uploadLimit}"; } // (lib.optionalAttrs cfg.qos { backupPrepareCommand = '' ${pkgs.nftables}/bin/nft -f ${qosRules} ''; backupCleanupCommand = '' ${pkgs.nftables}/bin/nft delete table inet restic ''; }); systemd.services."restic-backups-system".serviceConfig = { "Nice" = 10; "IOSchedulingClass" = "best-effort"; "IOSchedulingPriority" = 7; Slice = "restic.slice"; }; }; }