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