]>
Commit | Line | Data |
---|---|---|
1 | { pkgs, config, lib, ... }: | |
2 | let | |
3 | cfg = config.myServices.databasesReplication.mariadb; | |
4 | in | |
5 | { | |
6 | options.myServices.databasesReplication.mariadb = { | |
7 | enable = lib.mkEnableOption "Enable mariadb replication"; | |
8 | base = lib.mkOption { | |
9 | type = lib.types.path; | |
10 | description = '' | |
11 | Base path to put the replications | |
12 | ''; | |
13 | }; | |
14 | hosts = lib.mkOption { | |
15 | default = {}; | |
16 | description = '' | |
17 | Hosts to backup | |
18 | ''; | |
19 | type = lib.types.attrsOf (lib.types.submodule { | |
20 | options = { | |
21 | package = lib.mkOption { | |
22 | type = lib.types.package; | |
23 | default = pkgs.mariadb; | |
24 | description = '' | |
25 | Mariadb package for this host | |
26 | ''; | |
27 | }; | |
28 | serverId = lib.mkOption { | |
29 | type = lib.types.int; | |
30 | description = '' | |
31 | Server id to use for replication cluster (must be unique among the cluster!) | |
32 | ''; | |
33 | }; | |
34 | host = lib.mkOption { | |
35 | type = lib.types.str; | |
36 | description = '' | |
37 | Host to connect to | |
38 | ''; | |
39 | }; | |
40 | port = lib.mkOption { | |
41 | type = lib.types.int; | |
42 | description = '' | |
43 | Port to connect to | |
44 | ''; | |
45 | }; | |
46 | user = lib.mkOption { | |
47 | type = lib.types.str; | |
48 | description = '' | |
49 | User to connect as | |
50 | ''; | |
51 | }; | |
52 | password = lib.mkOption { | |
53 | type = lib.types.str; | |
54 | description = '' | |
55 | Password to use | |
56 | ''; | |
57 | }; | |
58 | dumpUser = lib.mkOption { | |
59 | type = lib.types.str; | |
60 | description = '' | |
61 | User who can do a dump | |
62 | ''; | |
63 | }; | |
64 | dumpPassword = lib.mkOption { | |
65 | type = lib.types.str; | |
66 | description = '' | |
67 | Password for the dump user | |
68 | ''; | |
69 | }; | |
70 | }; | |
71 | }); | |
72 | }; | |
73 | }; | |
74 | ||
75 | config = lib.mkIf cfg.enable { | |
76 | myServices.chatonsProperties.hostings.mysql-replication = { | |
77 | file.datetime = "2022-08-27T15:00:00"; | |
78 | hosting = { | |
79 | name = "Mysql replication"; | |
80 | description = "Replication of mysql database"; | |
81 | website = "db-1.immae.eu"; | |
82 | status.level = "OK"; | |
83 | status.description = "OK"; | |
84 | registration.load = "OPEN"; | |
85 | install.type = "PACKAGE"; | |
86 | }; | |
87 | software = { | |
88 | name = "MariaDB"; | |
89 | website = "https://mariadb.org/"; | |
90 | license.url = "https://github.com/MariaDB/server/blob/10.11/COPYING"; | |
91 | license.name = "GNU General Public License v2.0"; | |
92 | version = pkgs.mariadb.version; | |
93 | source.url = "https://github.com/MariaDB/server"; | |
94 | }; | |
95 | }; | |
96 | users.users.mysql = { | |
97 | description = "MySQL server user"; | |
98 | group = "mysql"; | |
99 | uid = config.ids.uids.mysql; | |
100 | extraGroups = [ "keys" ]; | |
101 | }; | |
102 | users.groups.mysql.gid = config.ids.gids.mysql; | |
103 | ||
104 | secrets.keys = lib.listToAttrs (lib.flatten (lib.mapAttrsToList (name: hcfg: [ | |
105 | (lib.nameValuePair "mysql_replication/${name}/slave_init_commands" { | |
106 | user = "mysql"; | |
107 | group = "mysql"; | |
108 | permissions = "0400"; | |
109 | text = '' | |
110 | CHANGE MASTER TO master_host="${hcfg.host}", master_port=${builtins.toString hcfg.port}, master_user="${hcfg.user}", master_password="${hcfg.password}", master_ssl=1, master_use_gtid=slave_pos; | |
111 | START SLAVE; | |
112 | ''; | |
113 | }) | |
114 | (lib.nameValuePair "mysql_replication/${name}/mysqldump_remote" { | |
115 | permissions = "0400"; | |
116 | user = "root"; | |
117 | group = "root"; | |
118 | text = '' | |
119 | [mysqldump] | |
120 | user = ${hcfg.user} | |
121 | password = ${hcfg.password} | |
122 | ''; | |
123 | }) | |
124 | (lib.nameValuePair "mysql_replication/${name}/mysqldump" { | |
125 | permissions = "0400"; | |
126 | user = "root"; | |
127 | group = "root"; | |
128 | text = '' | |
129 | [mysqldump] | |
130 | user = ${hcfg.dumpUser} | |
131 | password = ${hcfg.dumpPassword} | |
132 | ''; | |
133 | }) | |
134 | (lib.nameValuePair "mysql_replication/${name}/client" { | |
135 | permissions = "0400"; | |
136 | user = "mysql"; | |
137 | group = "mysql"; | |
138 | text = '' | |
139 | [client] | |
140 | user = ${hcfg.dumpUser} | |
141 | password = ${hcfg.dumpPassword} | |
142 | ''; | |
143 | }) | |
144 | ]) cfg.hosts)); | |
145 | ||
146 | services.cron = { | |
147 | enable = true; | |
148 | systemCronJobs = lib.flatten (lib.mapAttrsToList (name: hcfg: | |
149 | let | |
150 | dataDir = "${cfg.base}/${name}/mysql"; | |
151 | backupDir = "${cfg.base}/${name}/mysql_backup"; | |
152 | backup_script = pkgs.writeScript "backup_mysql_${name}" '' | |
153 | #!${pkgs.stdenv.shell} | |
154 | ||
155 | set -euo pipefail | |
156 | ||
157 | filename=${backupDir}/$(${pkgs.coreutils}/bin/date -Iminutes).sql | |
158 | ${hcfg.package}/bin/mysqldump \ | |
159 | --defaults-file=${config.secrets.fullPaths."mysql_replication/${name}/mysqldump"} \ | |
160 | -S /run/mysqld_${name}/mysqld.sock \ | |
161 | --gtid \ | |
162 | --master-data \ | |
163 | --flush-privileges \ | |
164 | --all-databases > $filename | |
165 | ${pkgs.gzip}/bin/gzip $filename | |
166 | ''; | |
167 | u = pkgs.callPackage ./utils.nix {}; | |
168 | cleanup_script = pkgs.writeScript "cleanup_mysql_${name}" (u.exponentialDumps "sql.gz" backupDir); | |
169 | in [ | |
170 | "0 22,4,10,16 * * * root ${backup_script}" | |
171 | "0 3 * * * root ${cleanup_script}" | |
172 | ]) cfg.hosts); | |
173 | }; | |
174 | ||
175 | system.activationScripts = lib.attrsets.mapAttrs' (name: hcfg: | |
176 | lib.attrsets.nameValuePair "mysql_replication_${name}" { | |
177 | deps = [ "users" "groups" ]; | |
178 | text = '' | |
179 | install -m 0700 -o mysql -g mysql -d ${cfg.base}/${name}/mysql | |
180 | install -m 0700 -o mysql -g mysql -d ${cfg.base}/${name}/mysql_backup | |
181 | ''; | |
182 | }) cfg.hosts; | |
183 | ||
184 | environment.etc = lib.attrsets.mapAttrs' (name: hcfg: | |
185 | lib.attrsets.nameValuePair "mysql/${name}_my.cnf" { | |
186 | text = '' | |
187 | [mysqld] | |
188 | skip-networking | |
189 | socket = /run/mysqld_${name}/mysqld.sock | |
190 | datadir = ${cfg.base}/${name}/mysql/ | |
191 | log-bin = mariadb-bin | |
192 | server-id = ${builtins.toString hcfg.serverId} | |
193 | ''; | |
194 | } | |
195 | ) cfg.hosts; | |
196 | ||
197 | environment.systemPackages = lib.mapAttrsToList (name: hcfg: | |
198 | pkgs.writeScriptBin "mysql_backup_${name}" '' | |
199 | #!${pkgs.stdenv.shell} | |
200 | ||
201 | exec ${hcfg.package}/bin/mysql -S /run/mysqld_${name}/mysqld.sock "$@" | |
202 | '' | |
203 | ) cfg.hosts; | |
204 | ||
205 | systemd.services = lib.attrsets.mapAttrs' (name: hcfg: | |
206 | let | |
207 | dataDir = "${cfg.base}/${name}/mysql"; | |
208 | in | |
209 | lib.attrsets.nameValuePair "mysql_backup_${name}" { | |
210 | description = "Mysql replication for ${name}"; | |
211 | wantedBy = [ "multi-user.target" ]; | |
212 | after = [ "network.target" ]; | |
213 | restartTriggers = [ config.environment.etc."mysql/${name}_my.cnf".source ]; | |
214 | unitConfig.RequiresMountsFor = dataDir; | |
215 | ||
216 | preStart = '' | |
217 | if ! test -e ${dataDir}/mysql; then | |
218 | if ! test -e ${dataDir}/initial.sql; then | |
219 | ${hcfg.package}/bin/mysqldump \ | |
220 | --defaults-file=${config.secrets.fullPaths."mysql_replication/${name}/mysqldump_remote"} \ | |
221 | -h ${hcfg.host} \ | |
222 | -P ${builtins.toString hcfg.port} \ | |
223 | --ssl \ | |
224 | --gtid \ | |
225 | --flush-privileges \ | |
226 | --master-data \ | |
227 | --all-databases > ${dataDir}/initial.sql | |
228 | fi | |
229 | ||
230 | ${hcfg.package}/bin/mysql_install_db \ | |
231 | --defaults-file=/etc/mysql/${name}_my.cnf \ | |
232 | --user=mysql \ | |
233 | --datadir=${dataDir} \ | |
234 | --basedir=${hcfg.package} | |
235 | fi | |
236 | ''; | |
237 | ||
238 | serviceConfig = { | |
239 | User = "mysql"; | |
240 | Group = "mysql"; | |
241 | RuntimeDirectory = "mysqld_${name}"; | |
242 | RuntimeDirectoryMode = "0755"; | |
243 | SupplementaryGroups = "keys"; | |
244 | PermissionsStartOnly = true; | |
245 | Type = "notify"; | |
246 | ||
247 | ExecStart = "${hcfg.package}/bin/mysqld --defaults-file=/etc/mysql/${name}_my.cnf --user=mysql --datadir=${dataDir} --basedir=${hcfg.package}"; | |
248 | ExecStartPost = | |
249 | let | |
250 | sql_before = pkgs.writeText "mysql-initial-before" '' | |
251 | DROP DATABASE test; | |
252 | INSTALL SONAME 'auth_pam'; | |
253 | ''; | |
254 | setupScript = pkgs.writeScript "mysql-setup" '' | |
255 | #!${pkgs.runtimeShell} -e | |
256 | ||
257 | if test -e ${dataDir}/initial.sql; then | |
258 | cat \ | |
259 | ${sql_before} \ | |
260 | ${dataDir}/initial.sql \ | |
261 | ${config.secrets.fullPaths."mysql_replication/${name}/slave_init_commands"} \ | |
262 | | ${hcfg.package}/bin/mysql \ | |
263 | --defaults-file=/etc/mysql/${name}_my.cnf \ | |
264 | -S /run/mysqld_${name}/mysqld.sock \ | |
265 | --user=root | |
266 | rm -f ${dataDir}/initial.sql | |
267 | fi | |
268 | ''; | |
269 | in | |
270 | "+${setupScript}"; | |
271 | # initial dump can take a long time | |
272 | TimeoutStartSec="infinity"; | |
273 | TimeoutStopSec = 120; | |
274 | }; | |
275 | }) cfg.hosts; | |
276 | }; | |
277 | } | |
278 |