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}