1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.mysql;
8
9 mysql = cfg.package;
10
11 atLeast55 = versionAtLeast mysql.mysqlVersion "5.5";
12
13 pidFile = "${cfg.pidDir}/mysqld.pid";
14
15 mysqldOptions =
16 "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql} " +
17 "--pid-file=${pidFile}";
18
19 myCnf = pkgs.writeText "my.cnf"
20 ''
21 [mysqld]
22 port = ${toString cfg.port}
23 ${optionalString (cfg.replication.role == "master" || cfg.replication.role == "slave") "log-bin=mysql-bin"}
24 ${optionalString (cfg.replication.role == "master" || cfg.replication.role == "slave") "server-id = ${toString cfg.replication.serverId}"}
25 ${optionalString (cfg.replication.role == "slave" && !atLeast55)
26 ''
27 master-host = ${cfg.replication.masterHost}
28 master-user = ${cfg.replication.masterUser}
29 master-password = ${cfg.replication.masterPassword}
30 master-port = ${toString cfg.replication.masterPort}
31 ''}
32 ${cfg.extraOptions}
33 '';
34
35in
36
37{
38
39 ###### interface
40
41 options = {
42
43 services.mysql = {
44
45 enable = mkOption {
46 default = false;
47 description = "
48 Whether to enable the MySQL server.
49 ";
50 };
51
52 package = mkOption {
53 type = types.package;
54 example = literalExample "pkgs.mysql";
55 description = "
56 Which MySQL derivation to use.
57 ";
58 };
59
60 port = mkOption {
61 default = "3306";
62 description = "Port of MySQL";
63 };
64
65 user = mkOption {
66 default = "mysql";
67 description = "User account under which MySQL runs";
68 };
69
70 dataDir = mkOption {
71 default = "/var/mysql"; # !!! should be /var/db/mysql
72 description = "Location where MySQL stores its table files";
73 };
74
75 pidDir = mkOption {
76 default = "/run/mysqld";
77 description = "Location of the file which stores the PID of the MySQL server";
78 };
79
80 extraOptions = mkOption {
81 default = "";
82 example = ''
83 key_buffer_size = 6G
84 table_cache = 1600
85 log-error = /var/log/mysql_err.log
86 '';
87 description = ''
88 Provide extra options to the MySQL configuration file.
89
90 Please note, that these options are added to the
91 <literal>[mysqld]</literal> section so you don't need to explicitly
92 state it again.
93 '';
94 };
95
96 initialDatabases = mkOption {
97 default = [];
98 description = "List of database names and their initial schemas that should be used to create databases on the first startup of MySQL";
99 example = [
100 { name = "foodatabase"; schema = literalExample "./foodatabase.sql"; }
101 { name = "bardatabase"; schema = literalExample "./bardatabase.sql"; }
102 ];
103 };
104
105 initialScript = mkOption {
106 default = null;
107 description = "A file containing SQL statements to be executed on the first startup. Can be used for granting certain permissions on the database";
108 };
109
110 # FIXME: remove this option; it's a really bad idea.
111 rootPassword = mkOption {
112 default = null;
113 description = "Path to a file containing the root password, modified on the first startup. Not specifying a root password will leave the root password empty.";
114 };
115
116 replication = {
117 role = mkOption {
118 default = "none";
119 description = "Role of the MySQL server instance. Can be either: master, slave or none";
120 };
121
122 serverId = mkOption {
123 default = 1;
124 description = "Id of the MySQL server instance. This number must be unique for each instance";
125 };
126
127 masterHost = mkOption {
128 description = "Hostname of the MySQL master server";
129 };
130
131 masterUser = mkOption {
132 description = "Username of the MySQL replication user";
133 };
134
135 masterPassword = mkOption {
136 description = "Password of the MySQL replication user";
137 };
138
139 masterPort = mkOption {
140 default = 3306;
141 description = "Port number on which the MySQL master server runs";
142 };
143 };
144 };
145
146 };
147
148
149 ###### implementation
150
151 config = mkIf config.services.mysql.enable {
152
153 users.extraUsers.mysql = {
154 description = "MySQL server user";
155 group = "mysql";
156 uid = config.ids.uids.mysql;
157 };
158
159 users.extraGroups.mysql.gid = config.ids.gids.mysql;
160
161 environment.systemPackages = [mysql];
162
163 systemd.services.mysql =
164 { description = "MySQL Server";
165
166 wantedBy = [ "multi-user.target" ];
167
168 unitConfig.RequiresMountsFor = "${cfg.dataDir}";
169
170 path = [
171 # Needed for the mysql_install_db command in the preStart script
172 # which calls the hostname command.
173 pkgs.nettools
174 ];
175
176 preStart =
177 ''
178 if ! test -e ${cfg.dataDir}/mysql; then
179 mkdir -m 0700 -p ${cfg.dataDir}
180 chown -R ${cfg.user} ${cfg.dataDir}
181 ${mysql}/bin/mysql_install_db ${mysqldOptions}
182 touch /tmp/mysql_init
183 fi
184
185 mkdir -m 0755 -p ${cfg.pidDir}
186 chown -R ${cfg.user} ${cfg.pidDir}
187
188 # Make the socket directory
189 mkdir -p /run/mysqld
190 chmod 0755 /run/mysqld
191 chown -R ${cfg.user} /run/mysqld
192 '';
193
194 serviceConfig.ExecStart = "${mysql}/bin/mysqld --defaults-extra-file=${myCnf} ${mysqldOptions}";
195
196 postStart =
197 ''
198 # Wait until the MySQL server is available for use
199 count=0
200 while [ ! -e /run/mysqld/mysqld.sock ]
201 do
202 if [ $count -eq 30 ]
203 then
204 echo "Tried 30 times, giving up..."
205 exit 1
206 fi
207
208 echo "MySQL daemon not yet started. Waiting for 1 second..."
209 count=$((count++))
210 sleep 1
211 done
212
213 if [ -f /tmp/mysql_init ]
214 then
215 ${concatMapStrings (database:
216 ''
217 # Create initial databases
218 if ! test -e "${cfg.dataDir}/${database.name}"; then
219 echo "Creating initial database: ${database.name}"
220 ( echo "create database ${database.name};"
221 echo "use ${database.name};"
222
223 if [ -f "${database.schema}" ]
224 then
225 cat ${database.schema}
226 elif [ -d "${database.schema}" ]
227 then
228 cat ${database.schema}/mysql-databases/*.sql
229 fi
230 ) | ${mysql}/bin/mysql -u root -N
231 fi
232 '') cfg.initialDatabases}
233
234 ${optionalString (cfg.replication.role == "slave" && atLeast55)
235 ''
236 # Set up the replication master
237
238 ( echo "stop slave;"
239 echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
240 echo "start slave;"
241 ) | ${mysql}/bin/mysql -u root -N
242 ''}
243
244 ${optionalString (cfg.initialScript != null)
245 ''
246 # Execute initial script
247 cat ${cfg.initialScript} | ${mysql}/bin/mysql -u root -N
248 ''}
249
250 ${optionalString (cfg.rootPassword != null)
251 ''
252 # Change root password
253
254 ( echo "use mysql;"
255 echo "update user set Password=password('$(cat ${cfg.rootPassword})') where User='root';"
256 echo "flush privileges;"
257 ) | ${mysql}/bin/mysql -u root -N
258 ''}
259
260 rm /tmp/mysql_init
261 fi
262 ''; # */
263 };
264
265 };
266
267}