summaryrefslogtreecommitdiff
path: root/modules/borgbackup/default.nix
diff options
context:
space:
mode:
Diffstat (limited to 'modules/borgbackup/default.nix')
-rw-r--r--modules/borgbackup/default.nix206
1 files changed, 206 insertions, 0 deletions
diff --git a/modules/borgbackup/default.nix b/modules/borgbackup/default.nix
new file mode 100644
index 00000000..a0419d0e
--- /dev/null
+++ b/modules/borgbackup/default.nix
@@ -0,0 +1,206 @@
1{ config, lib, utils, pkgs, ... }:
2
3with utils;
4with lib;
5
6let
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 keyFile = mkOption {
70 type = types.nullOr types.path;
71 default = null;
72 };
73 };
74 };
75in {
76 disabledModules = [ "services/backup/borgbackup.nix" ];
77
78 options = {
79 services.borgbackup = {
80 snapshots = mkOption {
81 type = types.nullOr (types.enum ["btrfs" "lvm"]);
82 default = null;
83 };
84
85 targets = mkOption {
86 type = types.attrsOf (types.submodule targetOptions);
87 default = {};
88 };
89
90 prefix = mkOption {
91 type = types.str;
92 };
93 };
94 };
95
96 imports =
97 [ ./lvm-snapshots.nix
98 ./btrfs-snapshots.nix
99 ];
100
101 config = mkIf (any (t: t.paths != []) (attrValues cfg.targets)) {
102 services.btrfs-snapshots.enable = mkIf (cfg.snapshots == "btrfs") true;
103
104 services.lvm-snapshots.snapshots = mkIf (cfg.snapshots == "lvm") (listToAttrs (map (path: nameValuePair (path.VG + "-" + path.LV) {
105 inherit (path) LV VG;
106 mountName = withSuffix (path.VG + "-" + path.LV);
107 }) (unique (flatten (mapAttrsToList (target: tCfg: tCfg.paths) cfg.targets)))));
108
109 systemd.targets."timers-borg" = {
110 wantedBy = [ "timers.target" ];
111 };
112
113 systemd.slices."system-borgbackup" = {};
114
115 systemd.timers = (listToAttrs (map ({ target, path, tCfg }: nameValuePair "borgbackup-${target}@${systemdPath path}" {
116 requiredBy = [ "timers-borg.target" ];
117
118 timerConfig = {
119 Persistent = false;
120 OnBootSec = tCfg.interval;
121 OnUnitActiveSec = tCfg.interval;
122 RandomizedDelaySec = mkIf (tCfg.jitter != null) tCfg.jitter;
123 };
124 }) (flatten (mapAttrsToList (target: tCfg: map (path: { inherit target path tCfg; }) tCfg.paths) cfg.targets)))) // (mapAttrs' (target: tCfg: nameValuePair "borgbackup-prune-${target}" {
125 enable = tCfg.prune != {};
126
127 requiredBy = [ "timers-borg.target" ];
128
129 timerConfig = {
130 Persistent = false;
131 OnBootSec = tCfg.interval;
132 OnUnitActiveSec = tCfg.interval;
133 RandomizedDelaySec = mkIf (tCfg.jitter != null) tCfg.jitter;
134 };
135 }) cfg.targets);
136
137 systemd.services = (mapAttrs' (target: tCfg: nameValuePair "borgbackup-${target}@" (let
138 deps = flatten [
139 (optional (cfg.snapshots == "btrfs") "btrfs-snapshot@%i.service")
140 (optional tCfg.network "network-online.target")
141 ];
142 in {
143 bindsTo = deps;
144 after = deps;
145
146 path = with pkgs; [borgbackup] ++ optional (tCfg.lock != null) utillinux;
147
148 script = let
149 borgCmd = ''
150 borg create \
151 --lock-wait ${toString tCfg.lockWait} \
152 --stats \
153 --list \
154 --filter 'AME' \
155 --exclude-caches \
156 --keep-exclude-tags \
157 --patterns-from .backup-${target} \
158 --one-file-system \
159 --compression auto,lzma \
160 ${tCfg.repo}::${cfg.prefix}$1-{utcnow}
161 '';
162 in if tCfg.lock == null then borgCmd else "flock -xo /var/lock/${tCfg.lock} ${borgCmd}";
163 scriptArgs = if cfg.snapshots == "lvm" then "%I" else "%i";
164
165 unitConfig = {
166 AssertPathIsDirectory = mkIf (tCfg.lock != null) "/var/lock";
167 DefaultDependencies = false;
168 RequiresMountsFor = mkIf (cfg.snapshots == "lvm") [ "${mountPoint}/${withSuffix "%I"}" ];
169 };
170
171 serviceConfig = {
172 Type = "oneshot";
173 WorkingDirectory = if (cfg.snapshots == null) then "%I" else (if (cfg.snapshots == "lvm") then "${mountPoint}/${withSuffix "%I"}" else "${withSuffix "%f"}");
174 Nice = 15;
175 IOSchedulingClass = 2;
176 IOSchedulingPriority = 7;
177 SuccessExitStatus = [1 2];
178 Slice = "system-borgbackup.slice";
179 Environment = lib.mkIf (tCfg.keyFile != null) "BORG_KEY_FILE=${tCfg.keyFile}";
180 };
181 })) cfg.targets) // (mapAttrs' (target: tCfg: nameValuePair "borgbackup-prune-${target}" {
182 enable = tCfg.prune != {};
183
184 bindsTo = ["network-online.target"];
185 after = ["network-online.target"];
186
187 path = with pkgs; [borgbackup];
188
189 script = concatStringsSep "\n" (mapAttrsToList (path: args: ''
190 borg prune \
191 --lock-wait ${toString tCfg.lockWait} \
192 --list \
193 --stats \
194 --prefix ${escapeShellArg "${cfg.prefix}${path}"} \
195 ${escapeShellArgs args} \
196 ${tCfg.repo}
197 '') tCfg.prune);
198
199 serviceConfig = {
200 Type = "oneshot";
201 Slice = "system-borgbackup.slice";
202 Environment = lib.mkIf (tCfg.keyFile != null) "BORG_KEY_FILE=${tCfg.keyFile}";
203 };
204 }) cfg.targets);
205 };
206}