1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.borgmatic;
9 settingsFormat = pkgs.formats.yaml { };
10
11 postgresql = config.services.postgresql.package;
12 mysql = config.services.mysql.package;
13 requireSudo =
14 s:
15 s ? postgresql_databases
16 && lib.any (d: d ? username && !(d ? password) && !(d ? pg_dump_command)) s.postgresql_databases;
17 addRequiredBinaries =
18 s:
19 s
20 // (lib.optionalAttrs (s ? postgresql_databases && s.postgresql_databases != [ ]) {
21 postgresql_databases = map (
22 d:
23 let
24 as_user = if d ? username && !(d ? password) then "${pkgs.sudo}/bin/sudo -u ${d.username} " else "";
25 in
26 {
27 pg_dump_command =
28 if d.name == "all" && (!(d ? format) || isNull d.format) then
29 "${as_user}${postgresql}/bin/pg_dumpall"
30 else
31 "${as_user}${postgresql}/bin/pg_dump";
32 pg_restore_command = "${as_user}${postgresql}/bin/pg_restore";
33 psql_command = "${as_user}${postgresql}/bin/psql";
34 }
35 // d
36 ) s.postgresql_databases;
37 })
38 // (lib.optionalAttrs (s ? mariadb_databases && s.mariadb_databases != [ ]) {
39 mariadb_databases = map (
40 d:
41 {
42 mariadb_dump_command = "${mysql}/bin/mariadb-dump";
43 mariadb_command = "${mysql}/bin/mariadb";
44 }
45 // d
46 ) s.mariadb_databases;
47 })
48 // (lib.optionalAttrs (s ? mysql_databases && s.mysql_databases != [ ]) {
49 mysql_databases = map (
50 d:
51 {
52 mysql_dump_command = "${mysql}/bin/mysqldump";
53 mysql_command = "${mysql}/bin/mysql";
54 }
55 // d
56 ) s.mysql_databases;
57 });
58
59 repository =
60 with lib.types;
61 submodule {
62 options = {
63 path = lib.mkOption {
64 type = str;
65 description = ''
66 Path to the repository
67 '';
68 };
69 label = lib.mkOption {
70 type = str;
71 description = ''
72 Label to the repository
73 '';
74 };
75 };
76 };
77 cfgType =
78 with lib.types;
79 submodule {
80 freeformType = settingsFormat.type;
81 options = {
82 source_directories = lib.mkOption {
83 type = listOf str;
84 default = [ ];
85 description = ''
86 List of source directories and files to backup. Globs and tildes are
87 expanded. Do not backslash spaces in path names.
88 '';
89 example = [
90 "/home"
91 "/etc"
92 "/var/log/syslog*"
93 "/home/user/path with spaces"
94 ];
95 };
96 repositories = lib.mkOption {
97 type = listOf repository;
98 default = [ ];
99 description = ''
100 A required list of local or remote repositories with paths and
101 optional labels (which can be used with the --repository flag to
102 select a repository). Tildes are expanded. Multiple repositories are
103 backed up to in sequence. Borg placeholders can be used. See the
104 output of "borg help placeholders" for details. See ssh_command for
105 SSH options like identity file or port. If systemd service is used,
106 then add local repository paths in the systemd service file to the
107 ReadWritePaths list.
108 '';
109 example = [
110 {
111 path = "ssh://user@backupserver/./sourcehostname.borg";
112 label = "backupserver";
113 }
114 {
115 path = "/mnt/backup";
116 label = "local";
117 }
118 ];
119 };
120 };
121 };
122
123 cfgfile = settingsFormat.generate "config.yaml" (addRequiredBinaries cfg.settings);
124
125 anycfgRequiresSudo =
126 requireSudo cfg.settings || lib.any requireSudo (lib.attrValues cfg.configurations);
127in
128{
129 options.services.borgmatic = {
130 enable = lib.mkEnableOption "borgmatic";
131
132 settings = lib.mkOption {
133 description = ''
134 See <https://torsion.org/borgmatic/docs/reference/configuration/>
135 '';
136 default = null;
137 type = lib.types.nullOr cfgType;
138 };
139
140 configurations = lib.mkOption {
141 description = ''
142 Set of borgmatic configurations, see <https://torsion.org/borgmatic/docs/reference/configuration/>
143 '';
144 default = { };
145 type = lib.types.attrsOf cfgType;
146 };
147
148 enableConfigCheck = lib.mkEnableOption "checking all configurations during build time" // {
149 default = true;
150 };
151 };
152
153 config =
154 let
155 configFiles =
156 (lib.optionalAttrs (cfg.settings != null) {
157 "borgmatic/config.yaml".source = cfgfile;
158 })
159 // lib.mapAttrs' (
160 name: value:
161 lib.nameValuePair "borgmatic.d/${name}.yaml" {
162 source = settingsFormat.generate "${name}.yaml" (addRequiredBinaries value);
163 }
164 ) cfg.configurations;
165 borgmaticCheck =
166 name: f:
167 pkgs.runCommandCC "${name} validation" { } ''
168 ${pkgs.borgmatic}/bin/borgmatic -c ${f.source} config validate
169 touch $out
170 '';
171 in
172 lib.mkIf cfg.enable {
173
174 warnings =
175 [ ]
176 ++
177 lib.optional (cfg.settings != null && cfg.settings ? location)
178 "`services.borgmatic.settings.location` is deprecated, please move your options out of sections to the global scope"
179 ++
180 lib.optional (lib.catAttrs "location" (lib.attrValues cfg.configurations) != [ ])
181 "`services.borgmatic.configurations.<name>.location` is deprecated, please move your options out of sections to the global scope";
182
183 environment.systemPackages = [ pkgs.borgmatic ];
184
185 environment.etc = configFiles;
186
187 systemd.packages = [ pkgs.borgmatic ];
188 systemd.services.borgmatic.path = [ pkgs.coreutils ];
189 systemd.services.borgmatic.serviceConfig = lib.optionalAttrs anycfgRequiresSudo {
190 NoNewPrivileges = false;
191 CapabilityBoundingSet = "CAP_DAC_READ_SEARCH CAP_NET_RAW CAP_SETUID CAP_SETGID";
192 };
193
194 # Workaround: https://github.com/NixOS/nixpkgs/issues/81138
195 systemd.timers.borgmatic.wantedBy = [ "timers.target" ];
196
197 system.checks = lib.mkIf cfg.enableConfigCheck (lib.mapAttrsToList borgmaticCheck configFiles);
198 };
199}