blob: ae54265a7191fb320022ebe33f1a50f082d957a5 (
plain) (
tree)
|
|
{ pkgs, config, lib, ... }:
let
cfg = config.myServices.databasesReplication.mariadb;
in
{
options.myServices.databasesReplication.mariadb = {
enable = lib.mkEnableOption "Enable mariadb replication";
base = lib.mkOption {
type = lib.types.path;
description = ''
Base path to put the replications
'';
};
hosts = lib.mkOption {
default = {};
description = ''
Hosts to backup
'';
type = lib.types.attrsOf (lib.types.submodule {
options = {
package = lib.mkOption {
type = lib.types.package;
default = pkgs.mariadb;
description = ''
Mariadb package for this host
'';
};
serverId = lib.mkOption {
type = lib.types.int;
description = ''
Server id to use for replication cluster (must be unique among the cluster!)
'';
};
host = lib.mkOption {
type = lib.types.str;
description = ''
Host to connect to
'';
};
port = lib.mkOption {
type = lib.types.str;
description = ''
Port to connect to
'';
};
user = lib.mkOption {
type = lib.types.str;
description = ''
User to connect as
'';
};
password = lib.mkOption {
type = lib.types.str;
description = ''
Password to use
'';
};
dumpUser = lib.mkOption {
type = lib.types.str;
description = ''
User who can do a dump
'';
};
dumpPassword = lib.mkOption {
type = lib.types.str;
description = ''
Password for the dump user
'';
};
};
});
};
};
config = lib.mkIf cfg.enable {
users.users.mysql = {
description = "MySQL server user";
group = "mysql";
uid = config.ids.uids.mysql;
extraGroups = [ "keys" ];
};
users.groups.mysql.gid = config.ids.gids.mysql;
secrets.keys = lib.flatten (lib.mapAttrsToList (name: hcfg: [
{
dest = "mysql_replication/${name}/slave_init_commands";
user = "mysql";
group = "mysql";
permissions = "0400";
text = ''
CHANGE MASTER TO master_host="${hcfg.host}", master_port=${hcfg.port}, master_user="${hcfg.user}", master_password="${hcfg.password}", master_ssl=1, master_use_gtid=slave_pos;
START SLAVE;
'';
}
{
dest = "mysql_replication/${name}/mysqldump_remote";
permissions = "0400";
user = "root";
group = "root";
text = ''
[mysqldump]
user = ${hcfg.user}
password = ${hcfg.password}
'';
}
{
dest = "mysql_replication/${name}/mysqldump";
permissions = "0400";
user = "root";
group = "root";
text = ''
[mysqldump]
user = ${hcfg.dumpUser}
password = ${hcfg.dumpPassword}
'';
}
{
dest = "mysql_replication/${name}/client";
permissions = "0400";
user = "mysql";
group = "mysql";
text = ''
[client]
user = ${hcfg.dumpUser}
password = ${hcfg.dumpPassword}
'';
}
]) cfg.hosts);
services.cron = {
enable = true;
systemCronJobs = lib.flatten (lib.mapAttrsToList (name: hcfg:
let
dataDir = "${cfg.base}/${name}/mysql";
backupDir = "${cfg.base}/${name}/mysql_backup";
backup_script = pkgs.writeScript "backup_mysql_${name}" ''
#!${pkgs.stdenv.shell}
set -euo pipefail
filename=${backupDir}/$(${pkgs.coreutils}/bin/date -Iminutes).sql
${hcfg.package}/bin/mysqldump \
--defaults-file=${config.secrets.location}/mysql_replication/${name}/mysqldump \
-S /run/mysqld_${name}/mysqld.sock \
--gtid \
--master-data \
--flush-privileges \
--all-databases > $filename
${pkgs.gzip}/bin/gzip $filename
'';
u = pkgs.callPackage ./utils.nix {};
cleanup_script = pkgs.writeScript "cleanup_mysql_${name}" (u.exponentialDumps "sql.gz" backupDir);
in [
"0 22,4,10,16 * * * root ${backup_script}"
"0 3 * * * root ${cleanup_script}"
]) cfg.hosts);
};
system.activationScripts = lib.attrsets.mapAttrs' (name: hcfg:
lib.attrsets.nameValuePair "mysql_replication_${name}" {
deps = [ "users" "groups" ];
text = ''
install -m 0700 -o mysql -g mysql -d ${cfg.base}/${name}/mysql
install -m 0700 -o mysql -g mysql -d ${cfg.base}/${name}/mysql_backup
'';
}) cfg.hosts;
environment.etc = lib.attrsets.mapAttrs' (name: hcfg:
lib.attrsets.nameValuePair "mysql/${name}_my.cnf" {
text = ''
[mysqld]
skip-networking
socket = /run/mysqld_${name}/mysqld.sock
datadir = ${cfg.base}/${name}/mysql/
log-bin = mariadb-bin
server-id = ${builtins.toString hcfg.serverId}
'';
}
) cfg.hosts;
systemd.services = lib.attrsets.mapAttrs' (name: hcfg:
let
dataDir = "${cfg.base}/${name}/mysql";
in
lib.attrsets.nameValuePair "mysql_backup_${name}" {
description = "Mysql replication for ${name}";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
restartTriggers = [ config.environment.etc."mysql/${name}_my.cnf".source ];
unitConfig.RequiresMountsFor = dataDir;
preStart = ''
if ! test -e ${dataDir}/mysql; then
${hcfg.package}/bin/mysqldump \
--defaults-file=${config.secrets.location}/mysql_replication/${name}/mysqldump_remote \
-h ${hcfg.host} \
-P ${hcfg.port} \
--ssl \
--gtid \
--flush-privileges \
--master-data \
--all-databases > ${dataDir}/initial.sql
${hcfg.package}/bin/mysql_install_db \
--defaults-file=/etc/mysql/${name}_my.cnf \
--user=mysql \
--datadir=${dataDir} \
--basedir=${hcfg.package}
fi
'';
serviceConfig = {
User = "mysql";
Group = "mysql";
RuntimeDirectory = "mysqld_${name}";
RuntimeDirectoryMode = "0755";
SupplementaryGroups = "keys";
PermissionsStartOnly = true;
Type = "notify";
ExecStart = "${hcfg.package}/bin/mysqld --defaults-file=/etc/mysql/${name}_my.cnf --user=mysql --datadir=${dataDir} --basedir=${hcfg.package}";
ExecStartPost =
let
sql_before = pkgs.writeText "mysql-initial-before" ''
DROP DATABASE test;
'';
setupScript = pkgs.writeScript "mysql-setup" ''
#!${pkgs.runtimeShell} -e
if test -e ${dataDir}/initial.sql; then
cat \
${sql_before} \
${dataDir}/initial.sql \
${config.secrets.location}/mysql_replication/${name}/slave_init_commands \
| ${hcfg.package}/bin/mysql \
--defaults-file=/etc/mysql/${name}_my.cnf \
-S /run/mysqld_${name}/mysqld.sock \
--user=root
rm -f ${dataDir}/initial.sql
fi
'';
in
"+${setupScript}";
# initial dump can take a long time
TimeoutStartSec="infinity";
TimeoutStopSec = 120;
};
}) cfg.hosts;
};
}
|