aboutsummaryrefslogtreecommitdiff
path: root/nixpkgs/nixos/modules/services/databases/mysql.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/modules/services/databases/mysql.nix')
-rw-r--r--nixpkgs/nixos/modules/services/databases/mysql.nix294
1 files changed, 165 insertions, 129 deletions
diff --git a/nixpkgs/nixos/modules/services/databases/mysql.nix b/nixpkgs/nixos/modules/services/databases/mysql.nix
index 51885881cf7..7d0a3f9afc4 100644
--- a/nixpkgs/nixos/modules/services/databases/mysql.nix
+++ b/nixpkgs/nixos/modules/services/databases/mysql.nix
@@ -6,12 +6,10 @@ let
cfg = config.services.mysql;
- mysql = cfg.package;
-
- isMariaDB = lib.getName mysql == lib.getName pkgs.mariadb;
+ isMariaDB = lib.getName cfg.package == lib.getName pkgs.mariadb;
mysqldOptions =
- "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql}";
+ "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${cfg.package}";
settingsFile = pkgs.writeText "my.cnf" (
generators.toINI { listsAsDuplicateKeys = true; } cfg.settings +
@@ -22,7 +20,7 @@ in
{
imports = [
- (mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd")
+ (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.")
];
@@ -46,25 +44,31 @@ in
type = types.nullOr types.str;
default = null;
example = literalExample "0.0.0.0";
- description = "Address to bind to. The default is to bind to all addresses";
+ description = "Address to bind to. The default is to bind to all addresses.";
};
port = mkOption {
type = types.int;
default = 3306;
- description = "Port of MySQL";
+ 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.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "mysql";
+ description = "Group under which MySQL runs.";
};
dataDir = mkOption {
type = types.path;
example = "/var/lib/mysql";
- description = "Location where MySQL stores its table files";
+ description = "Location where MySQL stores its table files.";
};
configFile = mkOption {
@@ -171,7 +175,7 @@ in
initialScript = mkOption {
type = types.nullOr types.path;
default = null;
- description = "A file containing SQL statements to be executed on the first startup. Can be used for granting certain permissions on the database";
+ description = "A file containing SQL statements to be executed on the first startup. Can be used for granting certain permissions on the database.";
};
ensureDatabases = mkOption {
@@ -259,33 +263,33 @@ in
serverId = mkOption {
type = types.int;
default = 1;
- description = "Id of the MySQL server instance. This number must be unique for each instance";
+ description = "Id of the MySQL server instance. This number must be unique for each instance.";
};
masterHost = mkOption {
type = types.str;
- description = "Hostname of the MySQL master server";
+ description = "Hostname of the MySQL master server.";
};
slaveHost = mkOption {
type = types.str;
- description = "Hostname of the MySQL slave server";
+ description = "Hostname of the MySQL slave server.";
};
masterUser = mkOption {
type = types.str;
- description = "Username of the MySQL replication user";
+ description = "Username of the MySQL replication user.";
};
masterPassword = mkOption {
type = types.str;
- description = "Password of the MySQL replication user";
+ description = "Password of the MySQL replication user.";
};
masterPort = mkOption {
type = types.int;
default = 3306;
- description = "Port number on which the MySQL master server runs";
+ description = "Port number on which the MySQL master server runs.";
};
};
};
@@ -317,28 +321,33 @@ in
binlog-ignore-db = [ "information_schema" "performance_schema" "mysql" ];
})
(mkIf (!isMariaDB) {
- plugin-load-add = optional (cfg.ensureUsers != []) "auth_socket.so";
+ plugin-load-add = "auth_socket.so";
})
];
- users.users.mysql = {
- description = "MySQL server user";
- group = "mysql";
- uid = config.ids.uids.mysql;
+ users.users = optionalAttrs (cfg.user == "mysql") {
+ mysql = {
+ description = "MySQL server user";
+ group = cfg.group;
+ uid = config.ids.uids.mysql;
+ };
};
- users.groups.mysql.gid = config.ids.gids.mysql;
+ users.groups = optionalAttrs (cfg.group == "mysql") {
+ mysql.gid = config.ids.gids.mysql;
+ };
- environment.systemPackages = [mysql];
+ environment.systemPackages = [ cfg.package ];
environment.etc."my.cnf".source = cfg.configFile;
systemd.tmpfiles.rules = [
- "d '${cfg.dataDir}' 0700 ${cfg.user} mysql -"
+ "d '${cfg.dataDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
+ "z '${cfg.dataDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
];
systemd.services.mysql = let
- hasNotify = (cfg.package == pkgs.mariadb);
+ hasNotify = isMariaDB;
in {
description = "MySQL Server";
@@ -356,126 +365,153 @@ in
preStart = if isMariaDB then ''
if ! test -e ${cfg.dataDir}/mysql; then
- ${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${mysqldOptions}
- touch /tmp/mysql_init
+ ${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
- ${mysql}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} --initialize-insecure
- touch /tmp/mysql_init
+ ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} --initialize-insecure
+ touch ${cfg.dataDir}/mysql_init
fi
'';
- serviceConfig = {
- User = cfg.user;
- Group = "mysql";
- Type = if hasNotify then "notify" else "simple";
- RuntimeDirectory = "mysqld";
- RuntimeDirectoryMode = "0755";
- Restart = "on-abort";
- RestartSec = "5s";
- # The last two environment variables are used for starting Galera clusters
- ExecStart = "${mysql}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION";
- ExecStartPost =
- let
- setupScript = pkgs.writeScript "mysql-setup" ''
- #!${pkgs.runtimeShell} -e
-
- ${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 /tmp/mysql_init ]
+ 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
- ${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
- ''}
- ) | ${mysql}/bin/mysql -u root -N
- fi
- '') cfg.initialDatabases}
-
- ${optionalString (cfg.replication.role == "master")
- ''
- # Set up the replication master
+ echo "Tried 30 times, giving up..."
+ exit 1
+ fi
- ( 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}';"
- ) | ${mysql}/bin/mysql -u root -N
+ 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 == "slave")
- ''
- # Set up the replication slave
+ ${optionalString (cfg.replication.role == "master")
+ ''
+ # Set up the replication master
- ( 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;"
- ) | ${mysql}/bin/mysql -u root -N
- ''}
+ ( 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.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} | ${mysql}/bin/mysql -u root -N
- ''}
+ ${optionalString (cfg.replication.role == "slave")
+ ''
+ # Set up the replication slave
- rm /tmp/mysql_init
- fi
+ ( 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.ensureDatabases != []) ''
- (
- ${concatMapStrings (database: ''
- echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
- '') cfg.ensureDatabases}
- ) | ${mysql}/bin/mysql -u root -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
''}
- ${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)}
- ) | ${mysql}/bin/mysql -u root -N
- '') cfg.ensureUsers}
- '';
- in
- # ensureDatbases & ensureUsers depends on this script being run as root
- # when the user has secured their mysql install
- "+${setupScript}";
+ 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
+ '') cfg.ensureUsers}
+ '';
+
+ serviceConfig = {
+ Type = if hasNotify then "notify" else "simple";
+ Restart = "on-abort";
+ RestartSec = "5s";
+ # The last two environment variables are used for starting Galera clusters
+ ExecStart = "${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION";
+ # User and group
+ User = cfg.user;
+ Group = cfg.group;
+ # Runtime directory and mode
+ RuntimeDirectory = "mysqld";
+ RuntimeDirectoryMode = "0755";
+ # Access write directories
+ ReadWritePaths = [ cfg.dataDir ];
+ # Capabilities
+ CapabilityBoundingSet = "";
+ # Security
+ NoNewPrivileges = true;
+ # Sandboxing
+ ProtectSystem = "strict";
+ ProtectHome = true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ProtectHostname = true;
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectControlGroups = true;
+ RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+ LockPersonality = true;
+ MemoryDenyWriteExecute = true;
+ RestrictRealtime = true;
+ RestrictSUIDSGID = true;
+ PrivateMounts = true;
+ # System Call Filtering
+ SystemCallArchitectures = "native";
};
};