at 16.09-beta 8.9 kB view raw
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 slaveHost = mkOption { 132 description = "Hostname of the MySQL slave server"; 133 }; 134 135 masterUser = mkOption { 136 description = "Username of the MySQL replication user"; 137 }; 138 139 masterPassword = mkOption { 140 description = "Password of the MySQL replication user"; 141 }; 142 143 masterPort = mkOption { 144 default = 3306; 145 description = "Port number on which the MySQL master server runs"; 146 }; 147 }; 148 }; 149 150 }; 151 152 153 ###### implementation 154 155 config = mkIf config.services.mysql.enable { 156 157 users.extraUsers.mysql = { 158 description = "MySQL server user"; 159 group = "mysql"; 160 uid = config.ids.uids.mysql; 161 }; 162 163 users.extraGroups.mysql.gid = config.ids.gids.mysql; 164 165 environment.systemPackages = [mysql]; 166 167 systemd.services.mysql = 168 { description = "MySQL Server"; 169 170 wantedBy = [ "multi-user.target" ]; 171 172 unitConfig.RequiresMountsFor = "${cfg.dataDir}"; 173 174 path = [ 175 # Needed for the mysql_install_db command in the preStart script 176 # which calls the hostname command. 177 pkgs.nettools 178 ]; 179 180 preStart = 181 '' 182 if ! test -e ${cfg.dataDir}/mysql; then 183 mkdir -m 0700 -p ${cfg.dataDir} 184 chown -R ${cfg.user} ${cfg.dataDir} 185 ${mysql}/bin/mysql_install_db ${mysqldOptions} 186 touch /tmp/mysql_init 187 fi 188 189 mkdir -m 0755 -p ${cfg.pidDir} 190 chown -R ${cfg.user} ${cfg.pidDir} 191 192 # Make the socket directory 193 mkdir -p /run/mysqld 194 chmod 0755 /run/mysqld 195 chown -R ${cfg.user} /run/mysqld 196 ''; 197 198 serviceConfig.ExecStart = "${mysql}/bin/mysqld --defaults-extra-file=${myCnf} ${mysqldOptions}"; 199 200 postStart = 201 '' 202 # Wait until the MySQL server is available for use 203 count=0 204 while [ ! -e /run/mysqld/mysqld.sock ] 205 do 206 if [ $count -eq 30 ] 207 then 208 echo "Tried 30 times, giving up..." 209 exit 1 210 fi 211 212 echo "MySQL daemon not yet started. Waiting for 1 second..." 213 count=$((count++)) 214 sleep 1 215 done 216 217 if [ -f /tmp/mysql_init ] 218 then 219 ${concatMapStrings (database: 220 '' 221 # Create initial databases 222 if ! test -e "${cfg.dataDir}/${database.name}"; then 223 echo "Creating initial database: ${database.name}" 224 ( echo "create database ${database.name};" 225 echo "use ${database.name};" 226 227 if [ -f "${database.schema}" ] 228 then 229 cat ${database.schema} 230 elif [ -d "${database.schema}" ] 231 then 232 cat ${database.schema}/mysql-databases/*.sql 233 fi 234 ) | ${mysql}/bin/mysql -u root -N 235 fi 236 '') cfg.initialDatabases} 237 238 ${optionalString (cfg.replication.role == "master" && atLeast55) 239 '' 240 # Set up the replication master 241 242 ( echo "use mysql;" 243 echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;" 244 echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');" 245 echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';" 246 ) | ${mysql}/bin/mysql -u root -N 247 ''} 248 249 ${optionalString (cfg.replication.role == "slave" && atLeast55) 250 '' 251 # Set up the replication slave 252 253 ( echo "stop slave;" 254 echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';" 255 echo "start slave;" 256 ) | ${mysql}/bin/mysql -u root -N 257 ''} 258 259 ${optionalString (cfg.initialScript != null) 260 '' 261 # Execute initial script 262 cat ${cfg.initialScript} | ${mysql}/bin/mysql -u root -N 263 ''} 264 265 ${optionalString (cfg.rootPassword != null) 266 '' 267 # Change root password 268 269 ( echo "use mysql;" 270 echo "update user set Password=password('$(cat ${cfg.rootPassword})') where User='root';" 271 echo "flush privileges;" 272 ) | ${mysql}/bin/mysql -u root -N 273 ''} 274 275 rm /tmp/mysql_init 276 fi 277 ''; # */ 278 }; 279 280 }; 281 282}