nixos/services.mysql: remove with lib;

This commit is contained in:
Felix Buehler 2024-08-27 20:42:59 +02:00
parent 8cf91e2c5b
commit 8e91c6b7b3

View file

@ -1,7 +1,4 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
with lib;
let let
cfg = config.services.mysql; cfg = config.services.mysql;
@ -9,7 +6,7 @@ let
isMariaDB = lib.getName cfg.package == lib.getName pkgs.mariadb; isMariaDB = lib.getName cfg.package == lib.getName pkgs.mariadb;
isOracle = lib.getName cfg.package == lib.getName pkgs.mysql80; isOracle = lib.getName cfg.package == lib.getName pkgs.mysql80;
# Oracle MySQL has supported "notify" service type since 8.0 # Oracle MySQL has supported "notify" service type since 8.0
hasNotify = isMariaDB || (isOracle && versionAtLeast cfg.package.version "8.0"); hasNotify = isMariaDB || (isOracle && lib.versionAtLeast cfg.package.version "8.0");
mysqldOptions = mysqldOptions =
"--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${cfg.package}"; "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${cfg.package}";
@ -21,11 +18,11 @@ in
{ {
imports = [ imports = [
(mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd.") (lib.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.") (lib.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.") (lib.mkRemovedOptionModule [ "services" "mysql" "extraOptions" ] "Use services.mysql.settings.mysqld instead.")
(mkRemovedOptionModule [ "services" "mysql" "bind" ] "Use services.mysql.settings.mysqld.bind-address instead.") (lib.mkRemovedOptionModule [ "services" "mysql" "bind" ] "Use services.mysql.settings.mysqld.bind-address instead.")
(mkRemovedOptionModule [ "services" "mysql" "port" ] "Use services.mysql.settings.mysqld.port instead.") (lib.mkRemovedOptionModule [ "services" "mysql" "port" ] "Use services.mysql.settings.mysqld.port instead.")
]; ];
###### interface ###### interface
@ -34,18 +31,18 @@ in
services.mysql = { services.mysql = {
enable = mkEnableOption "MySQL server"; enable = lib.mkEnableOption "MySQL server";
package = mkOption { package = lib.mkOption {
type = types.package; type = lib.types.package;
example = literalExpression "pkgs.mariadb"; example = lib.literalExpression "pkgs.mariadb";
description = '' description = ''
Which MySQL derivation to use. MariaDB packages are supported too. Which MySQL derivation to use. MariaDB packages are supported too.
''; '';
}; };
user = mkOption { user = lib.mkOption {
type = types.str; type = lib.types.str;
default = "mysql"; default = "mysql";
description = '' description = ''
User account under which MySQL runs. User account under which MySQL runs.
@ -58,8 +55,8 @@ in
''; '';
}; };
group = mkOption { group = lib.mkOption {
type = types.str; type = lib.types.str;
default = "mysql"; default = "mysql";
description = '' description = ''
Group account under which MySQL runs. Group account under which MySQL runs.
@ -72,8 +69,8 @@ in
''; '';
}; };
dataDir = mkOption { dataDir = lib.mkOption {
type = types.path; type = lib.types.path;
example = "/var/lib/mysql"; example = "/var/lib/mysql";
description = '' description = ''
The data directory for MySQL. The data directory for MySQL.
@ -85,8 +82,8 @@ in
''; '';
}; };
configFile = mkOption { configFile = lib.mkOption {
type = types.path; type = lib.types.path;
default = configFile; default = configFile;
defaultText = '' defaultText = ''
A configuration file automatically generated by NixOS. A configuration file automatically generated by NixOS.
@ -95,7 +92,7 @@ in
Override the configuration file used by MySQL. By default, Override the configuration file used by MySQL. By default,
NixOS generates one automatically from {option}`services.mysql.settings`. NixOS generates one automatically from {option}`services.mysql.settings`.
''; '';
example = literalExpression '' example = lib.literalExpression ''
pkgs.writeText "my.cnf" ''' pkgs.writeText "my.cnf" '''
[mysqld] [mysqld]
datadir = /var/lib/mysql datadir = /var/lib/mysql
@ -107,7 +104,7 @@ in
''; '';
}; };
settings = mkOption { settings = lib.mkOption {
type = format.type; type = format.type;
default = {}; default = {};
description = '' description = ''
@ -123,7 +120,7 @@ in
`1`, or `0`. See the provided example below. `1`, or `0`. See the provided example below.
::: :::
''; '';
example = literalExpression '' example = lib.literalExpression ''
{ {
mysqld = { mysqld = {
key_buffer_size = "6G"; key_buffer_size = "6G";
@ -139,17 +136,17 @@ in
''; '';
}; };
initialDatabases = mkOption { initialDatabases = lib.mkOption {
type = types.listOf (types.submodule { type = lib.types.listOf (lib.types.submodule {
options = { options = {
name = mkOption { name = lib.mkOption {
type = types.str; type = lib.types.str;
description = '' description = ''
The name of the database to create. The name of the database to create.
''; '';
}; };
schema = mkOption { schema = lib.mkOption {
type = types.nullOr types.path; type = lib.types.nullOr lib.types.path;
default = null; default = null;
description = '' description = ''
The initial schema of the database; if null (the default), The initial schema of the database; if null (the default),
@ -163,7 +160,7 @@ in
List of database names and their initial schemas that should be used to create databases on the first startup List of database names and their initial schemas that should be used to create databases on the first startup
of MySQL. The schema attribute is optional: If not specified, an empty database is created. of MySQL. The schema attribute is optional: If not specified, an empty database is created.
''; '';
example = literalExpression '' example = lib.literalExpression ''
[ [
{ name = "foodatabase"; schema = ./foodatabase.sql; } { name = "foodatabase"; schema = ./foodatabase.sql; }
{ name = "bardatabase"; } { name = "bardatabase"; }
@ -171,14 +168,14 @@ in
''; '';
}; };
initialScript = mkOption { initialScript = lib.mkOption {
type = types.nullOr types.path; type = lib.types.nullOr lib.types.path;
default = null; 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 { ensureDatabases = lib.mkOption {
type = types.listOf types.str; type = lib.types.listOf lib.types.str;
default = []; default = [];
description = '' description = ''
Ensures that the specified databases exist. Ensures that the specified databases exist.
@ -192,17 +189,17 @@ in
]; ];
}; };
ensureUsers = mkOption { ensureUsers = lib.mkOption {
type = types.listOf (types.submodule { type = lib.types.listOf (lib.types.submodule {
options = { options = {
name = mkOption { name = lib.mkOption {
type = types.str; type = lib.types.str;
description = '' description = ''
Name of the user to ensure. Name of the user to ensure.
''; '';
}; };
ensurePermissions = mkOption { ensurePermissions = lib.mkOption {
type = types.attrsOf types.str; type = lib.types.attrsOf lib.types.str;
default = {}; default = {};
description = '' description = ''
Permissions to ensure for the user, specified as attribute set. Permissions to ensure for the user, specified as attribute set.
@ -216,7 +213,7 @@ in
[GRANT syntax](https://mariadb.com/kb/en/library/grant/). [GRANT syntax](https://mariadb.com/kb/en/library/grant/).
The attributes are used as `GRANT ''${attrName} ON ''${attrValue}`. The attributes are used as `GRANT ''${attrName} ON ''${attrValue}`.
''; '';
example = literalExpression '' example = lib.literalExpression ''
{ {
"database.*" = "ALL PRIVILEGES"; "database.*" = "ALL PRIVILEGES";
"*.*" = "SELECT, LOCK TABLES"; "*.*" = "SELECT, LOCK TABLES";
@ -234,7 +231,7 @@ in
option is changed. This means that users created and permissions assigned once through this option or option is changed. This means that users created and permissions assigned once through this option or
otherwise have to be removed manually. otherwise have to be removed manually.
''; '';
example = literalExpression '' example = lib.literalExpression ''
[ [
{ {
name = "nextcloud"; name = "nextcloud";
@ -253,40 +250,40 @@ in
}; };
replication = { replication = {
role = mkOption { role = lib.mkOption {
type = types.enum [ "master" "slave" "none" ]; type = lib.types.enum [ "master" "slave" "none" ];
default = "none"; default = "none";
description = "Role of the MySQL server instance."; description = "Role of the MySQL server instance.";
}; };
serverId = mkOption { serverId = lib.mkOption {
type = types.int; type = lib.types.int;
default = 1; 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 { masterHost = lib.mkOption {
type = types.str; type = lib.types.str;
description = "Hostname of the MySQL master server."; description = "Hostname of the MySQL master server.";
}; };
slaveHost = mkOption { slaveHost = lib.mkOption {
type = types.str; type = lib.types.str;
description = "Hostname of the MySQL slave server."; description = "Hostname of the MySQL slave server.";
}; };
masterUser = mkOption { masterUser = lib.mkOption {
type = types.str; type = lib.types.str;
description = "Username of the MySQL replication user."; description = "Username of the MySQL replication user.";
}; };
masterPassword = mkOption { masterPassword = lib.mkOption {
type = types.str; type = lib.types.str;
description = "Password of the MySQL replication user."; description = "Password of the MySQL replication user.";
}; };
masterPort = mkOption { masterPort = lib.mkOption {
type = types.port; type = lib.types.port;
default = 3306; default = 3306;
description = "Port number on which the MySQL master server runs."; description = "Port number on which the MySQL master server runs.";
}; };
@ -298,30 +295,30 @@ in
###### implementation ###### implementation
config = mkIf cfg.enable { config = lib.mkIf cfg.enable {
services.mysql.dataDir = services.mysql.dataDir =
mkDefault (if versionAtLeast config.system.stateVersion "17.09" then "/var/lib/mysql" lib.mkDefault (if lib.versionAtLeast config.system.stateVersion "17.09" then "/var/lib/mysql"
else "/var/mysql"); else "/var/mysql");
services.mysql.settings.mysqld = mkMerge [ services.mysql.settings.mysqld = lib.mkMerge [
{ {
datadir = cfg.dataDir; datadir = cfg.dataDir;
port = mkDefault 3306; port = lib.mkDefault 3306;
} }
(mkIf (cfg.replication.role == "master" || cfg.replication.role == "slave") { (lib.mkIf (cfg.replication.role == "master" || cfg.replication.role == "slave") {
log-bin = "mysql-bin-${toString cfg.replication.serverId}"; log-bin = "mysql-bin-${toString cfg.replication.serverId}";
log-bin-index = "mysql-bin-${toString cfg.replication.serverId}.index"; log-bin-index = "mysql-bin-${toString cfg.replication.serverId}.index";
relay-log = "mysql-relay-bin"; relay-log = "mysql-relay-bin";
server-id = cfg.replication.serverId; server-id = cfg.replication.serverId;
binlog-ignore-db = [ "information_schema" "performance_schema" "mysql" ]; binlog-ignore-db = [ "information_schema" "performance_schema" "mysql" ];
}) })
(mkIf (!isMariaDB) { (lib.mkIf (!isMariaDB) {
plugin-load-add = "auth_socket.so"; plugin-load-add = "auth_socket.so";
}) })
]; ];
users.users = optionalAttrs (cfg.user == "mysql") { users.users = lib.optionalAttrs (cfg.user == "mysql") {
mysql = { mysql = {
description = "MySQL server user"; description = "MySQL server user";
group = cfg.group; group = cfg.group;
@ -329,7 +326,7 @@ in
}; };
}; };
users.groups = optionalAttrs (cfg.group == "mysql") { users.groups = lib.optionalAttrs (cfg.group == "mysql") {
mysql.gid = config.ids.gids.mysql; mysql.gid = config.ids.gids.mysql;
}; };
@ -380,7 +377,7 @@ in
# The super user account to use on *first* run of MySQL server # The super user account to use on *first* run of MySQL server
superUser = if isMariaDB then cfg.user else "root"; superUser = if isMariaDB then cfg.user else "root";
in '' in ''
${optionalString (!hasNotify) '' ${lib.optionalString (!hasNotify) ''
# Wait until the MySQL server is available for use # Wait until the MySQL server is available for use
while [ ! -e /run/mysqld/mysqld.sock ] while [ ! -e /run/mysqld/mysqld.sock ]
do do
@ -397,13 +394,13 @@ in
echo "GRANT ALL PRIVILEGES ON *.* TO '${cfg.user}'@'localhost' WITH GRANT OPTION;" echo "GRANT ALL PRIVILEGES ON *.* TO '${cfg.user}'@'localhost' WITH GRANT OPTION;"
) | ${cfg.package}/bin/mysql -u ${superUser} -N ) | ${cfg.package}/bin/mysql -u ${superUser} -N
${concatMapStrings (database: '' ${lib.concatMapStrings (database: ''
# Create initial databases # Create initial databases
if ! test -e "${cfg.dataDir}/${database.name}"; then if ! test -e "${cfg.dataDir}/${database.name}"; then
echo "Creating initial database: ${database.name}" echo "Creating initial database: ${database.name}"
( echo 'create database `${database.name}`;' ( echo 'create database `${database.name}`;'
${optionalString (database.schema != null) '' ${lib.optionalString (database.schema != null) ''
echo 'use `${database.name}`;' echo 'use `${database.name}`;'
# TODO: this silently falls through if database.schema does not exist, # TODO: this silently falls through if database.schema does not exist,
@ -420,7 +417,7 @@ in
fi fi
'') cfg.initialDatabases} '') cfg.initialDatabases}
${optionalString (cfg.replication.role == "master") ${lib.optionalString (cfg.replication.role == "master")
'' ''
# Set up the replication master # Set up the replication master
@ -431,7 +428,7 @@ in
) | ${cfg.package}/bin/mysql -u ${superUser} -N ) | ${cfg.package}/bin/mysql -u ${superUser} -N
''} ''}
${optionalString (cfg.replication.role == "slave") ${lib.optionalString (cfg.replication.role == "slave")
'' ''
# Set up the replication slave # Set up the replication slave
@ -441,7 +438,7 @@ in
) | ${cfg.package}/bin/mysql -u ${superUser} -N ) | ${cfg.package}/bin/mysql -u ${superUser} -N
''} ''}
${optionalString (cfg.initialScript != null) ${lib.optionalString (cfg.initialScript != null)
'' ''
# Execute initial script # Execute initial script
# using toString to avoid copying the file to nix store if given as path instead of string, # using toString to avoid copying the file to nix store if given as path instead of string,
@ -452,25 +449,25 @@ in
rm ${cfg.dataDir}/mysql_init rm ${cfg.dataDir}/mysql_init
fi fi
${optionalString (cfg.ensureDatabases != []) '' ${lib.optionalString (cfg.ensureDatabases != []) ''
( (
${concatMapStrings (database: '' ${lib.concatMapStrings (database: ''
echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;" echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
'') cfg.ensureDatabases} '') cfg.ensureDatabases}
) | ${cfg.package}/bin/mysql -N ) | ${cfg.package}/bin/mysql -N
''} ''}
${concatMapStrings (user: ${lib.concatMapStrings (user:
'' ''
( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};" ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
${concatStringsSep "\n" (mapAttrsToList (database: permission: '' ${lib.concatStringsSep "\n" (lib.mapAttrsToList (database: permission: ''
echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';" echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
'') user.ensurePermissions)} '') user.ensurePermissions)}
) | ${cfg.package}/bin/mysql -N ) | ${cfg.package}/bin/mysql -N
'') cfg.ensureUsers} '') cfg.ensureUsers}
''; '';
serviceConfig = mkMerge [ serviceConfig = lib.mkMerge [
{ {
Type = if hasNotify then "notify" else "simple"; Type = if hasNotify then "notify" else "simple";
Restart = "on-abort"; Restart = "on-abort";
@ -506,7 +503,7 @@ in
# System Call Filtering # System Call Filtering
SystemCallArchitectures = "native"; SystemCallArchitectures = "native";
} }
(mkIf (cfg.dataDir == "/var/lib/mysql") { (lib.mkIf (cfg.dataDir == "/var/lib/mysql") {
StateDirectory = "mysql"; StateDirectory = "mysql";
StateDirectoryMode = "0700"; StateDirectoryMode = "0700";
}) })