{ config, lib, utils, pkgs, ... }: with lib; with utils; 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: if cfg.snapshots == "lvm" then escapeSystemdPath "${path.VG}/${path.LV}" else escapeSystemdPath path; 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"; }; 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 = mkIf (cfg.snapshots == "lvm") (listToAttrs (map (path: nameValuePair (systemdPath path) { inherit (path) LV VG; mountName = "snapshot-${systemdPath path}"; }) (unique (flatten (mapAttrsToList (target: tCfg: tCfg.paths) cfg.targets))))); systemd.timers = (listToAttrs (map ({ target, path, tCfg }: nameValuePair "borgbackup-${target}@${systemdPath path}" { wantedBy = [ "timers.target" ]; timerConfig = { Persistent = false; OnBootSec = tCfg.interval; OnUnitInactiveSec = tCfg.interval; }; }) (flatten (mapAttrsToList (target: tCfg: map (path: { inherit target path tCfg; }) tCfg.paths) cfg.targets)))) // (mapAttrs' (target: tCfg: nameValuePair "borgbackup-prune-${target}" { wantedBy = [ "timers.target" ]; timerConfig = { Persistent = false; OnBootSec = tCfg.interval; OnUnitInactiveSec = tCfg.interval; }; }) 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 = "%i"; unitConfig = { AssertPathIsDirectory = mkIf (tCfg.lock != null) "/var/lock"; }; serviceConfig = { Type = "oneshot"; WorkingDirectory = if (cfg.snapshots == null) then "%p" else "/mnt/snapshot-%i"; Nice = 15; IOSchedulingClass = 2; IOSchedulingPriority = 7; SuccessExitStatus = [1 2]; }; })) cfg.targets) // (mapAttrs' (target: tCfg: nameValuePair "borgbackup-prune-${target}" { 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 "${cfg.prefix}${escapeSystemdPath path}" \ ${concatStringsSep " " args} \ ${tCfg.repo} '') tCfg.prune); serviceConfig = { Type = "oneshot"; }; }) cfg.targets); }; }