{ config, lib, utils, pkgs, ... }: with utils; with lib; let cfg = config.services.borgbackup; lvmPath = { options = { LV = mkOption { type = types.str; }; VG = mkOption { type = types.str; }; }; }; pathType = if cfg.snapshots == "lvm" then types.submodule lvmPath else types.path; systemdPath = path: escapeSystemdPath (if cfg.snapshots == "lvm" then "${path.VG}-${path.LV}" else path); withSuffix = path: path + (if cfg.snapshots == "btrfs" then config.services.btrfs-snapshots.mountSuffix else config.services.lvm-snapshots.mountSuffix); mountPoint = if cfg.snapshots == "lvm" then config.services.lvm-snapshots.mountPoint else ""; targetOptions = { options = { repo = mkOption { type = types.str; }; paths = mkOption { type = types.listOf pathType; default = []; }; prune = mkOption { type = types.attrsOf (types.listOf types.str); default = {}; }; interval = mkOption { type = types.str; default = "6h"; }; jitter = mkOption { type = with types; nullOr str; default = "6h"; }; lock = mkOption { type = types.nullOr types.str; default = "backup"; }; network = mkOption { type = types.bool; default = true; }; lockWait = mkOption { type = types.int; default = 600; }; }; }; in { options = { services.borgbackup = { snapshots = mkOption { type = types.nullOr (types.enum ["btrfs" "lvm"]); default = null; }; targets = mkOption { type = types.attrsOf (types.submodule targetOptions); default = {}; }; prefix = mkOption { type = types.str; }; }; }; imports = [ ./lvm-snapshots.nix ./btrfs-snapshots.nix ]; config = mkIf (any (t: t.paths != []) (attrValues cfg.targets)) { services.btrfs-snapshots.enable = mkIf (cfg.snapshots == "btrfs") true; services.lvm-snapshots.snapshots = mkIf (cfg.snapshots == "lvm") (listToAttrs (map (path: nameValuePair (path.VG + "-" + path.LV) { inherit (path) LV VG; mountName = withSuffix (path.VG + "-" + path.LV); }) (unique (flatten (mapAttrsToList (target: tCfg: tCfg.paths) cfg.targets))))); systemd.targets."timers-borg" = { wantedBy = [ "timers.target" ]; }; systemd.slices."system-borgbackup" = {}; systemd.timers = (listToAttrs (map ({ target, path, tCfg }: nameValuePair "borgbackup-${target}@${systemdPath path}" { requiredBy = [ "timers-borg.target" ]; timerConfig = { Persistent = false; OnBootSec = tCfg.interval; OnUnitActiveSec = tCfg.interval; RandomizedDelaySec = mkIf (tCfg.jitter != null) tCfg.jitter; }; }) (flatten (mapAttrsToList (target: tCfg: map (path: { inherit target path tCfg; }) tCfg.paths) cfg.targets)))) // (mapAttrs' (target: tCfg: nameValuePair "borgbackup-prune-${target}" { enable = tCfg.prune != {}; requiredBy = [ "timers-borg.target" ]; timerConfig = { Persistent = false; OnBootSec = tCfg.interval; OnUnitActiveSec = tCfg.interval; RandomizedDelaySec = mkIf (tCfg.jitter != null) tCfg.jitter; }; }) cfg.targets); systemd.services = (mapAttrs' (target: tCfg: nameValuePair "borgbackup-${target}@" (let deps = flatten [ (optional (cfg.snapshots == "btrfs") "btrfs-snapshot@%i.service") (optional tCfg.network "network-online.target") ]; in { bindsTo = deps; after = deps; path = with pkgs; [borgbackup] ++ optional (tCfg.lock != null) utillinux; script = let borgCmd = '' borg create \ --lock-wait ${toString tCfg.lockWait} \ --stats \ --list \ --filter 'AME' \ --exclude-caches \ --keep-exclude-tags \ --patterns-from .backup-${target} \ --one-file-system \ --compression auto,lzma \ ${tCfg.repo}::${cfg.prefix}$1-{utcnow} ''; in if tCfg.lock == null then borgCmd else "flock -xo /var/lock/${tCfg.lock} ${borgCmd}"; scriptArgs = if cfg.snapshots == "lvm" then "%I" else "%i"; unitConfig = { AssertPathIsDirectory = mkIf (tCfg.lock != null) "/var/lock"; DefaultDependencies = false; RequiresMountsFor = mkIf (cfg.snapshots == "lvm") [ "${mountPoint}/${withSuffix "%I"}" ]; }; serviceConfig = { Type = "oneshot"; WorkingDirectory = if (cfg.snapshots == null) then "%I" else (if (cfg.snapshots == "lvm") then "${withSuffix "%I"}" else "${withSuffix "%i"}"); Nice = 15; IOSchedulingClass = 2; IOSchedulingPriority = 7; SuccessExitStatus = [1 2]; Slice = "system-borgbackup.slice"; }; })) cfg.targets) // (mapAttrs' (target: tCfg: nameValuePair "borgbackup-prune-${target}" { enable = tCfg.prune != {}; bindsTo = ["network-online.target"]; after = ["network-online.target"]; path = with pkgs; [borgbackup]; script = concatStringsSep "\n" (mapAttrsToList (path: args: '' borg prune \ --lock-wait ${toString tCfg.lockWait} \ --list \ --stats \ --prefix ${escapeShellArg "${cfg.prefix}${path}"} \ ${escapeShellArgs args} \ ${tCfg.repo} '') tCfg.prune); serviceConfig = { Type = "oneshot"; Slice = "system-borgbackup.slice"; }; }) cfg.targets); }; }