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