From f6e600c20d6a97ebeda23fa2bb5621646222b2b0 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Sat, 2 Jan 2021 20:53:17 +0100 Subject: sif: import config --- modules/borgbackup/default.nix | 199 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 modules/borgbackup/default.nix (limited to 'modules/borgbackup/default.nix') diff --git a/modules/borgbackup/default.nix b/modules/borgbackup/default.nix new file mode 100644 index 00000000..47f8e06d --- /dev/null +++ b/modules/borgbackup/default.nix @@ -0,0 +1,199 @@ +{ 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 { + disabledModules = [ "services/backup/borgbackup.nix" ]; + + 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 "${mountPoint}/${withSuffix "%I"}" else "${withSuffix "%f"}"); + 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); + }; +} -- cgit v1.2.3