diff options
Diffstat (limited to 'modules/borgbackup/default.nix')
-rw-r--r-- | modules/borgbackup/default.nix | 199 |
1 files changed, 199 insertions, 0 deletions
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 | } | ||