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 preStart =
171 ''
172 if ! test -e ${cfg.dataDir}/mysql; then
173 mkdir -m 0700 -p ${cfg.dataDir}
174 chown -R ${cfg.user} ${cfg.dataDir}
175 ${mysql}/bin/mysql_install_db ${mysqldOptions}
176 touch /tmp/mysql_init
177 fi
178
179 mkdir -m 0755 -p ${cfg.pidDir}
180 chown -R ${cfg.user} ${cfg.pidDir}
181
182 # Make the socket directory
183 mkdir -p /run/mysqld
184 chmod 0755 /run/mysqld
185 chown -R ${cfg.user} /run/mysqld
186 '';
187
188 serviceConfig.ExecStart = "${mysql}/bin/mysqld --defaults-extra-file=${myCnf} ${mysqldOptions}";
189
190 postStart =
191 ''
192 # Wait until the MySQL server is available for use
193 count=0
194 while [ ! -e /run/mysqld/mysqld.sock ]
195 do
196 if [ $count -eq 30 ]
197 then
198 echo "Tried 30 times, giving up..."
199 exit 1
200 fi
201
202 echo "MySQL daemon not yet started. Waiting for 1 second..."
203 count=$((count++))
204 sleep 1
205 done
206
207 if [ -f /tmp/mysql_init ]
208 then
209 ${concatMapStrings (database:
210 ''
211 # Create initial databases
212 if ! test -e "${cfg.dataDir}/${database.name}"; then
213 echo "Creating initial database: ${database.name}"
214 ( echo "create database ${database.name};"
215 echo "use ${database.name};"
216
217 if [ -f "${database.schema}" ]
218 then
219 cat ${database.schema}
220 elif [ -d "${database.schema}" ]
221 then
222 cat ${database.schema}/mysql-databases/*.sql
223 fi
224 ) | ${mysql}/bin/mysql -u root -N
225 fi
226 '') cfg.initialDatabases}
227
228 ${optionalString (cfg.replication.role == "slave" && atLeast55)
229 ''
230 # Set up the replication master
231
232 ( echo "stop slave;"
233 echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
234 echo "start slave;"
235 ) | ${mysql}/bin/mysql -u root -N
236 ''}
237
238 ${optionalString (cfg.initialScript != null)
239 ''
240 # Execute initial script
241 cat ${cfg.initialScript} | ${mysql}/bin/mysql -u root -N
242 ''}
243
244 ${optionalString (cfg.rootPassword != null)
245 ''
246 # Change root password
247
248 ( echo "use mysql;"
249 echo "update user set Password=password('$(cat ${cfg.rootPassword})') where User='root';"
250 echo "flush privileges;"
251 ) | ${mysql}/bin/mysql -u root -N
252 ''}
253
254 rm /tmp/mysql_init
255 fi
256 ''; # */
257 };
258
259 };
260
261}