git.iko.soy/typeable/nixpkgs
on master help sign in
Merge pull request #151255 from aanderse/nixos/mysql-cleanup

nixos/mysql: module cleanup

commit9ec14cd78d0618d6e3b1e68e257da262e05a3023 authorAaron Andersen <aaron@fosslib.net> dateSat 25 Dec 2021, 17:04 (5 years ago) parentbaa0e61d621ad0
1 file changed+185−195
nixos/modules/services/databases/mysql.nix+185−195
nixos/modules/services/databases/mysql.nix+185−195
@@ -11,10 +11,8 @@ let
1111 mysqldOptions =
1212 "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${cfg.package}";
1313
14− settingsFile = pkgs.writeText "my.cnf" (
15− generators.toINI { listsAsDuplicateKeys = true; } cfg.settings +
16− optionalString (cfg.extraOptions != null) "[mysqld]\n${cfg.extraOptions}"
17− );
14+ format = pkgs.formats.ini { listsAsDuplicateKeys = true; };
15+ configFile = format.generate "my.cnf" cfg.settings;
1816
1917in
2018
@@ -22,6 +20,9 @@ in
2220 imports = [
2321 (mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd.")
2422 (mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.")
23+ (mkRemovedOptionModule [ "services" "mysql" "extraOptions" ] "Use services.mysql.settings.mysqld instead.")
24+ (mkRemovedOptionModule [ "services" "mysql" "bind" ] "Use services.mysql.settings.mysqld.bind-address instead.")
25+ (mkRemovedOptionModule [ "services" "mysql" "port" ] "Use services.mysql.settings.mysqld.port instead.")
2526 ];
2627
2728 ###### interface
@@ -40,41 +41,53 @@ in
4041 ";
4142 };
4243
43− bind = mkOption {
44− type = types.nullOr types.str;
45− default = null;
46− example = "0.0.0.0";
47− description = "Address to bind to. The default is to bind to all addresses.";
48− };
49−
50− port = mkOption {
51− type = types.port;
52− default = 3306;
53− description = "Port of MySQL.";
54− };
55−
5644 user = mkOption {
5745 type = types.str;
5846 default = "mysql";
59− description = "User account under which MySQL runs.";
47+ description = ''
48+ User account under which MySQL runs.
49+
50+ <note><para>
51+ If left as the default value this user will automatically be created
52+ on system activation, otherwise you are responsible for
53+ ensuring the user exists before the MySQL service starts.
54+ </para></note>
55+ '';
6056 };
6157
6258 group = mkOption {
6359 type = types.str;
6460 default = "mysql";
65− description = "Group under which MySQL runs.";
61+ description = ''
62+ Group account under which MySQL runs.
63+
64+ <note><para>
65+ If left as the default value this group will automatically be created
66+ on system activation, otherwise you are responsible for
67+ ensuring the user exists before the MySQL service starts.
68+ </para></note>
69+ '';
6670 };
6771
6872 dataDir = mkOption {
6973 type = types.path;
7074 example = "/var/lib/mysql";
71− description = "Location where MySQL stores its table files.";
75+ description = ''
76+ The data directory for MySQL.
77+
78+ <note><para>
79+ If left as the default value of <literal>/var/lib/mysql</literal> this directory will automatically be created before the MySQL
80+ server starts, otherwise you are responsible for ensuring the directory exists with appropriate ownership and permissions.
81+ </para></note>
82+ '';
7283 };
7384
7485 configFile = mkOption {
7586 type = types.path;
76− default = settingsFile;
77− defaultText = literalExpression "settingsFile";
87+ default = configFile;
88+ defaultText = ''
89+ A configuration file automatically generated by NixOS.
90+ '';
7891 description = ''
7992 Override the configuration file used by MySQL. By default,
8093 NixOS generates one automatically from <option>services.mysql.settings</option>.
@@ -92,7 +105,7 @@ in
92105 };
93106
94107 settings = mkOption {
95− type = with types; attrsOf (attrsOf (oneOf [ bool int str (listOf str) ]));
108+ type = format.type;
96109 default = {};
97110 description = ''
98111 MySQL configuration. Refer to
@@ -125,23 +138,6 @@ in
125138 '';
126139 };
127140
128− extraOptions = mkOption {
129− type = with types; nullOr lines;
130− default = null;
131− example = ''
132− key_buffer_size = 6G
133− table_cache = 1600
134− log-error = /var/log/mysql_err.log
135− '';
136− description = ''
137− Provide extra options to the MySQL configuration file.
138−
139− Please note, that these options are added to the
140− <literal>[mysqld]</literal> section so you don't need to explicitly
141− state it again.
142− '';
143− };
144−
145141 initialDatabases = mkOption {
146142 type = types.listOf (types.submodule {
147143 options = {
@@ -287,7 +283,7 @@ in
287283 };
288284
289285 masterPort = mkOption {
290− type = types.int;
286+ type = types.port;
291287 default = 3306;
292288 description = "Port number on which the MySQL master server runs.";
293289 };
@@ -299,9 +295,7 @@ in
299295
300296 ###### implementation
301297
302− config = mkIf config.services.mysql.enable {
303−
304− warnings = optional (cfg.extraOptions != null) "services.mysql.`extraOptions` is deprecated, please use services.mysql.`settings`.";
298+ config = mkIf cfg.enable {
305299
306300 services.mysql.dataDir =
307301 mkDefault (if versionAtLeast config.system.stateVersion "17.09" then "/var/lib/mysql"
@@ -310,8 +304,7 @@ in
310304 services.mysql.settings.mysqld = mkMerge [
311305 {
312306 datadir = cfg.dataDir;
313− bind-address = mkIf (cfg.bind != null) cfg.bind;
314− port = cfg.port;
307+ port = mkDefault 3306;
315308 }
316309 (mkIf (cfg.replication.role == "master" || cfg.replication.role == "slave") {
317310 log-bin = "mysql-bin-${toString cfg.replication.serverId}";
@@ -341,156 +334,150 @@ in
341334
342335 environment.etc."my.cnf".source = cfg.configFile;
343336
344− systemd.tmpfiles.rules = [
345− "d '${cfg.dataDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
346− "z '${cfg.dataDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
347− ];
348−
349− systemd.services.mysql = let
350− hasNotify = isMariaDB;
351− in {
352− description = "MySQL Server";
353−
354− after = [ "network.target" ];
355− wantedBy = [ "multi-user.target" ];
356− restartTriggers = [ cfg.configFile ];
357−
358− unitConfig.RequiresMountsFor = "${cfg.dataDir}";
359−
360− path = [
361− # Needed for the mysql_install_db command in the preStart script
362− # which calls the hostname command.
363− pkgs.nettools
364− ];
365−
366− preStart = if isMariaDB then ''
367− if ! test -e ${cfg.dataDir}/mysql; then
368− ${cfg.package}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${mysqldOptions}
369− touch ${cfg.dataDir}/mysql_init
370− fi
371− '' else ''
372− if ! test -e ${cfg.dataDir}/mysql; then
373− ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} --initialize-insecure
374− touch ${cfg.dataDir}/mysql_init
375− fi
376− '';
377−
378− script = ''
379− # https://mariadb.com/kb/en/getting-started-with-mariadb-galera-cluster/#systemd-and-galera-recovery
380− if test -n "''${_WSREP_START_POSITION}"; then
381− if test -e "${cfg.package}/bin/galera_recovery"; then
382− VAR=$(cd ${cfg.package}/bin/..; ${cfg.package}/bin/galera_recovery); [[ $? -eq 0 ]] && export _WSREP_START_POSITION=$VAR || exit 1
383− fi
384− fi
385−
386− # The last two environment variables are used for starting Galera clusters
387− exec ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION
388− '';
389−
390− postStart = let
391− # The super user account to use on *first* run of MySQL server
392− superUser = if isMariaDB then cfg.user else "root";
393− in ''
394− ${optionalString (!hasNotify) ''
395− # Wait until the MySQL server is available for use
396− count=0
397− while [ ! -e /run/mysqld/mysqld.sock ]
398− do
399− if [ $count -eq 30 ]
400− then
401− echo "Tried 30 times, giving up..."
402− exit 1
403− fi
404−
405− echo "MySQL daemon not yet started. Waiting for 1 second..."
406− count=$((count++))
407− sleep 1
408− done
409− ''}
410−
411− if [ -f ${cfg.dataDir}/mysql_init ]
412− then
413− # While MariaDB comes with a 'mysql' super user account since 10.4.x, MySQL does not
414− # Since we don't want to run this service as 'root' we need to ensure the account exists on first run
415− ( echo "CREATE USER IF NOT EXISTS '${cfg.user}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
416− echo "GRANT ALL PRIVILEGES ON *.* TO '${cfg.user}'@'localhost' WITH GRANT OPTION;"
417− ) | ${cfg.package}/bin/mysql -u ${superUser} -N
418−
419− ${concatMapStrings (database: ''
420− # Create initial databases
421− if ! test -e "${cfg.dataDir}/${database.name}"; then
422− echo "Creating initial database: ${database.name}"
423− ( echo 'create database `${database.name}`;'
424−
425− ${optionalString (database.schema != null) ''
426− echo 'use `${database.name}`;'
427−
428− # TODO: this silently falls through if database.schema does not exist,
429− # we should catch this somehow and exit, but can't do it here because we're in a subshell.
430− if [ -f "${database.schema}" ]
431− then
432− cat ${database.schema}
433− elif [ -d "${database.schema}" ]
434− then
435− cat ${database.schema}/mysql-databases/*.sql
436− fi
437− ''}
438− ) | ${cfg.package}/bin/mysql -u ${superUser} -N
439− fi
440− '') cfg.initialDatabases}
441−
442− ${optionalString (cfg.replication.role == "master")
443− ''
444− # Set up the replication master
445−
446− ( echo "use mysql;"
447− echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
448− echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
449− echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
450− ) | ${cfg.package}/bin/mysql -u ${superUser} -N
451− ''}
452−
453− ${optionalString (cfg.replication.role == "slave")
454− ''
455− # Set up the replication slave
456−
457− ( echo "stop slave;"
458− echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
459− echo "start slave;"
460− ) | ${cfg.package}/bin/mysql -u ${superUser} -N
461− ''}
462−
463− ${optionalString (cfg.initialScript != null)
464− ''
465− # Execute initial script
466− # using toString to avoid copying the file to nix store if given as path instead of string,
467− # as it might contain credentials
468− cat ${toString cfg.initialScript} | ${cfg.package}/bin/mysql -u ${superUser} -N
469− ''}
470−
471− rm ${cfg.dataDir}/mysql_init
337+ systemd.services.mysql = {
338+ description = "MySQL Server";
339+
340+ after = [ "network.target" ];
341+ wantedBy = [ "multi-user.target" ];
342+ restartTriggers = [ cfg.configFile ];
343+
344+ unitConfig.RequiresMountsFor = cfg.dataDir;
345+
346+ path = [
347+ # Needed for the mysql_install_db command in the preStart script
348+ # which calls the hostname command.
349+ pkgs.nettools
350+ ];
351+
352+ preStart = if isMariaDB then ''
353+ if ! test -e ${cfg.dataDir}/mysql; then
354+ ${cfg.package}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${mysqldOptions}
355+ touch ${cfg.dataDir}/mysql_init
356+ fi
357+ '' else ''
358+ if ! test -e ${cfg.dataDir}/mysql; then
359+ ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} --initialize-insecure
360+ touch ${cfg.dataDir}/mysql_init
361+ fi
362+ '';
363+
364+ script = ''
365+ # https://mariadb.com/kb/en/getting-started-with-mariadb-galera-cluster/#systemd-and-galera-recovery
366+ if test -n "''${_WSREP_START_POSITION}"; then
367+ if test -e "${cfg.package}/bin/galera_recovery"; then
368+ VAR=$(cd ${cfg.package}/bin/..; ${cfg.package}/bin/galera_recovery); [[ $? -eq 0 ]] && export _WSREP_START_POSITION=$VAR || exit 1
472369 fi
370+ fi
371+
372+ # The last two environment variables are used for starting Galera clusters
373+ exec ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION
374+ '';
375+
376+ postStart = let
377+ # The super user account to use on *first* run of MySQL server
378+ superUser = if isMariaDB then cfg.user else "root";
379+ in ''
380+ ${optionalString (!isMariaDB) ''
381+ # Wait until the MySQL server is available for use
382+ count=0
383+ while [ ! -e /run/mysqld/mysqld.sock ]
384+ do
385+ if [ $count -eq 30 ]
386+ then
387+ echo "Tried 30 times, giving up..."
388+ exit 1
389+ fi
390+
391+ echo "MySQL daemon not yet started. Waiting for 1 second..."
392+ count=$((count++))
393+ sleep 1
394+ done
395+ ''}
396+
397+ if [ -f ${cfg.dataDir}/mysql_init ]
398+ then
399+ # While MariaDB comes with a 'mysql' super user account since 10.4.x, MySQL does not
400+ # Since we don't want to run this service as 'root' we need to ensure the account exists on first run
401+ ( echo "CREATE USER IF NOT EXISTS '${cfg.user}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
402+ echo "GRANT ALL PRIVILEGES ON *.* TO '${cfg.user}'@'localhost' WITH GRANT OPTION;"
403+ ) | ${cfg.package}/bin/mysql -u ${superUser} -N
473404
474− ${optionalString (cfg.ensureDatabases != []) ''
475− (
476405 ${concatMapStrings (database: ''
477− echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
478− '') cfg.ensureDatabases}
406+ # Create initial databases
407+ if ! test -e "${cfg.dataDir}/${database.name}"; then
408+ echo "Creating initial database: ${database.name}"
409+ ( echo 'create database `${database.name}`;'
410+
411+ ${optionalString (database.schema != null) ''
412+ echo 'use `${database.name}`;'
413+
414+ # TODO: this silently falls through if database.schema does not exist,
415+ # we should catch this somehow and exit, but can't do it here because we're in a subshell.
416+ if [ -f "${database.schema}" ]
417+ then
418+ cat ${database.schema}
419+ elif [ -d "${database.schema}" ]
420+ then
421+ cat ${database.schema}/mysql-databases/*.sql
422+ fi
423+ ''}
424+ ) | ${cfg.package}/bin/mysql -u ${superUser} -N
425+ fi
426+ '') cfg.initialDatabases}
427+
428+ ${optionalString (cfg.replication.role == "master")
429+ ''
430+ # Set up the replication master
431+
432+ ( echo "use mysql;"
433+ echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
434+ echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
435+ echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
436+ ) | ${cfg.package}/bin/mysql -u ${superUser} -N
437+ ''}
438+
439+ ${optionalString (cfg.replication.role == "slave")
440+ ''
441+ # Set up the replication slave
442+
443+ ( echo "stop slave;"
444+ echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
445+ echo "start slave;"
446+ ) | ${cfg.package}/bin/mysql -u ${superUser} -N
447+ ''}
448+
449+ ${optionalString (cfg.initialScript != null)
450+ ''
451+ # Execute initial script
452+ # using toString to avoid copying the file to nix store if given as path instead of string,
453+ # as it might contain credentials
454+ cat ${toString cfg.initialScript} | ${cfg.package}/bin/mysql -u ${superUser} -N
455+ ''}
456+
457+ rm ${cfg.dataDir}/mysql_init
458+ fi
459+
460+ ${optionalString (cfg.ensureDatabases != []) ''
461+ (
462+ ${concatMapStrings (database: ''
463+ echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
464+ '') cfg.ensureDatabases}
465+ ) | ${cfg.package}/bin/mysql -N
466+ ''}
467+
468+ ${concatMapStrings (user:
469+ ''
470+ ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
471+ ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
472+ echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
473+ '') user.ensurePermissions)}
479474 ) | ${cfg.package}/bin/mysql -N
480− ''}
481−
482− ${concatMapStrings (user:
483− ''
484− ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
485− ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
486− echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
487− '') user.ensurePermissions)}
488− ) | ${cfg.package}/bin/mysql -N
489− '') cfg.ensureUsers}
490− '';
475+ '') cfg.ensureUsers}
476+ '';
491477
492− serviceConfig = {
493− Type = if hasNotify then "notify" else "simple";
478+ serviceConfig = mkMerge [
479+ {
480+ Type = if isMariaDB then "notify" else "simple";
494481 Restart = "on-abort";
495482 RestartSec = "5s";
496483
@@ -523,9 +510,12 @@ in
523510 PrivateMounts = true;
524511 # System Call Filtering
525512 SystemCallArchitectures = "native";
526− };
527− };
528−
513+ }
514+ (mkIf (cfg.dataDir == "/var/lib/mysql") {
515+ StateDirectory = "mysql";
516+ StateDirectoryMode = "0700";
517+ })
518+ ];
519+ };
529520 };
530−
531521}