summaryrefslogtreecommitdiffstats
path: root/nixos
diff options
context:
space:
mode:
authorAaron Andersen <aaron@fosslib.net>2021-12-25 17:04:35 -0500
committerGitHub <noreply@github.com>2021-12-25 17:04:35 -0500
commit9ec14cd78d0618d6e3b1e68e257da262e05a3023 (patch)
tree43809dbf4d410d5a971cf423a2e835587129379a /nixos
parentbaa0e61569468570b07025c06443f7dc19dee105 (diff)
parentd621ad09a8c51d0d17c6beebc9d5c4d4a7a2b188 (diff)
Merge pull request #151255 from aanderse/nixos/mysql-cleanup
nixos/mysql: module cleanup
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/services/databases/mysql.nix380
1 files changed, 185 insertions, 195 deletions
diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index a9d9a6d80588..625b31d081c9 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -11,10 +11,8 @@ let
mysqldOptions =
"--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${cfg.package}";
- settingsFile = pkgs.writeText "my.cnf" (
- generators.toINI { listsAsDuplicateKeys = true; } cfg.settings +
- optionalString (cfg.extraOptions != null) "[mysqld]\n${cfg.extraOptions}"
- );
+ format = pkgs.formats.ini { listsAsDuplicateKeys = true; };
+ configFile = format.generate "my.cnf" cfg.settings;
in
@@ -22,6 +20,9 @@ in
imports = [
(mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd.")
(mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.")
+ (mkRemovedOptionModule [ "services" "mysql" "extraOptions" ] "Use services.mysql.settings.mysqld instead.")
+ (mkRemovedOptionModule [ "services" "mysql" "bind" ] "Use services.mysql.settings.mysqld.bind-address instead.")
+ (mkRemovedOptionModule [ "services" "mysql" "port" ] "Use services.mysql.settings.mysqld.port instead.")
];
###### interface
@@ -40,41 +41,53 @@ in
";
};
- bind = mkOption {
- type = types.nullOr types.str;
- default = null;
- example = "0.0.0.0";
- description = "Address to bind to. The default is to bind to all addresses.";
- };
-
- port = mkOption {
- type = types.port;
- default = 3306;
- description = "Port of MySQL.";
- };
-
user = mkOption {
type = types.str;
default = "mysql";
- description = "User account under which MySQL runs.";
+ description = ''
+ User account under which MySQL runs.
+
+ <note><para>
+ If left as the default value this user will automatically be created
+ on system activation, otherwise you are responsible for
+ ensuring the user exists before the MySQL service starts.
+ </para></note>
+ '';
};
group = mkOption {
type = types.str;
default = "mysql";
- description = "Group under which MySQL runs.";
+ description = ''
+ Group account under which MySQL runs.
+
+ <note><para>
+ If left as the default value this group will automatically be created
+ on system activation, otherwise you are responsible for
+ ensuring the user exists before the MySQL service starts.
+ </para></note>
+ '';
};
dataDir = mkOption {
type = types.path;
example = "/var/lib/mysql";
- description = "Location where MySQL stores its table files.";
+ description = ''
+ The data directory for MySQL.
+
+ <note><para>
+ If left as the default value of <literal>/var/lib/mysql</literal> this directory will automatically be created before the MySQL
+ server starts, otherwise you are responsible for ensuring the directory exists with appropriate ownership and permissions.
+ </para></note>
+ '';
};
configFile = mkOption {
type = types.path;
- default = settingsFile;
- defaultText = literalExpression "settingsFile";
+ default = configFile;
+ defaultText = ''
+ A configuration file automatically generated by NixOS.
+ '';
description = ''
Override the configuration file used by MySQL. By default,
NixOS generates one automatically from <option>services.mysql.settings</option>.
@@ -92,7 +105,7 @@ in
};
settings = mkOption {
- type = with types; attrsOf (attrsOf (oneOf [ bool int str (listOf str) ]));
+ type = format.type;
default = {};
description = ''
MySQL configuration. Refer to
@@ -125,23 +138,6 @@ in
'';
};
- extraOptions = mkOption {
- type = with types; nullOr lines;
- default = null;
- example = ''
- key_buffer_size = 6G
- table_cache = 1600
- log-error = /var/log/mysql_err.log
- '';
- description = ''
- Provide extra options to the MySQL configuration file.
-
- Please note, that these options are added to the
- <literal>[mysqld]</literal> section so you don't need to explicitly
- state it again.
- '';
- };
-
initialDatabases = mkOption {
type = types.listOf (types.submodule {
options = {
@@ -287,7 +283,7 @@ in
};
masterPort = mkOption {
- type = types.int;
+ type = types.port;
default = 3306;
description = "Port number on which the MySQL master server runs.";
};
@@ -299,9 +295,7 @@ in
###### implementation
- config = mkIf config.services.mysql.enable {
-
- warnings = optional (cfg.extraOptions != null) "services.mysql.`extraOptions` is deprecated, please use services.mysql.`settings`.";
+ config = mkIf cfg.enable {
services.mysql.dataDir =
mkDefault (if versionAtLeast config.system.stateVersion "17.09" then "/var/lib/mysql"
@@ -310,8 +304,7 @@ in
services.mysql.settings.mysqld = mkMerge [
{
datadir = cfg.dataDir;
- bind-address = mkIf (cfg.bind != null) cfg.bind;
- port = cfg.port;
+ port = mkDefault 3306;
}
(mkIf (cfg.replication.role == "master" || cfg.replication.role == "slave") {
log-bin = "mysql-bin-${toString cfg.replication.serverId}";
@@ -341,156 +334,150 @@ in
environment.etc."my.cnf".source = cfg.configFile;
- systemd.tmpfiles.rules = [
- "d '${cfg.dataDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
- "z '${cfg.dataDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
- ];
-
- systemd.services.mysql = let
- hasNotify = isMariaDB;
- in {
- description = "MySQL Server";
-
- after = [ "network.target" ];
- wantedBy = [ "multi-user.target" ];
- restartTriggers = [ cfg.configFile ];
-
- unitConfig.RequiresMountsFor = "${cfg.dataDir}";
-
- path = [
- # Needed for the mysql_install_db command in the preStart script
- # which calls the hostname command.
- pkgs.nettools
- ];
-
- preStart = if isMariaDB then ''
- if ! test -e ${cfg.dataDir}/mysql; then
- ${cfg.package}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${mysqldOptions}
- touch ${cfg.dataDir}/mysql_init
- fi
- '' else ''
- if ! test -e ${cfg.dataDir}/mysql; then
- ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} --initialize-insecure
- touch ${cfg.dataDir}/mysql_init
- fi
- '';
-
- script = ''
- # https://mariadb.com/kb/en/getting-started-with-mariadb-galera-cluster/#systemd-and-galera-recovery
- if test -n "''${_WSREP_START_POSITION}"; then
- if test -e "${cfg.package}/bin/galera_recovery"; then
- VAR=$(cd ${cfg.package}/bin/..; ${cfg.package}/bin/galera_recovery); [[ $? -eq 0 ]] && export _WSREP_START_POSITION=$VAR || exit 1
- fi
- fi
-
- # The last two environment variables are used for starting Galera clusters
- exec ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION
- '';
-
- postStart = let
- # The super user account to use on *first* run of MySQL server
- superUser = if isMariaDB then cfg.user else "root";
- in ''
- ${optionalString (!hasNotify) ''
- # Wait until the MySQL server is available for use
- count=0
- while [ ! -e /run/mysqld/mysqld.sock ]
- do
- if [ $count -eq 30 ]
- then
- echo "Tried 30 times, giving up..."
- exit 1
- fi
-
- echo "MySQL daemon not yet started. Waiting for 1 second..."
- count=$((count++))
- sleep 1
- done
- ''}
-
- if [ -f ${cfg.dataDir}/mysql_init ]
- then
- # While MariaDB comes with a 'mysql' super user account since 10.4.x, MySQL does not
- # Since we don't want to run this service as 'root' we need to ensure the account exists on first run
- ( echo "CREATE USER IF NOT EXISTS '${cfg.user}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
- echo "GRANT ALL PRIVILEGES ON *.* TO '${cfg.user}'@'localhost' WITH GRANT OPTION;"
- ) | ${cfg.package}/bin/mysql -u ${superUser} -N
-
- ${concatMapStrings (database: ''
- # Create initial databases
- if ! test -e "${cfg.dataDir}/${database.name}"; then
- echo "Creating initial database: ${database.name}"
- ( echo 'create database `${database.name}`;'
-
- ${optionalString (database.schema != null) ''
- echo 'use `${database.name}`;'
-
- # TODO: this silently falls through if database.schema does not exist,
- # we should catch this somehow and exit, but can't do it here because we're in a subshell.
- if [ -f "${database.schema}" ]
- then
- cat ${database.schema}
- elif [ -d "${database.schema}" ]
- then
- cat ${database.schema}/mysql-databases/*.sql
- fi
- ''}
- ) | ${cfg.package}/bin/mysql -u ${superUser} -N
- fi
- '') cfg.initialDatabases}
-
- ${optionalString (cfg.replication.role == "master")
- ''
- # Set up the replication master
-
- ( echo "use mysql;"
- echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
- echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
- echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
- ) | ${cfg.package}/bin/mysql -u ${superUser} -N
- ''}
-
- ${optionalString (cfg.replication.role == "slave")
- ''
- # Set up the replication slave
-
- ( echo "stop slave;"
- echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
- echo "start slave;"
- ) | ${cfg.package}/bin/mysql -u ${superUser} -N
- ''}
-
- ${optionalString (cfg.initialScript != null)
- ''
- # Execute initial script
- # using toString to avoid copying the file to nix store if given as path instead of string,
- # as it might contain credentials
- cat ${toString cfg.initialScript} | ${cfg.package}/bin/mysql -u ${superUser} -N
- ''}
-
- rm ${cfg.dataDir}/mysql_init
+ systemd.services.mysql = {
+ description = "MySQL Server";
+
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ cfg.configFile ];
+
+ unitConfig.RequiresMountsFor = cfg.dataDir;
+
+ path = [
+ # Needed for the mysql_install_db command in the preStart script
+ # which calls the hostname command.
+ pkgs.nettools
+ ];
+
+ preStart = if isMariaDB then ''
+ if ! test -e ${cfg.dataDir}/mysql; then
+ ${cfg.package}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${mysqldOptions}
+ touch ${cfg.dataDir}/mysql_init
+ fi
+ '' else ''
+ if ! test -e ${cfg.dataDir}/mysql; then
+ ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} --initialize-insecure
+ touch ${cfg.dataDir}/mysql_init
+ fi
+ '';
+
+ script = ''
+ # https://mariadb.com/kb/en/getting-started-with-mariadb-galera-cluster/#systemd-and-galera-recovery
+ if test -n "''${_WSREP_START_POSITION}"; then
+ if test -e "${cfg.package}/bin/galera_recovery"; then
+ VAR=$(cd ${cfg.package}/bin/..; ${cfg.package}/bin/galera_recovery); [[ $? -eq 0 ]] && export _WSREP_START_POSITION=$VAR || exit 1
fi
+ fi
+
+ # The last two environment variables are used for starting Galera clusters
+ exec ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION
+ '';
+
+ postStart = let
+ # The super user account to use on *first* run of MySQL server
+ superUser = if isMariaDB then cfg.user else "root";
+ in ''
+ ${optionalString (!isMariaDB) ''
+ # Wait until the MySQL server is available for use
+ count=0
+ while [ ! -e /run/mysqld/mysqld.sock ]
+ do
+ if [ $count -eq 30 ]
+ then
+ echo "Tried 30 times, giving up..."
+ exit 1
+ fi
+
+ echo "MySQL daemon not yet started. Waiting for 1 second..."
+ count=$((count++))
+ sleep 1
+ done
+ ''}
+
+ if [ -f ${cfg.dataDir}/mysql_init ]
+ then
+ # While MariaDB comes with a 'mysql' super user account since 10.4.x, MySQL does not
+ # Since we don't want to run this service as 'root' we need to ensure the account exists on first run
+ ( echo "CREATE USER IF NOT EXISTS '${cfg.user}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
+ echo "GRANT ALL PRIVILEGES ON *.* TO '${cfg.user}'@'localhost' WITH GRANT OPTION;"
+ ) | ${cfg.package}/bin/mysql -u ${superUser} -N
- ${optionalString (cfg.ensureDatabases != []) ''
- (
${concatMapStrings (database: ''
- echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
- '') cfg.ensureDatabases}
+ # Create initial databases
+ if ! test -e "${cfg.dataDir}/${database.name}"; then
+ echo "Creating initial database: ${database.name}"
+ ( echo 'create database `${database.name}`;'
+
+ ${optionalString (database.schema != null) ''
+ echo 'use `${database.name}`;'
+
+ # TODO: this silently falls through if database.schema does not exist,
+ # we should catch this somehow and exit, but can't do it here because we're in a subshell.
+ if [ -f "${database.schema}" ]
+ then
+ cat ${database.schema}
+ elif [ -d "${database.schema}" ]
+ then
+ cat ${database.schema}/mysql-databases/*.sql
+ fi
+ ''}
+ ) | ${cfg.package}/bin/mysql -u ${superUser} -N
+ fi
+ '') cfg.initialDatabases}
+
+ ${optionalString (cfg.replication.role == "master")
+ ''
+ # Set up the replication master
+
+ ( echo "use mysql;"
+ echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
+ echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
+ echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
+ ) | ${cfg.package}/bin/mysql -u ${superUser} -N
+ ''}
+
+ ${optionalString (cfg.replication.role == "slave")
+ ''
+ # Set up the replication slave
+
+ ( echo "stop slave;"
+ echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
+ echo "start slave;"
+ ) | ${cfg.package}/bin/mysql -u ${superUser} -N
+ ''}
+
+ ${optionalString (cfg.initialScript != null)
+ ''
+ # Execute initial script
+ # using toString to avoid copying the file to nix store if given as path instead of string,
+ # as it might contain credentials
+ cat ${toString cfg.initialScript} | ${cfg.package}/bin/mysql -u ${superUser} -N
+ ''}
+
+ rm ${cfg.dataDir}/mysql_init
+ fi
+
+ ${optionalString (cfg.ensureDatabases != []) ''
+ (
+ ${concatMapStrings (database: ''
+ echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
+ '') cfg.ensureDatabases}
+ ) | ${cfg.package}/bin/mysql -N
+ ''}
+
+ ${concatMapStrings (user:
+ ''
+ ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
+ ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
+ echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
+ '') user.ensurePermissions)}
) | ${cfg.package}/bin/mysql -N
- ''}
-
- ${concatMapStrings (user:
- ''
- ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
- ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
- echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
- '') user.ensurePermissions)}
- ) | ${cfg.package}/bin/mysql -N
- '') cfg.ensureUsers}
- '';
+ '') cfg.ensureUsers}
+ '';
- serviceConfig = {
- Type = if hasNotify then "notify" else "simple";
+ serviceConfig = mkMerge [
+ {
+ Type = if isMariaDB then "notify" else "simple";
Restart = "on-abort";
RestartSec = "5s";
@@ -523,9 +510,12 @@ in
PrivateMounts = true;
# System Call Filtering
SystemCallArchitectures = "native";
- };
- };
-
+ }
+ (mkIf (cfg.dataDir == "/var/lib/mysql") {
+ StateDirectory = "mysql";
+ StateDirectoryMode = "0700";
+ })
+ ];
+ };
};
-
}