diff options
| author | Gregor Kleen <gkleen@yggdrasil.li> | 2021-01-02 20:53:17 +0100 |
|---|---|---|
| committer | Gregor Kleen <gkleen@yggdrasil.li> | 2021-01-03 16:21:34 +0100 |
| commit | f6e600c20d6a97ebeda23fa2bb5621646222b2b0 (patch) | |
| tree | 5551a23218db79e3edbf41557b474121f0745821 /modules/borgbackup | |
| parent | f4fa33d0d258c4f66f804ed3fc3be590d8039e6e (diff) | |
| download | nixos-f6e600c20d6a97ebeda23fa2bb5621646222b2b0.tar nixos-f6e600c20d6a97ebeda23fa2bb5621646222b2b0.tar.gz nixos-f6e600c20d6a97ebeda23fa2bb5621646222b2b0.tar.bz2 nixos-f6e600c20d6a97ebeda23fa2bb5621646222b2b0.tar.xz nixos-f6e600c20d6a97ebeda23fa2bb5621646222b2b0.zip | |
sif: import config
Diffstat (limited to 'modules/borgbackup')
| -rw-r--r-- | modules/borgbackup/btrfs-snapshots.nix | 52 | ||||
| -rw-r--r-- | modules/borgbackup/default.nix | 199 | ||||
| -rw-r--r-- | modules/borgbackup/lvm-snapshots.nix | 133 |
3 files changed, 384 insertions, 0 deletions
diff --git a/modules/borgbackup/btrfs-snapshots.nix b/modules/borgbackup/btrfs-snapshots.nix new file mode 100644 index 00000000..96d2b2ba --- /dev/null +++ b/modules/borgbackup/btrfs-snapshots.nix | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | { config, lib, pkgs, ... }: | ||
| 2 | |||
| 3 | with lib; | ||
| 4 | |||
| 5 | let | ||
| 6 | cfg = config.services.btrfs-snapshots; | ||
| 7 | |||
| 8 | snapshotMount = str: "${str}${cfg.mountSuffix}"; | ||
| 9 | in { | ||
| 10 | options = { | ||
| 11 | |||
| 12 | services.btrfs-snapshots = { | ||
| 13 | enable = mkEnableOption "a systemd unit for btrfs snapshots"; | ||
| 14 | |||
| 15 | mountSuffix = mkOption { | ||
| 16 | type = types.str; | ||
| 17 | default = ".snapshot"; | ||
| 18 | }; | ||
| 19 | |||
| 20 | readOnly = mkOption { | ||
| 21 | type = types.bool; | ||
| 22 | default = true; | ||
| 23 | }; | ||
| 24 | |||
| 25 | persist = mkOption { | ||
| 26 | type = types.bool; | ||
| 27 | default = false; | ||
| 28 | }; | ||
| 29 | }; | ||
| 30 | |||
| 31 | }; | ||
| 32 | |||
| 33 | |||
| 34 | config = mkIf cfg.enable { | ||
| 35 | systemd.services."btrfs-snapshot@" = { | ||
| 36 | enable = true; | ||
| 37 | |||
| 38 | unitConfig = { | ||
| 39 | StopWhenUnneeded = !cfg.persist; | ||
| 40 | }; | ||
| 41 | |||
| 42 | serviceConfig = with pkgs; { | ||
| 43 | Type = "oneshot"; | ||
| 44 | ExecStartPre = "-${btrfs-progs}/bin/btrfs subvolume delete -c ${snapshotMount "%f"}"; | ||
| 45 | ExecStart = "${btrfs-progs}/bin/btrfs subvolume snapshot ${optionalString cfg.readOnly "-r"} %f ${snapshotMount "%f"}"; | ||
| 46 | RemainAfterExit = true; | ||
| 47 | ExecStop = "${btrfs-progs}/bin/btrfs subvolume delete -c ${snapshotMount "%f"}"; | ||
| 48 | }; | ||
| 49 | }; | ||
| 50 | |||
| 51 | }; | ||
| 52 | } | ||
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 @@ | |||
| 1 | { config, lib, utils, pkgs, ... }: | ||
| 2 | |||
| 3 | with utils; | ||
| 4 | with lib; | ||
| 5 | |||
| 6 | let | ||
| 7 | cfg = config.services.borgbackup; | ||
| 8 | |||
| 9 | lvmPath = { | ||
| 10 | options = { | ||
| 11 | LV = mkOption { | ||
| 12 | type = types.str; | ||
| 13 | }; | ||
| 14 | VG = mkOption { | ||
| 15 | type = types.str; | ||
| 16 | }; | ||
| 17 | }; | ||
| 18 | }; | ||
| 19 | |||
| 20 | pathType = if cfg.snapshots == "lvm" then types.submodule lvmPath else types.path; | ||
| 21 | |||
| 22 | systemdPath = path: escapeSystemdPath (if cfg.snapshots == "lvm" then "${path.VG}-${path.LV}" else path); | ||
| 23 | |||
| 24 | withSuffix = path: path + (if cfg.snapshots == "btrfs" then config.services.btrfs-snapshots.mountSuffix else config.services.lvm-snapshots.mountSuffix); | ||
| 25 | |||
| 26 | mountPoint = if cfg.snapshots == "lvm" then config.services.lvm-snapshots.mountPoint else ""; | ||
| 27 | |||
| 28 | targetOptions = { | ||
| 29 | options = { | ||
| 30 | repo = mkOption { | ||
| 31 | type = types.str; | ||
| 32 | }; | ||
| 33 | |||
| 34 | paths = mkOption { | ||
| 35 | type = types.listOf pathType; | ||
| 36 | default = []; | ||
| 37 | }; | ||
| 38 | |||
| 39 | prune = mkOption { | ||
| 40 | type = types.attrsOf (types.listOf types.str); | ||
| 41 | default = {}; | ||
| 42 | }; | ||
| 43 | |||
| 44 | interval = mkOption { | ||
| 45 | type = types.str; | ||
| 46 | default = "6h"; | ||
| 47 | }; | ||
| 48 | |||
| 49 | jitter = mkOption { | ||
| 50 | type = with types; nullOr str; | ||
| 51 | default = "6h"; | ||
| 52 | }; | ||
| 53 | |||
| 54 | lock = mkOption { | ||
| 55 | type = types.nullOr types.str; | ||
| 56 | default = "backup"; | ||
| 57 | }; | ||
| 58 | |||
| 59 | network = mkOption { | ||
| 60 | type = types.bool; | ||
| 61 | default = true; | ||
| 62 | }; | ||
| 63 | |||
| 64 | lockWait = mkOption { | ||
| 65 | type = types.int; | ||
| 66 | default = 600; | ||
| 67 | }; | ||
| 68 | }; | ||
| 69 | }; | ||
| 70 | in { | ||
| 71 | disabledModules = [ "services/backup/borgbackup.nix" ]; | ||
| 72 | |||
| 73 | options = { | ||
| 74 | services.borgbackup = { | ||
| 75 | snapshots = mkOption { | ||
| 76 | type = types.nullOr (types.enum ["btrfs" "lvm"]); | ||
| 77 | default = null; | ||
| 78 | }; | ||
| 79 | |||
| 80 | targets = mkOption { | ||
| 81 | type = types.attrsOf (types.submodule targetOptions); | ||
| 82 | default = {}; | ||
| 83 | }; | ||
| 84 | |||
| 85 | prefix = mkOption { | ||
| 86 | type = types.str; | ||
| 87 | }; | ||
| 88 | }; | ||
| 89 | }; | ||
| 90 | |||
| 91 | imports = | ||
| 92 | [ ./lvm-snapshots.nix | ||
| 93 | ./btrfs-snapshots.nix | ||
| 94 | ]; | ||
| 95 | |||
| 96 | config = mkIf (any (t: t.paths != []) (attrValues cfg.targets)) { | ||
| 97 | services.btrfs-snapshots.enable = mkIf (cfg.snapshots == "btrfs") true; | ||
| 98 | |||
| 99 | services.lvm-snapshots.snapshots = mkIf (cfg.snapshots == "lvm") (listToAttrs (map (path: nameValuePair (path.VG + "-" + path.LV) { | ||
| 100 | inherit (path) LV VG; | ||
| 101 | mountName = withSuffix (path.VG + "-" + path.LV); | ||
| 102 | }) (unique (flatten (mapAttrsToList (target: tCfg: tCfg.paths) cfg.targets))))); | ||
| 103 | |||
| 104 | systemd.targets."timers-borg" = { | ||
| 105 | wantedBy = [ "timers.target" ]; | ||
| 106 | }; | ||
| 107 | |||
| 108 | systemd.slices."system-borgbackup" = {}; | ||
| 109 | |||
| 110 | systemd.timers = (listToAttrs (map ({ target, path, tCfg }: nameValuePair "borgbackup-${target}@${systemdPath path}" { | ||
| 111 | requiredBy = [ "timers-borg.target" ]; | ||
| 112 | |||
| 113 | timerConfig = { | ||
| 114 | Persistent = false; | ||
| 115 | OnBootSec = tCfg.interval; | ||
| 116 | OnUnitActiveSec = tCfg.interval; | ||
| 117 | RandomizedDelaySec = mkIf (tCfg.jitter != null) tCfg.jitter; | ||
| 118 | }; | ||
| 119 | }) (flatten (mapAttrsToList (target: tCfg: map (path: { inherit target path tCfg; }) tCfg.paths) cfg.targets)))) // (mapAttrs' (target: tCfg: nameValuePair "borgbackup-prune-${target}" { | ||
| 120 | enable = tCfg.prune != {}; | ||
| 121 | |||
| 122 | requiredBy = [ "timers-borg.target" ]; | ||
| 123 | |||
| 124 | timerConfig = { | ||
| 125 | Persistent = false; | ||
| 126 | OnBootSec = tCfg.interval; | ||
| 127 | OnUnitActiveSec = tCfg.interval; | ||
| 128 | RandomizedDelaySec = mkIf (tCfg.jitter != null) tCfg.jitter; | ||
| 129 | }; | ||
| 130 | }) cfg.targets); | ||
| 131 | |||
| 132 | systemd.services = (mapAttrs' (target: tCfg: nameValuePair "borgbackup-${target}@" (let | ||
| 133 | deps = flatten [ | ||
| 134 | (optional (cfg.snapshots == "btrfs") "btrfs-snapshot@%i.service") | ||
| 135 | (optional tCfg.network "network-online.target") | ||
| 136 | ]; | ||
| 137 | in { | ||
| 138 | bindsTo = deps; | ||
| 139 | after = deps; | ||
| 140 | |||
| 141 | path = with pkgs; [borgbackup] ++ optional (tCfg.lock != null) utillinux; | ||
| 142 | |||
| 143 | script = let | ||
| 144 | borgCmd = '' | ||
| 145 | borg create \ | ||
| 146 | --lock-wait ${toString tCfg.lockWait} \ | ||
| 147 | --stats \ | ||
| 148 | --list \ | ||
| 149 | --filter 'AME' \ | ||
| 150 | --exclude-caches \ | ||
| 151 | --keep-exclude-tags \ | ||
| 152 | --patterns-from .backup-${target} \ | ||
| 153 | --one-file-system \ | ||
| 154 | --compression auto,lzma \ | ||
| 155 | ${tCfg.repo}::${cfg.prefix}$1-{utcnow} | ||
| 156 | ''; | ||
| 157 | in if tCfg.lock == null then borgCmd else "flock -xo /var/lock/${tCfg.lock} ${borgCmd}"; | ||
| 158 | scriptArgs = if cfg.snapshots == "lvm" then "%I" else "%i"; | ||
| 159 | |||
| 160 | unitConfig = { | ||
| 161 | AssertPathIsDirectory = mkIf (tCfg.lock != null) "/var/lock"; | ||
| 162 | DefaultDependencies = false; | ||
| 163 | RequiresMountsFor = mkIf (cfg.snapshots == "lvm") [ "${mountPoint}/${withSuffix "%I"}" ]; | ||
| 164 | }; | ||
| 165 | |||
| 166 | serviceConfig = { | ||
| 167 | Type = "oneshot"; | ||
| 168 | WorkingDirectory = if (cfg.snapshots == null) then "%I" else (if (cfg.snapshots == "lvm") then "${mountPoint}/${withSuffix "%I"}" else "${withSuffix "%f"}"); | ||
| 169 | Nice = 15; | ||
| 170 | IOSchedulingClass = 2; | ||
| 171 | IOSchedulingPriority = 7; | ||
| 172 | SuccessExitStatus = [1 2]; | ||
| 173 | Slice = "system-borgbackup.slice"; | ||
| 174 | }; | ||
| 175 | })) cfg.targets) // (mapAttrs' (target: tCfg: nameValuePair "borgbackup-prune-${target}" { | ||
| 176 | enable = tCfg.prune != {}; | ||
| 177 | |||
| 178 | bindsTo = ["network-online.target"]; | ||
| 179 | after = ["network-online.target"]; | ||
| 180 | |||
| 181 | path = with pkgs; [borgbackup]; | ||
| 182 | |||
| 183 | script = concatStringsSep "\n" (mapAttrsToList (path: args: '' | ||
| 184 | borg prune \ | ||
| 185 | --lock-wait ${toString tCfg.lockWait} \ | ||
| 186 | --list \ | ||
| 187 | --stats \ | ||
| 188 | --prefix ${escapeShellArg "${cfg.prefix}${path}"} \ | ||
| 189 | ${escapeShellArgs args} \ | ||
| 190 | ${tCfg.repo} | ||
| 191 | '') tCfg.prune); | ||
| 192 | |||
| 193 | serviceConfig = { | ||
| 194 | Type = "oneshot"; | ||
| 195 | Slice = "system-borgbackup.slice"; | ||
| 196 | }; | ||
| 197 | }) cfg.targets); | ||
| 198 | }; | ||
| 199 | } | ||
diff --git a/modules/borgbackup/lvm-snapshots.nix b/modules/borgbackup/lvm-snapshots.nix new file mode 100644 index 00000000..9b2a6562 --- /dev/null +++ b/modules/borgbackup/lvm-snapshots.nix | |||
| @@ -0,0 +1,133 @@ | |||
| 1 | { config, lib, utils, pkgs, ... }: | ||
| 2 | |||
| 3 | with utils; | ||
| 4 | with lib; | ||
| 5 | |||
| 6 | let | ||
| 7 | cfg = config.services.lvm-snapshots; | ||
| 8 | |||
| 9 | snapshotMount = name: "${cfg.mountPoint}/${if isNull cfg.snapshots."${name}".mountName then name else cfg.snapshots."${name}".mountName}"; | ||
| 10 | snapshotName = name: "${name}-${cfg.mountSuffix}"; | ||
| 11 | |||
| 12 | snapshotConfig = { | ||
| 13 | options = { | ||
| 14 | LV = mkOption { | ||
| 15 | type = types.str; | ||
| 16 | }; | ||
| 17 | |||
| 18 | VG = mkOption { | ||
| 19 | type = types.str; | ||
| 20 | }; | ||
| 21 | |||
| 22 | mountName = mkOption { | ||
| 23 | type = types.nullOr types.str; | ||
| 24 | default = null; | ||
| 25 | }; | ||
| 26 | |||
| 27 | cowSize = mkOption { | ||
| 28 | type = types.str; | ||
| 29 | default = "-l20%ORIGIN"; | ||
| 30 | }; | ||
| 31 | |||
| 32 | readOnly = mkOption { | ||
| 33 | type = types.bool; | ||
| 34 | default = true; | ||
| 35 | }; | ||
| 36 | |||
| 37 | persist = mkOption { | ||
| 38 | type = types.bool; | ||
| 39 | default = false; | ||
| 40 | }; | ||
| 41 | }; | ||
| 42 | }; | ||
| 43 | in { | ||
| 44 | options = { | ||
| 45 | |||
| 46 | services.lvm-snapshots = { | ||
| 47 | snapshots = mkOption { | ||
| 48 | type = types.attrsOf (types.submodule snapshotConfig); | ||
| 49 | default = {}; | ||
| 50 | }; | ||
| 51 | |||
| 52 | mountPoint = mkOption { | ||
| 53 | type = types.path; | ||
| 54 | default = "/mnt"; | ||
| 55 | }; | ||
| 56 | |||
| 57 | mountSuffix = mkOption { | ||
| 58 | type = types.str; | ||
| 59 | default = "-snapshot"; | ||
| 60 | }; | ||
| 61 | }; | ||
| 62 | }; | ||
| 63 | |||
| 64 | |||
| 65 | config = mkIf (cfg != {}) { | ||
| 66 | |||
| 67 | boot.kernelModules = [ "dm_snapshot" ]; | ||
| 68 | |||
| 69 | # system.activationScripts = mapAttrs' (name: scfg: nameValuePair ("lvm-mountpoint" + name) '' | ||
| 70 | # mkdir -p ${snapshotMount name} | ||
| 71 | # '') cfg.snapshots; | ||
| 72 | |||
| 73 | systemd.services = mapAttrs' (name: scfg: nameValuePair ("lvm-snapshot@" + escapeSystemdPath name) { | ||
| 74 | enable = true; | ||
| 75 | |||
| 76 | description = "LVM-snapshot of ${scfg.VG}/${scfg.LV}"; | ||
| 77 | |||
| 78 | bindsTo = ["${escapeSystemdPath "/dev/${scfg.VG}/${scfg.LV}"}.device"]; | ||
| 79 | after = ["${escapeSystemdPath "/dev/${scfg.VG}/${scfg.LV}"}.device"]; | ||
| 80 | |||
| 81 | unitConfig = { | ||
| 82 | StopWhenUnneeded = !scfg.persist; | ||
| 83 | AssertPathIsDirectory = "/var/lock"; | ||
| 84 | }; | ||
| 85 | |||
| 86 | path = with pkgs; [ devicemapper utillinux ]; | ||
| 87 | |||
| 88 | script = '' | ||
| 89 | ( | ||
| 90 | flock -xn -E 4 9 | ||
| 91 | if [[ "$?" -ne 0 ]]; then | ||
| 92 | exit $? | ||
| 93 | fi | ||
| 94 | |||
| 95 | lvcreate -s ${scfg.cowSize} --name ${snapshotName name} ${scfg.VG}/${scfg.LV} | ||
| 96 | |||
| 97 | sleep infinity & | ||
| 98 | ) 9>/var/lock/lvm-snapshot.${scfg.VG} | ||
| 99 | ''; | ||
| 100 | |||
| 101 | preStart = '' | ||
| 102 | lvremove -f ${scfg.VG}/${snapshotName name} | ||
| 103 | ''; | ||
| 104 | |||
| 105 | preStop = '' | ||
| 106 | lvremove -f ${scfg.VG}/${snapshotName name} | ||
| 107 | ''; | ||
| 108 | |||
| 109 | serviceConfig = with pkgs; { | ||
| 110 | Type = "forking"; | ||
| 111 | RestartForceExitStatus = [ "4" ]; | ||
| 112 | RestartSec = "5min"; | ||
| 113 | }; | ||
| 114 | }) cfg.snapshots; | ||
| 115 | |||
| 116 | systemd.mounts = mapAttrsToList (name: scfg: { | ||
| 117 | enable = true; | ||
| 118 | |||
| 119 | unitConfig = { | ||
| 120 | # AssertPathIsDirectory = snapshotMount name; | ||
| 121 | StopWhenUnneeded = !scfg.persist; | ||
| 122 | }; | ||
| 123 | |||
| 124 | bindsTo = [ ("lvm-snapshot@" + escapeSystemdPath name + ".service") ]; | ||
| 125 | after = [ ("lvm-snapshot@" + escapeSystemdPath name + ".service") ]; | ||
| 126 | |||
| 127 | options = concatStringsSep "," ([ "noauto" ] ++ optional scfg.readOnly "ro"); | ||
| 128 | |||
| 129 | where = snapshotMount name; | ||
| 130 | what = "/dev/" + scfg.VG + "/" + snapshotName name; | ||
| 131 | }) cfg.snapshots; | ||
| 132 | }; | ||
| 133 | } | ||
