main
nix 193 lines 5.09 KB
Raw
1 {
2 lib,
3 config,
4 pkgs,
5 ...
6 }:
7
8 let
9 cfg = config.services.backup;
10
11 mkZfsPreHook = mountpoint: ''
12 DATASET="$(findmnt -nr -o source "${mountpoint}")"
13 zfs snapshot -r "$DATASET@borg"
14
15 # https://github.com/borgbackup/borg/issues/6652
16 ls ${mountpoint}/.zfs/snapshot/borg/ > /dev/null
17 '';
18
19 mkZfsPostHook = mountpoint: ''
20 DATASET="$(findmnt -nr -o source "${mountpoint}")"
21 zfs destroy -r "$DATASET@borg"
22 '';
23 in
24 {
25 options.services.backup =
26 with lib;
27 with types;
28 {
29 user = mkOption {
30 type = str;
31 description = ''
32 Username for the SSH remote host.
33 '';
34 };
35
36 host = mkOption {
37 type = str;
38 description = ''
39 Hostname of the SSH remote host.
40 '';
41 };
42
43 hostPublicKey = mkOption {
44 type = str;
45 example = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA5EB5p/5Hp3hGW1oHok+PIOH9Pbn7cnUiGmUEBrCVjnAw+HrKyN8bYVV0dIGllswYXwkG/+bgiBlE6IVIBAq+JwVWu1Sss3KarHY3OvFJUXZoZyRRg/Gc/+LRCE7lyKpwWQ70dbelGRyyJFH36eNv6ySXoUYtGkwlU5IVaHPApOxe4LHPZa/qhSRbPo2hwoh0orCtgejRebNtW5nlx00DNFgsvn8Svz2cIYLxsPVzKgUxs8Zxsxgn+Q/UvR7uq4AbAhyBMLxv7DjJ1pc7PJocuTno2Rw9uMZi1gkjbnmiOh6TTXIEWbnroyIhwc8555uto9melEUmWNQ+C+PwAK+MPw==";
46 description = ''
47 Public SSH host key of the remote host. Discoverable using e.g. `ssh-keyscan`.
48 '';
49 };
50
51 port = mkOption {
52 type = port;
53 default = 22;
54 description = ''
55 Port of the SSH remote host.
56 '';
57 apply = toString;
58 };
59
60 sshKey = mkOption {
61 type = path;
62 example = "/var/keys/ssh-key";
63 description = ''
64 Path to the SSH key required to access the remote host.
65 '';
66 };
67
68 secretPath = mkOption {
69 type = path;
70 example = "/var/keys/borg-secret";
71 description = ''
72 Path to the secret used to encrypt backups in the repository.
73 '';
74 };
75
76 quota = mkOption {
77 type = nullOr str;
78 default = null;
79 example = "90G";
80 description = ''
81 Quota for the borg repository. Useful to prevent the target disk from running full and ensuring borg keeps some space to work with.
82 '';
83 };
84
85 includes = mkOption {
86 type = listOf path;
87 default = [ ];
88 description = ''
89 Paths to include in the backup.
90 '';
91 };
92 includesZfsDatasets = mkOption {
93 type = listOf str;
94 default = [ ];
95 description = ''
96 ZFS datasets referenced by mountpoint to snapshot and include
97 '';
98 };
99
100 excludes = mkOption {
101 type = listOf path;
102 default = [ ];
103 description = ''
104 Paths to exclude in the backup.
105 '';
106 };
107
108 preHook = mkOption {
109 type = lines;
110 default = "";
111 description = ''
112 Shell commands to run before the backup.
113 '';
114 };
115
116 postHook = mkOption {
117 type = lines;
118 default = "";
119 description = ''
120 Shell commands to run after the backup.
121 '';
122 };
123
124 wantedUnits = mkOption {
125 type = listOf str;
126 default = [ ];
127 description = ''
128 List of units to require before starting the backup.
129 '';
130 };
131 };
132
133 config = lib.mkIf (cfg.includes != [ ] || cfg.includesZfsDatasets != [ ]) {
134 programs.ssh.knownHosts."${if cfg.port != 22 then "[${cfg.host}]:${cfg.port}" else cfg.host}" = {
135 publicKey = "${cfg.hostPublicKey}";
136 };
137
138 systemd.services.borgbackup-job-state = {
139 wants = cfg.wantedUnits;
140 after = cfg.wantedUnits;
141
142 path = lib.optionals (cfg.includesZfsDatasets != [ ]) [
143 config.boot.zfs.package
144 pkgs.util-linux
145 ];
146 };
147
148 systemd.timers.borgbackup-job-state.timerConfig = {
149 # Spread all backups over the day
150 RandomizedDelaySec = "24h";
151 FixedRandomDelay = true;
152 };
153
154 services.borgbackup.jobs.state = {
155 preHook = lib.concatMapStringsSep "\n" mkZfsPreHook cfg.includesZfsDatasets;
156 postHook = lib.concatMapStringsSep "\n" mkZfsPostHook cfg.includesZfsDatasets;
157
158 # Create the repo
159 doInit = true;
160
161 # Create daily backups, but prune to a reasonable amount
162 startAt = "daily";
163 prune.keep = {
164 daily = 7;
165 weekly = 4;
166 monthly = 3;
167 };
168
169 # What to backup
170 paths = cfg.includes ++ (map (mp: "${mp}/.zfs/snapshot/borg") cfg.includesZfsDatasets);
171 exclude = cfg.excludes;
172
173 # Where to backup it to
174 repo = "${cfg.user}@${cfg.host}:${config.networking.fqdn}";
175 environment.BORG_RSH = "ssh -p ${cfg.port} -i ${cfg.sshKey}";
176
177 # Ensure we don't fill up the destination disk
178 extraInitArgs = lib.optionalString (cfg.quota != null) "--storage-quota ${cfg.quota}";
179
180 # Authenticated & encrypted, key resides in the repository
181 encryption = {
182 mode = "repokey-blake2";
183 passCommand = "cat ${cfg.secretPath}";
184 };
185
186 # Reduce the backup size
187 compression = "auto,zstd";
188
189 # Show summary detailing data usage once completed
190 extraCreateArgs = "--stats";
191 };
192 };
193 }