diff options
Diffstat (limited to 'modules/private/databases')
-rw-r--r-- | modules/private/databases/default.nix | 57 | ||||
-rw-r--r-- | modules/private/databases/mariadb.nix | 182 | ||||
-rw-r--r-- | modules/private/databases/mariadb_replication.nix | 251 | ||||
-rw-r--r-- | modules/private/databases/openldap/default.nix | 147 | ||||
-rw-r--r-- | modules/private/databases/openldap/eldiron_schemas.nix | 21 | ||||
-rw-r--r-- | modules/private/databases/openldap/immae.schema | 179 | ||||
-rw-r--r-- | modules/private/databases/openldap_replication.nix | 166 | ||||
-rw-r--r-- | modules/private/databases/postgresql.nix | 228 | ||||
-rw-r--r-- | modules/private/databases/postgresql_replication.nix | 182 | ||||
-rw-r--r-- | modules/private/databases/redis.nix | 133 | ||||
-rw-r--r-- | modules/private/databases/redis_replication.nix | 171 | ||||
-rw-r--r-- | modules/private/databases/utils.nix | 30 |
12 files changed, 0 insertions, 1747 deletions
diff --git a/modules/private/databases/default.nix b/modules/private/databases/default.nix deleted file mode 100644 index 1241658..0000000 --- a/modules/private/databases/default.nix +++ /dev/null | |||
@@ -1,57 +0,0 @@ | |||
1 | { lib, config, nodes, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databases; | ||
4 | in | ||
5 | { | ||
6 | options.myServices = { | ||
7 | databases.enable = lib.mkEnableOption "my databases service"; | ||
8 | databasesCerts = lib.mkOption { | ||
9 | description = "Default databases configurations for certificates as accepted by acme"; | ||
10 | }; | ||
11 | }; | ||
12 | |||
13 | config.myServices.databases = lib.mkIf cfg.enable { | ||
14 | mariadb = { | ||
15 | enable = true; | ||
16 | ldapConfig = { | ||
17 | inherit (config.myEnv.ldap) host base; | ||
18 | inherit (config.myEnv.databases.mysql.pam) dn filter password; | ||
19 | }; | ||
20 | replicationLdapConfig = { | ||
21 | inherit (config.myEnv.ldap) host base; | ||
22 | inherit (config.myEnv.servers.eldiron.ldap) dn password; | ||
23 | }; | ||
24 | credentials.root = config.myEnv.databases.mysql.systemUsers.root; | ||
25 | }; | ||
26 | |||
27 | openldap = { | ||
28 | accessFile = ../../../nixops/secrets/ldap.conf; | ||
29 | baseDn = config.myEnv.ldap.base; | ||
30 | rootDn = config.myEnv.ldap.root_dn; | ||
31 | rootPw = config.myEnv.ldap.root_pw; | ||
32 | enable = true; | ||
33 | }; | ||
34 | |||
35 | postgresql = { | ||
36 | ldapConfig = { | ||
37 | inherit (config.myEnv.ldap) host base; | ||
38 | inherit (config.myEnv.databases.postgresql.pam) dn filter password; | ||
39 | }; | ||
40 | replicationLdapConfig = { | ||
41 | inherit (config.myEnv.ldap) host base; | ||
42 | inherit (config.myEnv.servers.eldiron.ldap) dn password; | ||
43 | }; | ||
44 | authorizedHosts = { | ||
45 | }; | ||
46 | replicationHosts = { | ||
47 | backup-2 = { | ||
48 | ip4 = [config.myEnv.servers.backup-2.ips.main.ip4]; | ||
49 | ip6 = config.myEnv.servers.backup-2.ips.main.ip6; | ||
50 | }; | ||
51 | }; | ||
52 | enable = true; | ||
53 | }; | ||
54 | |||
55 | redis.enable = true; | ||
56 | }; | ||
57 | } | ||
diff --git a/modules/private/databases/mariadb.nix b/modules/private/databases/mariadb.nix deleted file mode 100644 index 101eb3f..0000000 --- a/modules/private/databases/mariadb.nix +++ /dev/null | |||
@@ -1,182 +0,0 @@ | |||
1 | { lib, pkgs, config, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databases.mariadb; | ||
4 | in { | ||
5 | options.myServices.databases = { | ||
6 | mariadb = { | ||
7 | enable = lib.mkOption { | ||
8 | default = false; | ||
9 | example = true; | ||
10 | description = "Whether to enable mariadb database"; | ||
11 | type = lib.types.bool; | ||
12 | }; | ||
13 | package = lib.mkOption { | ||
14 | type = lib.types.package; | ||
15 | default = pkgs.mariadb; | ||
16 | description = '' | ||
17 | Mariadb package to use. | ||
18 | ''; | ||
19 | }; | ||
20 | credentials = lib.mkOption { | ||
21 | default = {}; | ||
22 | description = "Credentials"; | ||
23 | type = lib.types.attrsOf lib.types.str; | ||
24 | }; | ||
25 | ldapConfig = lib.mkOption { | ||
26 | description = "LDAP configuration to allow PAM identification via LDAP"; | ||
27 | type = lib.types.submodule { | ||
28 | options = { | ||
29 | host = lib.mkOption { type = lib.types.str; }; | ||
30 | base = lib.mkOption { type = lib.types.str; }; | ||
31 | dn = lib.mkOption { type = lib.types.str; }; | ||
32 | password = lib.mkOption { type = lib.types.str; }; | ||
33 | filter = lib.mkOption { type = lib.types.str; }; | ||
34 | }; | ||
35 | }; | ||
36 | }; | ||
37 | replicationLdapConfig = lib.mkOption { | ||
38 | description = "LDAP configuration to allow replication"; | ||
39 | type = lib.types.submodule { | ||
40 | options = { | ||
41 | host = lib.mkOption { type = lib.types.str; }; | ||
42 | base = lib.mkOption { type = lib.types.str; }; | ||
43 | dn = lib.mkOption { type = lib.types.str; }; | ||
44 | password = lib.mkOption { type = lib.types.str; }; | ||
45 | }; | ||
46 | }; | ||
47 | }; | ||
48 | dataDir = lib.mkOption { | ||
49 | type = lib.types.path; | ||
50 | default = "/var/lib/mysql"; | ||
51 | description = '' | ||
52 | The directory where Mariadb stores its data. | ||
53 | ''; | ||
54 | }; | ||
55 | # Output variables | ||
56 | socketsDir = lib.mkOption { | ||
57 | type = lib.types.path; | ||
58 | default = "/run/mysqld"; | ||
59 | description = '' | ||
60 | The directory where Mariadb puts sockets. | ||
61 | ''; | ||
62 | }; | ||
63 | sockets = lib.mkOption { | ||
64 | type = lib.types.attrsOf lib.types.path; | ||
65 | default = { | ||
66 | mysqld = "${cfg.socketsDir}/mysqld.sock"; | ||
67 | }; | ||
68 | readOnly = true; | ||
69 | description = '' | ||
70 | Mariadb sockets | ||
71 | ''; | ||
72 | }; | ||
73 | }; | ||
74 | }; | ||
75 | |||
76 | config = lib.mkIf cfg.enable { | ||
77 | networking.firewall.allowedTCPPorts = [ 3306 ]; | ||
78 | |||
79 | # for adminer, ssl is implemented with mysqli only, which is | ||
80 | # currently disabled because it’s not compatible with pam. | ||
81 | # Thus we need to generate two users for each 'remote': one remote | ||
82 | # with SSL, and one localhost without SSL. | ||
83 | # User identified by LDAP: | ||
84 | # CREATE USER foo@% IDENTIFIED VIA pam USING 'mysql' REQUIRE SSL; | ||
85 | # CREATE USER foo@localhost IDENTIFIED VIA pam USING 'mysql'; | ||
86 | |||
87 | # To create a user (host) for replication: | ||
88 | # CREATE USER 'host'@'%' IDENTIFIED VIA pam USING 'mysql_replication' REQUIRE SSL; | ||
89 | # GRANT REPLICATION SLAVE, REPLICATION CLIENT, RELOAD, LOCK TABLES, SELECT, SHOW VIEW ON *.* TO 'host'@'%'; | ||
90 | # (the lock/select grant permits to let the replication host handle | ||
91 | # the initial fetch of the database) | ||
92 | # % should be valid for both localhost (for cron dumps) and the origin host. | ||
93 | services.mysql = { | ||
94 | enable = true; | ||
95 | package = cfg.package; | ||
96 | dataDir = cfg.dataDir; | ||
97 | settings = { | ||
98 | mysqld = { | ||
99 | ssl_ca = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; | ||
100 | ssl_key = "${config.security.acme.certs.mysql.directory}/key.pem"; | ||
101 | ssl_cert = "${config.security.acme.certs.mysql.directory}/fullchain.pem"; | ||
102 | |||
103 | # for replication | ||
104 | log-bin = "mariadb-bin"; | ||
105 | server-id = "1"; | ||
106 | |||
107 | # this introduces a small delay before storing on disk, but | ||
108 | # makes it order of magnitudes quicker | ||
109 | innodb_flush_log_at_trx_commit = "0"; | ||
110 | }; | ||
111 | }; | ||
112 | }; | ||
113 | |||
114 | users.users.mysql.extraGroups = [ "keys" ]; | ||
115 | security.acme.certs."mysql" = config.myServices.databasesCerts // { | ||
116 | user = "mysql"; | ||
117 | group = "mysql"; | ||
118 | domain = "db-1.immae.eu"; | ||
119 | postRun = '' | ||
120 | systemctl restart mysql.service | ||
121 | ''; | ||
122 | }; | ||
123 | |||
124 | secrets.keys = { | ||
125 | "mysql/mysqldump" = { | ||
126 | permissions = "0400"; | ||
127 | user = "root"; | ||
128 | group = "root"; | ||
129 | text = '' | ||
130 | [mysqldump] | ||
131 | user = root | ||
132 | password = ${cfg.credentials.root} | ||
133 | ''; | ||
134 | }; | ||
135 | "mysql/pam" = { | ||
136 | permissions = "0400"; | ||
137 | user = "mysql"; | ||
138 | group = "mysql"; | ||
139 | text = with cfg.ldapConfig; '' | ||
140 | host ${host} | ||
141 | base ${base} | ||
142 | binddn ${dn} | ||
143 | bindpw ${password} | ||
144 | pam_filter ${filter} | ||
145 | ssl start_tls | ||
146 | ''; | ||
147 | }; | ||
148 | "mysql/pam_replication" = { | ||
149 | permissions = "0400"; | ||
150 | user = "mysql"; | ||
151 | group = "mysql"; | ||
152 | text = with cfg.replicationLdapConfig; '' | ||
153 | host ${host} | ||
154 | base ${base} | ||
155 | binddn ${dn} | ||
156 | bindpw ${password} | ||
157 | pam_login_attribute cn | ||
158 | ssl start_tls | ||
159 | ''; | ||
160 | }; | ||
161 | }; | ||
162 | |||
163 | security.pam.services = let | ||
164 | pam_ldap = "${pkgs.pam_ldap}/lib/security/pam_ldap.so"; | ||
165 | in { | ||
166 | mysql = { | ||
167 | text = '' | ||
168 | # https://mariadb.com/kb/en/mariadb/pam-authentication-plugin/ | ||
169 | auth required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam"} | ||
170 | account required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam"} | ||
171 | ''; | ||
172 | }; | ||
173 | mysql_replication = { | ||
174 | text = '' | ||
175 | auth required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam_replication"} | ||
176 | account required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam_replication"} | ||
177 | ''; | ||
178 | }; | ||
179 | }; | ||
180 | |||
181 | }; | ||
182 | } | ||
diff --git a/modules/private/databases/mariadb_replication.nix b/modules/private/databases/mariadb_replication.nix deleted file mode 100644 index 68e6f7f..0000000 --- a/modules/private/databases/mariadb_replication.nix +++ /dev/null | |||
@@ -1,251 +0,0 @@ | |||
1 | { pkgs, config, lib, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databasesReplication.mariadb; | ||
4 | in | ||
5 | { | ||
6 | options.myServices.databasesReplication.mariadb = { | ||
7 | enable = lib.mkEnableOption "Enable mariadb replication"; | ||
8 | base = lib.mkOption { | ||
9 | type = lib.types.path; | ||
10 | description = '' | ||
11 | Base path to put the replications | ||
12 | ''; | ||
13 | }; | ||
14 | hosts = lib.mkOption { | ||
15 | default = {}; | ||
16 | description = '' | ||
17 | Hosts to backup | ||
18 | ''; | ||
19 | type = lib.types.attrsOf (lib.types.submodule { | ||
20 | options = { | ||
21 | package = lib.mkOption { | ||
22 | type = lib.types.package; | ||
23 | default = pkgs.mariadb; | ||
24 | description = '' | ||
25 | Mariadb package for this host | ||
26 | ''; | ||
27 | }; | ||
28 | serverId = lib.mkOption { | ||
29 | type = lib.types.int; | ||
30 | description = '' | ||
31 | Server id to use for replication cluster (must be unique among the cluster!) | ||
32 | ''; | ||
33 | }; | ||
34 | host = lib.mkOption { | ||
35 | type = lib.types.str; | ||
36 | description = '' | ||
37 | Host to connect to | ||
38 | ''; | ||
39 | }; | ||
40 | port = lib.mkOption { | ||
41 | type = lib.types.str; | ||
42 | description = '' | ||
43 | Port to connect to | ||
44 | ''; | ||
45 | }; | ||
46 | user = lib.mkOption { | ||
47 | type = lib.types.str; | ||
48 | description = '' | ||
49 | User to connect as | ||
50 | ''; | ||
51 | }; | ||
52 | password = lib.mkOption { | ||
53 | type = lib.types.str; | ||
54 | description = '' | ||
55 | Password to use | ||
56 | ''; | ||
57 | }; | ||
58 | dumpUser = lib.mkOption { | ||
59 | type = lib.types.str; | ||
60 | description = '' | ||
61 | User who can do a dump | ||
62 | ''; | ||
63 | }; | ||
64 | dumpPassword = lib.mkOption { | ||
65 | type = lib.types.str; | ||
66 | description = '' | ||
67 | Password for the dump user | ||
68 | ''; | ||
69 | }; | ||
70 | }; | ||
71 | }); | ||
72 | }; | ||
73 | }; | ||
74 | |||
75 | config = lib.mkIf cfg.enable { | ||
76 | users.users.mysql = { | ||
77 | description = "MySQL server user"; | ||
78 | group = "mysql"; | ||
79 | uid = config.ids.uids.mysql; | ||
80 | extraGroups = [ "keys" ]; | ||
81 | }; | ||
82 | users.groups.mysql.gid = config.ids.gids.mysql; | ||
83 | |||
84 | secrets.keys = lib.listToAttrs (lib.flatten (lib.mapAttrsToList (name: hcfg: [ | ||
85 | (lib.nameValuePair "mysql_replication/${name}/slave_init_commands" { | ||
86 | user = "mysql"; | ||
87 | group = "mysql"; | ||
88 | permissions = "0400"; | ||
89 | text = '' | ||
90 | 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; | ||
91 | START SLAVE; | ||
92 | ''; | ||
93 | }) | ||
94 | (lib.nameValuePair "mysql_replication/${name}/mysqldump_remote" { | ||
95 | permissions = "0400"; | ||
96 | user = "root"; | ||
97 | group = "root"; | ||
98 | text = '' | ||
99 | [mysqldump] | ||
100 | user = ${hcfg.user} | ||
101 | password = ${hcfg.password} | ||
102 | ''; | ||
103 | }) | ||
104 | (lib.nameValuePair "mysql_replication/${name}/mysqldump" { | ||
105 | permissions = "0400"; | ||
106 | user = "root"; | ||
107 | group = "root"; | ||
108 | text = '' | ||
109 | [mysqldump] | ||
110 | user = ${hcfg.dumpUser} | ||
111 | password = ${hcfg.dumpPassword} | ||
112 | ''; | ||
113 | }) | ||
114 | (lib.nameValuePair "mysql_replication/${name}/client" { | ||
115 | permissions = "0400"; | ||
116 | user = "mysql"; | ||
117 | group = "mysql"; | ||
118 | text = '' | ||
119 | [client] | ||
120 | user = ${hcfg.dumpUser} | ||
121 | password = ${hcfg.dumpPassword} | ||
122 | ''; | ||
123 | }) | ||
124 | ]) cfg.hosts)); | ||
125 | |||
126 | services.cron = { | ||
127 | enable = true; | ||
128 | systemCronJobs = lib.flatten (lib.mapAttrsToList (name: hcfg: | ||
129 | let | ||
130 | dataDir = "${cfg.base}/${name}/mysql"; | ||
131 | backupDir = "${cfg.base}/${name}/mysql_backup"; | ||
132 | backup_script = pkgs.writeScript "backup_mysql_${name}" '' | ||
133 | #!${pkgs.stdenv.shell} | ||
134 | |||
135 | set -euo pipefail | ||
136 | |||
137 | filename=${backupDir}/$(${pkgs.coreutils}/bin/date -Iminutes).sql | ||
138 | ${hcfg.package}/bin/mysqldump \ | ||
139 | --defaults-file=${config.secrets.fullPaths."mysql_replication/${name}/mysqldump"} \ | ||
140 | -S /run/mysqld_${name}/mysqld.sock \ | ||
141 | --gtid \ | ||
142 | --master-data \ | ||
143 | --flush-privileges \ | ||
144 | --ignore-database=netdata \ | ||
145 | --all-databases > $filename | ||
146 | ${pkgs.gzip}/bin/gzip $filename | ||
147 | ''; | ||
148 | u = pkgs.callPackage ./utils.nix {}; | ||
149 | cleanup_script = pkgs.writeScript "cleanup_mysql_${name}" (u.exponentialDumps "sql.gz" backupDir); | ||
150 | in [ | ||
151 | "0 22,4,10,16 * * * root ${backup_script}" | ||
152 | "0 3 * * * root ${cleanup_script}" | ||
153 | ]) cfg.hosts); | ||
154 | }; | ||
155 | |||
156 | system.activationScripts = lib.attrsets.mapAttrs' (name: hcfg: | ||
157 | lib.attrsets.nameValuePair "mysql_replication_${name}" { | ||
158 | deps = [ "users" "groups" ]; | ||
159 | text = '' | ||
160 | install -m 0700 -o mysql -g mysql -d ${cfg.base}/${name}/mysql | ||
161 | install -m 0700 -o mysql -g mysql -d ${cfg.base}/${name}/mysql_backup | ||
162 | ''; | ||
163 | }) cfg.hosts; | ||
164 | |||
165 | environment.etc = lib.attrsets.mapAttrs' (name: hcfg: | ||
166 | lib.attrsets.nameValuePair "mysql/${name}_my.cnf" { | ||
167 | text = '' | ||
168 | [mysqld] | ||
169 | skip-networking | ||
170 | socket = /run/mysqld_${name}/mysqld.sock | ||
171 | datadir = ${cfg.base}/${name}/mysql/ | ||
172 | log-bin = mariadb-bin | ||
173 | server-id = ${builtins.toString hcfg.serverId} | ||
174 | ''; | ||
175 | } | ||
176 | ) cfg.hosts; | ||
177 | |||
178 | systemd.services = lib.attrsets.mapAttrs' (name: hcfg: | ||
179 | let | ||
180 | dataDir = "${cfg.base}/${name}/mysql"; | ||
181 | in | ||
182 | lib.attrsets.nameValuePair "mysql_backup_${name}" { | ||
183 | description = "Mysql replication for ${name}"; | ||
184 | wantedBy = [ "multi-user.target" ]; | ||
185 | after = [ "network.target" ]; | ||
186 | restartTriggers = [ config.environment.etc."mysql/${name}_my.cnf".source ]; | ||
187 | unitConfig.RequiresMountsFor = dataDir; | ||
188 | |||
189 | preStart = '' | ||
190 | if ! test -e ${dataDir}/mysql; then | ||
191 | if ! test -e ${dataDir}/initial.sql; then | ||
192 | ${hcfg.package}/bin/mysqldump \ | ||
193 | --defaults-file=${config.secrets.fullPaths."mysql_replication/${name}/mysqldump_remote"} \ | ||
194 | -h ${hcfg.host} \ | ||
195 | -P ${hcfg.port} \ | ||
196 | --ssl \ | ||
197 | --gtid \ | ||
198 | --flush-privileges \ | ||
199 | --master-data \ | ||
200 | --all-databases > ${dataDir}/initial.sql | ||
201 | fi | ||
202 | |||
203 | ${hcfg.package}/bin/mysql_install_db \ | ||
204 | --defaults-file=/etc/mysql/${name}_my.cnf \ | ||
205 | --user=mysql \ | ||
206 | --datadir=${dataDir} \ | ||
207 | --basedir=${hcfg.package} | ||
208 | fi | ||
209 | ''; | ||
210 | |||
211 | serviceConfig = { | ||
212 | User = "mysql"; | ||
213 | Group = "mysql"; | ||
214 | RuntimeDirectory = "mysqld_${name}"; | ||
215 | RuntimeDirectoryMode = "0755"; | ||
216 | SupplementaryGroups = "keys"; | ||
217 | PermissionsStartOnly = true; | ||
218 | Type = "notify"; | ||
219 | |||
220 | ExecStart = "${hcfg.package}/bin/mysqld --defaults-file=/etc/mysql/${name}_my.cnf --user=mysql --datadir=${dataDir} --basedir=${hcfg.package}"; | ||
221 | ExecStartPost = | ||
222 | let | ||
223 | sql_before = pkgs.writeText "mysql-initial-before" '' | ||
224 | DROP DATABASE test; | ||
225 | INSTALL SONAME 'auth_pam'; | ||
226 | ''; | ||
227 | setupScript = pkgs.writeScript "mysql-setup" '' | ||
228 | #!${pkgs.runtimeShell} -e | ||
229 | |||
230 | if test -e ${dataDir}/initial.sql; then | ||
231 | cat \ | ||
232 | ${sql_before} \ | ||
233 | ${dataDir}/initial.sql \ | ||
234 | ${config.secrets.fullPaths."mysql_replication/${name}/slave_init_commands"} \ | ||
235 | | ${hcfg.package}/bin/mysql \ | ||
236 | --defaults-file=/etc/mysql/${name}_my.cnf \ | ||
237 | -S /run/mysqld_${name}/mysqld.sock \ | ||
238 | --user=root | ||
239 | rm -f ${dataDir}/initial.sql | ||
240 | fi | ||
241 | ''; | ||
242 | in | ||
243 | "+${setupScript}"; | ||
244 | # initial dump can take a long time | ||
245 | TimeoutStartSec="infinity"; | ||
246 | TimeoutStopSec = 120; | ||
247 | }; | ||
248 | }) cfg.hosts; | ||
249 | }; | ||
250 | } | ||
251 | |||
diff --git a/modules/private/databases/openldap/default.nix b/modules/private/databases/openldap/default.nix deleted file mode 100644 index d35aca0..0000000 --- a/modules/private/databases/openldap/default.nix +++ /dev/null | |||
@@ -1,147 +0,0 @@ | |||
1 | { lib, pkgs, config, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databases.openldap; | ||
4 | ldapConfig = let | ||
5 | eldiron_schemas = pkgs.callPackage ./eldiron_schemas.nix {}; | ||
6 | in '' | ||
7 | ${eldiron_schemas} | ||
8 | |||
9 | pidfile ${cfg.pids.pid} | ||
10 | argsfile ${cfg.pids.args} | ||
11 | |||
12 | moduleload back_hdb | ||
13 | backend hdb | ||
14 | |||
15 | TLSCertificateFile ${config.security.acme.certs.ldap.directory}/cert.pem | ||
16 | TLSCertificateKeyFile ${config.security.acme.certs.ldap.directory}/key.pem | ||
17 | TLSCACertificateFile ${config.security.acme.certs.ldap.directory}/fullchain.pem | ||
18 | TLSCACertificatePath ${pkgs.cacert.unbundled}/etc/ssl/certs/ | ||
19 | #This makes openldap crash | ||
20 | #TLSCipherSuite DEFAULT | ||
21 | |||
22 | sasl-host kerberos.immae.eu | ||
23 | ''; | ||
24 | in | ||
25 | { | ||
26 | options.myServices.databases = { | ||
27 | openldap = { | ||
28 | enable = lib.mkOption { | ||
29 | default = false; | ||
30 | example = true; | ||
31 | description = "Whether to enable ldap"; | ||
32 | type = lib.types.bool; | ||
33 | }; | ||
34 | baseDn = lib.mkOption { | ||
35 | type = lib.types.str; | ||
36 | description = '' | ||
37 | Base DN for LDAP | ||
38 | ''; | ||
39 | }; | ||
40 | rootDn = lib.mkOption { | ||
41 | type = lib.types.str; | ||
42 | description = '' | ||
43 | Root DN | ||
44 | ''; | ||
45 | }; | ||
46 | rootPw = lib.mkOption { | ||
47 | type = lib.types.str; | ||
48 | description = '' | ||
49 | Root (Hashed) password | ||
50 | ''; | ||
51 | }; | ||
52 | accessFile = lib.mkOption { | ||
53 | type = lib.types.path; | ||
54 | description = '' | ||
55 | The file path that defines the access | ||
56 | ''; | ||
57 | }; | ||
58 | dataDir = lib.mkOption { | ||
59 | type = lib.types.path; | ||
60 | default = "/var/lib/openldap"; | ||
61 | description = '' | ||
62 | The directory where Openldap stores its data. | ||
63 | ''; | ||
64 | }; | ||
65 | socketsDir = lib.mkOption { | ||
66 | type = lib.types.path; | ||
67 | default = "/run/slapd"; | ||
68 | description = '' | ||
69 | The directory where Openldap puts sockets and pid files. | ||
70 | ''; | ||
71 | }; | ||
72 | # Output variables | ||
73 | pids = lib.mkOption { | ||
74 | type = lib.types.attrsOf lib.types.path; | ||
75 | default = { | ||
76 | pid = "${cfg.socketsDir}/slapd.pid"; | ||
77 | args = "${cfg.socketsDir}/slapd.args"; | ||
78 | }; | ||
79 | readOnly = true; | ||
80 | description = '' | ||
81 | Slapd pid files | ||
82 | ''; | ||
83 | }; | ||
84 | }; | ||
85 | }; | ||
86 | |||
87 | config = lib.mkIf cfg.enable { | ||
88 | secrets.keys = { | ||
89 | "ldap/password" = { | ||
90 | permissions = "0400"; | ||
91 | user = "openldap"; | ||
92 | group = "openldap"; | ||
93 | text = "rootpw ${cfg.rootPw}"; | ||
94 | }; | ||
95 | "ldap/access" = { | ||
96 | permissions = "0400"; | ||
97 | user = "openldap"; | ||
98 | group = "openldap"; | ||
99 | text = builtins.readFile cfg.accessFile; | ||
100 | }; | ||
101 | "ldap" = { | ||
102 | permissions = "0500"; | ||
103 | user = "openldap"; | ||
104 | group = "openldap"; | ||
105 | isDir = true; | ||
106 | }; | ||
107 | }; | ||
108 | users.users.openldap.extraGroups = [ "keys" ]; | ||
109 | networking.firewall.allowedTCPPorts = [ 636 389 ]; | ||
110 | |||
111 | security.acme.certs."ldap" = config.myServices.databasesCerts // { | ||
112 | user = "openldap"; | ||
113 | group = "openldap"; | ||
114 | domain = "ldap.immae.eu"; | ||
115 | postRun = '' | ||
116 | systemctl restart openldap.service | ||
117 | ''; | ||
118 | }; | ||
119 | |||
120 | services.filesWatcher.openldap = { | ||
121 | restart = true; | ||
122 | paths = [ config.secrets.fullPaths."ldap" ]; | ||
123 | }; | ||
124 | |||
125 | services.openldap = { | ||
126 | enable = true; | ||
127 | dataDir = cfg.dataDir; | ||
128 | urlList = [ "ldap://" "ldaps://" ]; | ||
129 | logLevel = "none"; | ||
130 | extraConfig = ldapConfig; | ||
131 | extraDatabaseConfig = '' | ||
132 | moduleload memberof | ||
133 | overlay memberof | ||
134 | |||
135 | moduleload syncprov | ||
136 | overlay syncprov | ||
137 | syncprov-checkpoint 100 10 | ||
138 | |||
139 | include ${config.secrets.fullPaths."ldap/access"} | ||
140 | ''; | ||
141 | rootpwFile = config.secrets.fullPaths."ldap/password"; | ||
142 | suffix = cfg.baseDn; | ||
143 | rootdn = cfg.rootDn; | ||
144 | database = "hdb"; | ||
145 | }; | ||
146 | }; | ||
147 | } | ||
diff --git a/modules/private/databases/openldap/eldiron_schemas.nix b/modules/private/databases/openldap/eldiron_schemas.nix deleted file mode 100644 index cf45ebe..0000000 --- a/modules/private/databases/openldap/eldiron_schemas.nix +++ /dev/null | |||
@@ -1,21 +0,0 @@ | |||
1 | { fetchurl, openldap }: | ||
2 | let | ||
3 | kerberosSchema = fetchurl { | ||
4 | url = "https://raw.githubusercontent.com/krb5/krb5/0bdd3b8058ed4ec9acc050e316bea86f6830b15f/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema"; | ||
5 | sha256 = "17fnkkf6s3lznsl7wp6914pqsc78d038rh38l638big8z608ksww"; | ||
6 | }; | ||
7 | puppetSchema = fetchurl { | ||
8 | url = "https://raw.githubusercontent.com/puppetlabs/puppet/bf7c108825ffdb5ea89cf3e500d55d27ab64b8d2/ext/ldap/puppet.schema"; | ||
9 | sha256 = "11bjf5zfvqlim7p9vddcafs0wiq3v8ys77x8h6fbp9c6bdfh0awh"; | ||
10 | }; | ||
11 | schemas = [ | ||
12 | #"${openldap}/etc/schema/core.schema" | ||
13 | #"${openldap}/etc/schema/cosine.schema" | ||
14 | #"${openldap}/etc/schema/inetorgperson.schema" | ||
15 | #"${openldap}/etc/schema/nis.schema" | ||
16 | puppetSchema | ||
17 | kerberosSchema | ||
18 | ./immae.schema | ||
19 | ]; | ||
20 | in | ||
21 | builtins.concatStringsSep "\n" (map (v: "include ${v}") schemas) | ||
diff --git a/modules/private/databases/openldap/immae.schema b/modules/private/databases/openldap/immae.schema deleted file mode 100644 index d2ef972..0000000 --- a/modules/private/databases/openldap/immae.schema +++ /dev/null | |||
@@ -1,179 +0,0 @@ | |||
1 | # vim: set filetype=slapd: | ||
2 | objectIdentifier Immaeroot 1.3.6.1.4.1.50071 | ||
3 | |||
4 | objectIdentifier Immae Immaeroot:2 | ||
5 | objectIdentifier ImmaeattributeType Immae:3 | ||
6 | objectIdentifier ImmaeobjectClass Immae:4 | ||
7 | |||
8 | # TT-RSS | ||
9 | attributetype ( ImmaeattributeType:1 NAME 'immaeTtrssLogin' | ||
10 | DESC 'login for TTRSS' | ||
11 | EQUALITY caseIgnoreMatch | ||
12 | SUBSTR caseIgnoreSubstringsMatch | ||
13 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) | ||
14 | |||
15 | objectclass ( ImmaeobjectClass:1 NAME 'immaeTtrssClass' | ||
16 | DESC 'Expansion of the existing object classes for ttrss' | ||
17 | SUP top AUXILIARY | ||
18 | MUST ( immaeTtrssLogin ) ) | ||
19 | |||
20 | # FTP | ||
21 | attributetype ( ImmaeattributeType:2 NAME 'immaeFtpDirectory' | ||
22 | DESC 'home directory for ftp' | ||
23 | EQUALITY caseExactIA5Match | ||
24 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) | ||
25 | |||
26 | attributetype ( ImmaeattributeType:3 NAME 'immaeFtpUid' | ||
27 | DESC 'user id for ftp' | ||
28 | EQUALITY integerMatch | ||
29 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 ) | ||
30 | |||
31 | attributetype ( ImmaeattributeType:4 NAME 'immaeFtpGid' | ||
32 | DESC 'group id for ftp' | ||
33 | EQUALITY integerMatch | ||
34 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 ) | ||
35 | |||
36 | objectclass ( ImmaeobjectClass:2 NAME 'immaeFtpClass' | ||
37 | DESC 'Expansion of the existing object classes for ftp' | ||
38 | SUP top AUXILIARY | ||
39 | MUST ( immaeFtpDirectory $ immaeFtpGid $ immaeFtpUid ) ) | ||
40 | |||
41 | |||
42 | # SSH keys | ||
43 | attributetype ( ImmaeattributeType:5 NAME 'immaeSshKey' | ||
44 | DESC 'OpenSSH Public key' | ||
45 | EQUALITY octetStringMatch | ||
46 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) | ||
47 | |||
48 | objectClass ( ImmaeobjectClass:3 NAME 'immaeSshClass' | ||
49 | DESC 'OpenSSH class' | ||
50 | SUP top AUXILIARY | ||
51 | MAy ( immaeSSHKey ) ) | ||
52 | |||
53 | # Specific access | ||
54 | attributetype (ImmaeattributeType:6 NAME 'immaeAccessDn' | ||
55 | EQUALITY distinguishedNameMatch | ||
56 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) | ||
57 | |||
58 | attributetype (ImmaeattributeType:17 NAME 'immaeAccessWriteDn' | ||
59 | EQUALITY distinguishedNameMatch | ||
60 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) | ||
61 | |||
62 | attributetype (ImmaeattributeType:18 NAME 'immaeAccessReadSubtree' | ||
63 | EQUALITY distinguishedNameMatch | ||
64 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) | ||
65 | |||
66 | objectClass ( ImmaeobjectClass:4 NAME 'immaeAccessClass' | ||
67 | DESC 'Access class' | ||
68 | SUP top AUXILIARY | ||
69 | MAY ( immaeAccessDn $ immaeAccessWriteDn $ immaeAccessReadSubtree ) ) | ||
70 | |||
71 | # Xmpp uid | ||
72 | attributetype ( ImmaeattributeType:7 NAME 'immaeXmppUid' | ||
73 | DESC 'user part for Xmpp' | ||
74 | EQUALITY caseIgnoreMatch | ||
75 | SUBSTR caseIgnoreSubstringsMatch | ||
76 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) | ||
77 | |||
78 | objectclass ( ImmaeobjectClass:5 NAME 'immaeXmppClass' | ||
79 | DESC 'Expansion of the existing object classes for XMPP' | ||
80 | SUP top AUXILIARY | ||
81 | MUST ( immaeXmppUid ) ) | ||
82 | |||
83 | # Postfix accounts | ||
84 | attributetype ( ImmaeattributeType:8 NAME 'immaePostfixAddress' | ||
85 | DESC 'the dovecot address to match as username' | ||
86 | EQUALITY caseIgnoreIA5Match | ||
87 | SUBSTR caseIgnoreIA5SubstringsMatch | ||
88 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) | ||
89 | |||
90 | attributetype ( ImmaeattributeType:9 NAME 'immaePostfixHome' | ||
91 | DESC 'the postfix home directory' | ||
92 | EQUALITY caseExactIA5Match | ||
93 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) | ||
94 | |||
95 | attributetype ( ImmaeattributeType:10 NAME 'immaePostfixMail' | ||
96 | DESC 'the dovecot mail location' | ||
97 | EQUALITY caseExactIA5Match | ||
98 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) | ||
99 | |||
100 | attributetype ( ImmaeattributeType:11 NAME 'immaePostfixUid' | ||
101 | DESC 'the dovecot uid' | ||
102 | EQUALITY caseExactIA5Match | ||
103 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) | ||
104 | |||
105 | attributetype ( ImmaeattributeType:12 NAME 'immaePostfixGid' | ||
106 | DESC 'the dovecot gid' | ||
107 | EQUALITY caseExactIA5Match | ||
108 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) | ||
109 | |||
110 | objectclass ( ImmaeobjectClass:6 NAME 'immaePostfixClass' | ||
111 | DESC 'Expansion of the existing object classes for Postfix' | ||
112 | SUP top AUXILIARY | ||
113 | MUST ( immaePostfixAddress $ immaePostfixHome $ | ||
114 | immaePostfixMail $ immaePostfixUid $ immaePostfixGid ) | ||
115 | ) | ||
116 | |||
117 | # Tinc informations | ||
118 | # Domaine = une classe a part ou une partie du dn ? | ||
119 | # attributetype ( ImmaeattributeType:13 NAME 'immaeTincIpSegment' | ||
120 | # DESC 'the internal ip segment in tinc' | ||
121 | # EQUALITY caseIgnoreIA5Match | ||
122 | # SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) | ||
123 | # | ||
124 | # attributetype ( ImmaeattributeType:14 NAME 'immaeTincSubdomain' | ||
125 | # DESC 'the host subdomain' | ||
126 | # EQUALITY caseIgnoreIA5Match | ||
127 | # SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) | ||
128 | # | ||
129 | # attributetype ( ImmaeattributeType:15 NAME 'immaeTincHostname' | ||
130 | # DESC 'the host name' | ||
131 | # EQUALITY caseIgnoreIA5Match | ||
132 | # SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) | ||
133 | # | ||
134 | # objectclass ( ImmaeobjectClass:7 NAME 'immaeTincHostClass' | ||
135 | # DESC 'Expansion of the existing object classes for Tinc' | ||
136 | # SUP top AUXILIARY | ||
137 | # MUST ( immaeTincInternalIp $ immaeTincSubdomain $ | ||
138 | # immaeTincHostname ) | ||
139 | # ) | ||
140 | |||
141 | attributetype (ImmaeattributeType:16 NAME 'immaePuppetJson' | ||
142 | DESC 'Puppet hiera json' | ||
143 | EQUALITY octetStringMatch | ||
144 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) | ||
145 | |||
146 | objectclass ( ImmaeobjectClass:8 NAME 'immaePuppetClass' | ||
147 | DESC 'Expansion of the existing object classes for Puppet' | ||
148 | SUP top AUXILIARY | ||
149 | MUST ( immaePuppetJson ) | ||
150 | ) | ||
151 | |||
152 | attributetype (ImmaeattributeType:19 NAME 'immaeTaskId' | ||
153 | DESC 'Taskwarrior server Org:Name:Key' | ||
154 | EQUALITY caseIgnoreMatch | ||
155 | SUBSTR caseIgnoreSubstringsMatch | ||
156 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) | ||
157 | |||
158 | objectclass ( ImmaeobjectClass:9 NAME 'immaeTaskClass' | ||
159 | DESC 'Expansion of the existing object classes for Task' | ||
160 | SUP top AUXILIARY | ||
161 | MUST ( immaeTaskId ) | ||
162 | ) | ||
163 | |||
164 | # Peertube uid | ||
165 | attributetype ( ImmaeattributeType:20 NAME 'immaePeertubeId' | ||
166 | DESC 'login for Peertube' | ||
167 | EQUALITY caseIgnoreMatch | ||
168 | SUBSTR caseIgnoreSubstringsMatch | ||
169 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) | ||
170 | |||
171 | objectclass ( ImmaeobjectClass:10 NAME 'immaePeertubeClass' | ||
172 | DESC 'Expansion of the existing object classes for peertube' | ||
173 | SUP top AUXILIARY | ||
174 | MUST ( immaePeertubeId ) ) | ||
175 | |||
176 | |||
177 | # Last: | ||
178 | # attributetype ( ImmaeattributeType:20 NAME 'immaePeertubeId' | ||
179 | # objectclass ( ImmaeobjectClass:10 NAME 'immaePeertubeClass' | ||
diff --git a/modules/private/databases/openldap_replication.nix b/modules/private/databases/openldap_replication.nix deleted file mode 100644 index b456323..0000000 --- a/modules/private/databases/openldap_replication.nix +++ /dev/null | |||
@@ -1,166 +0,0 @@ | |||
1 | { pkgs, config, lib, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databasesReplication.openldap; | ||
4 | eldiron_schemas = pkgs.callPackage ./openldap/eldiron_schemas.nix {}; | ||
5 | ldapConfig = hcfg: name: pkgs.writeText "slapd.conf" '' | ||
6 | include ${pkgs.openldap}/etc/schema/core.schema | ||
7 | include ${pkgs.openldap}/etc/schema/cosine.schema | ||
8 | include ${pkgs.openldap}/etc/schema/inetorgperson.schema | ||
9 | include ${pkgs.openldap}/etc/schema/nis.schema | ||
10 | ${eldiron_schemas} | ||
11 | pidfile /run/slapd_${name}/slapd.pid | ||
12 | argsfile /run/slapd_${name}/slapd.args | ||
13 | |||
14 | moduleload back_hdb | ||
15 | backend hdb | ||
16 | database hdb | ||
17 | |||
18 | suffix "${hcfg.base}" | ||
19 | rootdn "cn=root,${hcfg.base}" | ||
20 | directory ${cfg.base}/${name}/openldap | ||
21 | |||
22 | index objectClass eq | ||
23 | index uid pres,eq | ||
24 | index entryUUID eq | ||
25 | |||
26 | include ${config.secrets.fullPaths."openldap_replication/${name}/replication_config"} | ||
27 | ''; | ||
28 | in | ||
29 | { | ||
30 | options.myServices.databasesReplication.openldap = { | ||
31 | enable = lib.mkEnableOption "Enable openldap replication"; | ||
32 | base = lib.mkOption { | ||
33 | type = lib.types.path; | ||
34 | description = '' | ||
35 | Base path to put the replications | ||
36 | ''; | ||
37 | }; | ||
38 | hosts = lib.mkOption { | ||
39 | default = {}; | ||
40 | description = '' | ||
41 | Hosts to backup | ||
42 | ''; | ||
43 | type = lib.types.attrsOf (lib.types.submodule { | ||
44 | options = { | ||
45 | package = lib.mkOption { | ||
46 | type = lib.types.package; | ||
47 | default = pkgs.openldap; | ||
48 | description = '' | ||
49 | Openldap package for this host | ||
50 | ''; | ||
51 | }; | ||
52 | url = lib.mkOption { | ||
53 | type = lib.types.str; | ||
54 | description = '' | ||
55 | Host to connect to | ||
56 | ''; | ||
57 | }; | ||
58 | base = lib.mkOption { | ||
59 | type = lib.types.str; | ||
60 | description = '' | ||
61 | Base DN to replicate | ||
62 | ''; | ||
63 | }; | ||
64 | dn = lib.mkOption { | ||
65 | type = lib.types.str; | ||
66 | description = '' | ||
67 | DN to use | ||
68 | ''; | ||
69 | }; | ||
70 | password = lib.mkOption { | ||
71 | type = lib.types.str; | ||
72 | description = '' | ||
73 | Password to use | ||
74 | ''; | ||
75 | }; | ||
76 | }; | ||
77 | }); | ||
78 | }; | ||
79 | }; | ||
80 | |||
81 | config = lib.mkIf cfg.enable { | ||
82 | users.users.openldap = { | ||
83 | description = "Openldap database user"; | ||
84 | group = "openldap"; | ||
85 | uid = config.ids.uids.openldap; | ||
86 | extraGroups = [ "keys" ]; | ||
87 | }; | ||
88 | users.groups.openldap.gid = config.ids.gids.openldap; | ||
89 | |||
90 | secrets.keys = lib.listToAttrs (lib.flatten (lib.mapAttrsToList (name: hcfg: [ | ||
91 | (lib.nameValuePair "openldap_replication/${name}/replication_config" { | ||
92 | user = "openldap"; | ||
93 | group = "openldap"; | ||
94 | permissions = "0400"; | ||
95 | text = '' | ||
96 | syncrepl rid=000 | ||
97 | provider=${hcfg.url} | ||
98 | type=refreshAndPersist | ||
99 | searchbase="${hcfg.base}" | ||
100 | retry="5 10 300 +" | ||
101 | attrs="*,+" | ||
102 | schemachecking=off | ||
103 | bindmethod=simple | ||
104 | binddn="${hcfg.dn}" | ||
105 | credentials="${hcfg.password}" | ||
106 | ''; | ||
107 | }) | ||
108 | (lib.nameValuePair "openldap_replication/${name}/replication_password" { | ||
109 | user = "openldap"; | ||
110 | group = "openldap"; | ||
111 | permissions = "0400"; | ||
112 | text = hcfg.password; | ||
113 | }) | ||
114 | ]) cfg.hosts)); | ||
115 | |||
116 | services.cron = { | ||
117 | enable = true; | ||
118 | systemCronJobs = lib.flatten (lib.mapAttrsToList (name: hcfg: | ||
119 | let | ||
120 | dataDir = "${cfg.base}/${name}/openldap"; | ||
121 | backupDir = "${cfg.base}/${name}/openldap_backup"; | ||
122 | backup_script = pkgs.writeScript "backup_openldap_${name}" '' | ||
123 | #!${pkgs.stdenv.shell} | ||
124 | |||
125 | ${hcfg.package}/bin/slapcat -b "${hcfg.base}" -f ${ldapConfig hcfg name} -l ${backupDir}/$(${pkgs.coreutils}/bin/date -Iminutes).ldif | ||
126 | ''; | ||
127 | u = pkgs.callPackage ./utils.nix {}; | ||
128 | cleanup_script = pkgs.writeScript "cleanup_openldap_${name}" (u.exponentialDumps "ldif" backupDir); | ||
129 | in [ | ||
130 | "0 22,4,10,16 * * * root ${backup_script}" | ||
131 | "0 3 * * * root ${cleanup_script}" | ||
132 | ]) cfg.hosts); | ||
133 | }; | ||
134 | |||
135 | system.activationScripts = lib.attrsets.mapAttrs' (name: hcfg: | ||
136 | lib.attrsets.nameValuePair "openldap_replication_${name}" { | ||
137 | deps = [ "users" "groups" ]; | ||
138 | text = '' | ||
139 | install -m 0700 -o openldap -g openldap -d ${cfg.base}/${name}/openldap | ||
140 | install -m 0700 -o openldap -g openldap -d ${cfg.base}/${name}/openldap_backup | ||
141 | ''; | ||
142 | }) cfg.hosts; | ||
143 | |||
144 | systemd.services = lib.attrsets.mapAttrs' (name: hcfg: | ||
145 | let | ||
146 | dataDir = "${cfg.base}/${name}/openldap"; | ||
147 | in | ||
148 | lib.attrsets.nameValuePair "openldap_backup_${name}" { | ||
149 | description = "Openldap replication for ${name}"; | ||
150 | wantedBy = [ "multi-user.target" ]; | ||
151 | after = [ "network.target" ]; | ||
152 | unitConfig.RequiresMountsFor = dataDir; | ||
153 | |||
154 | preStart = '' | ||
155 | mkdir -p /run/slapd_${name} | ||
156 | chown -R "openldap:openldap" /run/slapd_${name} | ||
157 | ''; | ||
158 | |||
159 | serviceConfig = { | ||
160 | ExecStart = "${hcfg.package}/libexec/slapd -d 0 -u openldap -g openldap -f ${ldapConfig hcfg name}"; | ||
161 | }; | ||
162 | }) cfg.hosts; | ||
163 | }; | ||
164 | } | ||
165 | |||
166 | |||
diff --git a/modules/private/databases/postgresql.nix b/modules/private/databases/postgresql.nix deleted file mode 100644 index a6c4cc9..0000000 --- a/modules/private/databases/postgresql.nix +++ /dev/null | |||
@@ -1,228 +0,0 @@ | |||
1 | { lib, pkgs, config, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databases.postgresql; | ||
4 | in { | ||
5 | options.myServices.databases = { | ||
6 | postgresql = { | ||
7 | enable = lib.mkOption { | ||
8 | default = false; | ||
9 | example = true; | ||
10 | description = "Whether to enable postgresql database"; | ||
11 | type = lib.types.bool; | ||
12 | }; | ||
13 | package = lib.mkOption { | ||
14 | type = lib.types.package; | ||
15 | default = pkgs.postgresql; | ||
16 | description = '' | ||
17 | Postgresql package to use. | ||
18 | ''; | ||
19 | }; | ||
20 | ldapConfig = lib.mkOption { | ||
21 | description = "LDAP configuration to allow PAM identification via LDAP"; | ||
22 | type = lib.types.submodule { | ||
23 | options = { | ||
24 | host = lib.mkOption { type = lib.types.str; }; | ||
25 | base = lib.mkOption { type = lib.types.str; }; | ||
26 | dn = lib.mkOption { type = lib.types.str; }; | ||
27 | password = lib.mkOption { type = lib.types.str; }; | ||
28 | filter = lib.mkOption { type = lib.types.str; }; | ||
29 | }; | ||
30 | }; | ||
31 | }; | ||
32 | replicationLdapConfig = lib.mkOption { | ||
33 | description = "LDAP configuration to allow replication"; | ||
34 | type = lib.types.submodule { | ||
35 | options = { | ||
36 | host = lib.mkOption { type = lib.types.str; }; | ||
37 | base = lib.mkOption { type = lib.types.str; }; | ||
38 | dn = lib.mkOption { type = lib.types.str; }; | ||
39 | password = lib.mkOption { type = lib.types.str; }; | ||
40 | }; | ||
41 | }; | ||
42 | }; | ||
43 | authorizedHosts = lib.mkOption { | ||
44 | default = {}; | ||
45 | description = "Hosts to allow connections from"; | ||
46 | type = lib.types.attrsOf (lib.types.listOf (lib.types.submodule { | ||
47 | options = { | ||
48 | method = lib.mkOption { | ||
49 | default = "md5"; | ||
50 | type = lib.types.str; | ||
51 | }; | ||
52 | username = lib.mkOption { | ||
53 | default = "all"; | ||
54 | type = lib.types.str; | ||
55 | }; | ||
56 | database = lib.mkOption { | ||
57 | default = "all"; | ||
58 | type = lib.types.str; | ||
59 | }; | ||
60 | ip4 = lib.mkOption { | ||
61 | default = []; | ||
62 | type = lib.types.listOf lib.types.str; | ||
63 | }; | ||
64 | ip6 = lib.mkOption { | ||
65 | default = []; | ||
66 | type = lib.types.listOf lib.types.str; | ||
67 | }; | ||
68 | }; | ||
69 | })); | ||
70 | }; | ||
71 | replicationHosts = lib.mkOption { | ||
72 | default = {}; | ||
73 | description = "Hosts to allow replication from"; | ||
74 | type = lib.types.attrsOf (lib.types.submodule { | ||
75 | options = { | ||
76 | ip4 = lib.mkOption { | ||
77 | type = lib.types.listOf lib.types.str; | ||
78 | }; | ||
79 | ip6 = lib.mkOption { | ||
80 | type = lib.types.listOf lib.types.str; | ||
81 | }; | ||
82 | }; | ||
83 | }); | ||
84 | }; | ||
85 | # Output variables | ||
86 | socketsDir = lib.mkOption { | ||
87 | type = lib.types.path; | ||
88 | default = "/run/postgresql"; | ||
89 | description = '' | ||
90 | The directory where Postgresql puts sockets. | ||
91 | ''; | ||
92 | readOnly = true; | ||
93 | }; | ||
94 | }; | ||
95 | }; | ||
96 | |||
97 | config = lib.mkIf cfg.enable { | ||
98 | networking.firewall.allowedTCPPorts = [ 5432 ]; | ||
99 | |||
100 | security.acme.certs."postgresql" = config.myServices.databasesCerts // { | ||
101 | user = "postgres"; | ||
102 | group = "postgres"; | ||
103 | domain = "db-1.immae.eu"; | ||
104 | postRun = '' | ||
105 | systemctl reload postgresql.service | ||
106 | ''; | ||
107 | }; | ||
108 | |||
109 | systemd.services.postgresql.serviceConfig = { | ||
110 | SupplementaryGroups = "keys"; | ||
111 | }; | ||
112 | systemd.services.postgresql.postStart = lib.mkAfter '' | ||
113 | # This line is already defined in 19.09 | ||
114 | PSQL="${pkgs.sudo}/bin/sudo -u postgres psql --port=5432" | ||
115 | |||
116 | ${builtins.concatStringsSep "\n" (lib.mapAttrsToList (role: _: '' | ||
117 | $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${role}'" \ | ||
118 | | grep -q 1 \ | ||
119 | || $PSQL -tAc 'CREATE USER "${role}" WITH REPLICATION' | ||
120 | '') cfg.replicationHosts)} | ||
121 | |||
122 | ${builtins.concatStringsSep "\n" (lib.mapAttrsToList (role: _: | ||
123 | let | ||
124 | sname = builtins.replaceStrings ["-"] ["_"] role; | ||
125 | in | ||
126 | '' | ||
127 | $PSQL -tAc "SELECT 1 FROM pg_replication_slots WHERE slot_name='${sname}'" \ | ||
128 | | grep -q 1 \ | ||
129 | || $PSQL -tAc "SELECT * FROM pg_create_physical_replication_slot('${sname}')" | ||
130 | '') cfg.replicationHosts)} | ||
131 | ''; | ||
132 | |||
133 | services.postgresql = { | ||
134 | enable = true; | ||
135 | package = cfg.package; | ||
136 | enableTCPIP = true; | ||
137 | extraConfig = '' | ||
138 | max_connections = 100 | ||
139 | wal_level = logical | ||
140 | shared_buffers = 512MB | ||
141 | work_mem = 10MB | ||
142 | max_wal_size = 1GB | ||
143 | min_wal_size = 80MB | ||
144 | log_timezone = 'Europe/Paris' | ||
145 | datestyle = 'iso, mdy' | ||
146 | timezone = 'Europe/Paris' | ||
147 | lc_messages = 'en_US.UTF-8' | ||
148 | lc_monetary = 'en_US.UTF-8' | ||
149 | lc_numeric = 'en_US.UTF-8' | ||
150 | lc_time = 'en_US.UTF-8' | ||
151 | default_text_search_config = 'pg_catalog.english' | ||
152 | # this introduces a small delay before storing on disk, but | ||
153 | # makes it order of magnitudes quicker | ||
154 | synchronous_commit = off | ||
155 | ssl = on | ||
156 | ssl_cert_file = '${config.security.acme.certs.postgresql.directory}/fullchain.pem' | ||
157 | ssl_key_file = '${config.security.acme.certs.postgresql.directory}/key.pem' | ||
158 | ''; | ||
159 | authentication = let | ||
160 | hosts = builtins.concatStringsSep "\n" ( | ||
161 | lib.lists.flatten (lib.mapAttrsToList (k: vs: map (v: | ||
162 | map (ip6: "hostssl ${v.database} ${v.username} ${ip6}/128 ${v.method}") v.ip6 | ||
163 | ++ map (ip4: "hostssl ${v.database} ${v.username} ${ip4}/32 ${v.method}") v.ip4 | ||
164 | ) vs) cfg.authorizedHosts | ||
165 | )); | ||
166 | replication = builtins.concatStringsSep "\n" ( | ||
167 | lib.lists.flatten (lib.mapAttrsToList (k: v: | ||
168 | map (ip6: "hostssl replication ${k} ${ip6}/128 pam pamservice=postgresql_replication") v.ip6 | ||
169 | ++ map (ip4: "hostssl replication ${k} ${ip4}/32 pam pamservice=postgresql_replication") v.ip4 | ||
170 | ) cfg.replicationHosts | ||
171 | )); | ||
172 | in '' | ||
173 | local all postgres ident | ||
174 | local all all md5 | ||
175 | ${hosts} | ||
176 | hostssl all all all pam | ||
177 | ${replication} | ||
178 | ''; | ||
179 | }; | ||
180 | |||
181 | secrets.keys = { | ||
182 | "postgresql/pam" = { | ||
183 | permissions = "0400"; | ||
184 | group = "postgres"; | ||
185 | user = "postgres"; | ||
186 | text = with cfg.ldapConfig; '' | ||
187 | host ${host} | ||
188 | base ${base} | ||
189 | binddn ${dn} | ||
190 | bindpw ${password} | ||
191 | pam_filter ${filter} | ||
192 | ssl start_tls | ||
193 | ''; | ||
194 | }; | ||
195 | "postgresql/pam_replication" = { | ||
196 | permissions = "0400"; | ||
197 | group = "postgres"; | ||
198 | user = "postgres"; | ||
199 | text = with cfg.replicationLdapConfig; '' | ||
200 | host ${host} | ||
201 | base ${base} | ||
202 | binddn ${dn} | ||
203 | bindpw ${password} | ||
204 | pam_login_attribute cn | ||
205 | ssl start_tls | ||
206 | ''; | ||
207 | }; | ||
208 | }; | ||
209 | |||
210 | security.pam.services = let | ||
211 | pam_ldap = "${pkgs.pam_ldap}/lib/security/pam_ldap.so"; | ||
212 | in { | ||
213 | postgresql = { | ||
214 | text = '' | ||
215 | auth required ${pam_ldap} config=${config.secrets.fullPaths."postgresql/pam"} | ||
216 | account required ${pam_ldap} config=${config.secrets.fullPaths."postgresql/pam"} | ||
217 | ''; | ||
218 | }; | ||
219 | postgresql_replication = { | ||
220 | text = '' | ||
221 | auth required ${pam_ldap} config=${config.secrets.fullPaths."postgresql/pam_replication"} | ||
222 | account required ${pam_ldap} config=${config.secrets.fullPaths."postgresql/pam_replication"} | ||
223 | ''; | ||
224 | }; | ||
225 | }; | ||
226 | }; | ||
227 | } | ||
228 | |||
diff --git a/modules/private/databases/postgresql_replication.nix b/modules/private/databases/postgresql_replication.nix deleted file mode 100644 index 135bbed..0000000 --- a/modules/private/databases/postgresql_replication.nix +++ /dev/null | |||
@@ -1,182 +0,0 @@ | |||
1 | { pkgs, config, lib, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databasesReplication.postgresql; | ||
4 | in | ||
5 | { | ||
6 | options.myServices.databasesReplication.postgresql = { | ||
7 | enable = lib.mkEnableOption "Enable postgresql replication"; | ||
8 | base = lib.mkOption { | ||
9 | type = lib.types.path; | ||
10 | description = '' | ||
11 | Base path to put the replications | ||
12 | ''; | ||
13 | }; | ||
14 | mainPackage = lib.mkOption { | ||
15 | type = lib.types.package; | ||
16 | default = pkgs.postgresql; | ||
17 | description = '' | ||
18 | Postgresql package available in shell | ||
19 | ''; | ||
20 | }; | ||
21 | hosts = lib.mkOption { | ||
22 | default = {}; | ||
23 | description = '' | ||
24 | Hosts to backup | ||
25 | ''; | ||
26 | type = lib.types.attrsOf (lib.types.submodule { | ||
27 | options = { | ||
28 | package = lib.mkOption { | ||
29 | type = lib.types.package; | ||
30 | default = pkgs.postgresql; | ||
31 | description = '' | ||
32 | Postgresql package for this host | ||
33 | ''; | ||
34 | }; | ||
35 | slot = lib.mkOption { | ||
36 | type = lib.types.str; | ||
37 | description = '' | ||
38 | Slot to use for replication | ||
39 | ''; | ||
40 | }; | ||
41 | connection = lib.mkOption { | ||
42 | type = lib.types.str; | ||
43 | description = '' | ||
44 | Connection string to access the psql master | ||
45 | ''; | ||
46 | }; | ||
47 | }; | ||
48 | }); | ||
49 | }; | ||
50 | }; | ||
51 | |||
52 | config = lib.mkIf cfg.enable { | ||
53 | users.users.postgres = { | ||
54 | name = "postgres"; | ||
55 | uid = config.ids.uids.postgres; | ||
56 | group = "postgres"; | ||
57 | description = "PostgreSQL server user"; | ||
58 | home = "/var/lib/postgresql"; | ||
59 | useDefaultShell = true; | ||
60 | extraGroups = [ "keys" ]; | ||
61 | }; | ||
62 | users.groups.postgres.gid = config.ids.gids.postgres; | ||
63 | environment.systemPackages = [ cfg.mainPackage ]; | ||
64 | |||
65 | secrets.keys = lib.listToAttrs (lib.flatten (lib.mapAttrsToList (name: hcfg: [ | ||
66 | (lib.nameValuePair "postgresql_replication/${name}/recovery.conf" { | ||
67 | user = "postgres"; | ||
68 | group = "postgres"; | ||
69 | permissions = "0400"; | ||
70 | text = '' | ||
71 | standby_mode = on | ||
72 | primary_conninfo = '${hcfg.connection}?sslmode=require' | ||
73 | primary_slot_name = '${hcfg.slot}' | ||
74 | ''; | ||
75 | }) | ||
76 | (lib.nameValuePair "postgresql_replication/${name}/connection_string" { | ||
77 | user = "postgres"; | ||
78 | group = "postgres"; | ||
79 | permissions = "0400"; | ||
80 | text = hcfg.connection; | ||
81 | }) | ||
82 | (lib.nameValuePair "postgresql_replication/${name}/postgresql.conf" { | ||
83 | user = "postgres"; | ||
84 | group = "postgres"; | ||
85 | permissions = "0400"; | ||
86 | text = let | ||
87 | dataDir = "${cfg.base}/${name}/postgresql"; | ||
88 | in '' | ||
89 | listen_addresses = ''' | ||
90 | unix_socket_directories = '${dataDir}' | ||
91 | data_directory = '${dataDir}' | ||
92 | wal_level = logical | ||
93 | ''; | ||
94 | }) | ||
95 | ]) cfg.hosts)); | ||
96 | |||
97 | services.cron = { | ||
98 | enable = true; | ||
99 | systemCronJobs = lib.flatten (lib.mapAttrsToList (name: hcfg: | ||
100 | let | ||
101 | dataDir = "${cfg.base}/${name}/postgresql"; | ||
102 | backupDir = "${cfg.base}/${name}/postgresql_backup"; | ||
103 | backup_script = pkgs.writeScript "backup_psql_${name}" '' | ||
104 | #!${pkgs.stdenv.shell} | ||
105 | |||
106 | set -euo pipefail | ||
107 | |||
108 | resume_replication() { | ||
109 | ${hcfg.package}/bin/psql -h ${dataDir} -c "SELECT pg_wal_replay_resume();" >/dev/null || echo "impossible to resume replication" | ||
110 | } | ||
111 | |||
112 | trap resume_replication EXIT | ||
113 | |||
114 | ${hcfg.package}/bin/psql -h ${dataDir} -c "SELECT pg_wal_replay_pause();" >/dev/null || (echo "impossible to pause replication" && false) | ||
115 | |||
116 | ${hcfg.package}/bin/pg_dumpall -h ${dataDir} -f ${backupDir}/$(${pkgs.coreutils}/bin/date -Iminutes).sql | ||
117 | ''; | ||
118 | u = pkgs.callPackage ./utils.nix {}; | ||
119 | cleanup_script = pkgs.writeScript "cleanup_postgresql_${name}" (u.keepLastNDumps "sql" backupDir 6); | ||
120 | in [ | ||
121 | "0 22,4,10,16 * * * postgres ${backup_script}" | ||
122 | "0 3 * * * postgres ${cleanup_script}" | ||
123 | ]) cfg.hosts); | ||
124 | }; | ||
125 | |||
126 | system.activationScripts = lib.attrsets.mapAttrs' (name: hcfg: | ||
127 | lib.attrsets.nameValuePair "psql_replication_${name}" { | ||
128 | deps = [ "users" ]; | ||
129 | text = '' | ||
130 | install -m 0700 -o postgres -g postgres -d ${cfg.base}/${name}/postgresql | ||
131 | install -m 0700 -o postgres -g postgres -d ${cfg.base}/${name}/postgresql_backup | ||
132 | ''; | ||
133 | }) cfg.hosts; | ||
134 | |||
135 | systemd.services = lib.attrsets.mapAttrs' (name: hcfg: | ||
136 | let | ||
137 | dataDir = "${cfg.base}/${name}/postgresql"; | ||
138 | in | ||
139 | lib.attrsets.nameValuePair "postgresql_backup_${name}" { | ||
140 | description = "Postgresql replication for ${name}"; | ||
141 | wantedBy = [ "multi-user.target" ]; | ||
142 | after = [ "network.target" ]; | ||
143 | |||
144 | environment.PGDATA = dataDir; | ||
145 | path = [ hcfg.package ]; | ||
146 | |||
147 | preStart = '' | ||
148 | if ! test -e ${dataDir}/PG_VERSION; then | ||
149 | mkdir -m 0700 -p ${dataDir} | ||
150 | chown -R postgres:postgres ${dataDir} | ||
151 | fi | ||
152 | ''; | ||
153 | script = let | ||
154 | fp = n: config.secrets.fullPaths."postgresql_replication/${name}/${n}"; | ||
155 | in '' | ||
156 | if ! test -e ${dataDir}/PG_VERSION; then | ||
157 | pg_basebackup -d $(cat ${fp "connection_string"}) -D ${dataDir} -S ${hcfg.slot} | ||
158 | fi | ||
159 | ln -sfn ${fp "recovery.conf"} ${dataDir}/recovery.conf | ||
160 | ln -sfn ${fp "postgresql.conf"} ${dataDir}/postgresql.conf | ||
161 | |||
162 | exec postgres | ||
163 | ''; | ||
164 | |||
165 | serviceConfig = { | ||
166 | ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; | ||
167 | User = "postgres"; | ||
168 | Group = "postgres"; | ||
169 | PermissionsStartOnly = true; | ||
170 | RuntimeDirectory = "postgresql"; | ||
171 | Type = "notify"; | ||
172 | |||
173 | KillSignal = "SIGINT"; | ||
174 | KillMode = "mixed"; | ||
175 | # basebackup can take a long time | ||
176 | TimeoutStartSec="infinity"; | ||
177 | TimeoutStopSec = 120; | ||
178 | }; | ||
179 | unitConfig.RequiresMountsFor = dataDir; | ||
180 | }) cfg.hosts; | ||
181 | }; | ||
182 | } | ||
diff --git a/modules/private/databases/redis.nix b/modules/private/databases/redis.nix deleted file mode 100644 index 685fa46..0000000 --- a/modules/private/databases/redis.nix +++ /dev/null | |||
@@ -1,133 +0,0 @@ | |||
1 | { lib, config, pkgs, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databases.redis; | ||
4 | in { | ||
5 | options.myServices.databases.redis = { | ||
6 | enable = lib.mkOption { | ||
7 | default = false; | ||
8 | example = true; | ||
9 | description = "Whether to enable redis database"; | ||
10 | type = lib.types.bool; | ||
11 | }; | ||
12 | socketsDir = lib.mkOption { | ||
13 | type = lib.types.path; | ||
14 | default = "/run/redis"; | ||
15 | description = '' | ||
16 | The directory where Redis puts sockets. | ||
17 | ''; | ||
18 | }; | ||
19 | # Output variables | ||
20 | sockets = lib.mkOption { | ||
21 | type = lib.types.attrsOf lib.types.path; | ||
22 | default = { | ||
23 | redis = "${cfg.socketsDir}/redis.sock"; | ||
24 | }; | ||
25 | readOnly = true; | ||
26 | description = '' | ||
27 | Redis sockets | ||
28 | ''; | ||
29 | }; | ||
30 | }; | ||
31 | |||
32 | config = lib.mkIf cfg.enable { | ||
33 | users.users.redis.uid = config.ids.uids.redis; | ||
34 | users.groups.redis.gid = config.ids.gids.redis; | ||
35 | services.redis = rec { | ||
36 | enable = true; | ||
37 | bind = "127.0.0.1"; | ||
38 | unixSocket = cfg.sockets.redis; | ||
39 | extraConfig = '' | ||
40 | unixsocketperm 777 | ||
41 | maxclients 1024 | ||
42 | ''; | ||
43 | }; | ||
44 | systemd.services.redis.serviceConfig.Slice = "redis.slice"; | ||
45 | |||
46 | services.spiped = { | ||
47 | enable = true; | ||
48 | config.redis = { | ||
49 | decrypt = true; | ||
50 | source = "0.0.0.0:16379"; | ||
51 | target = "/run/redis/redis.sock"; | ||
52 | keyfile = config.secrets.fullPaths."redis/spiped_keyfile"; | ||
53 | }; | ||
54 | }; | ||
55 | systemd.services.spiped_redis = { | ||
56 | description = "Secure pipe 'redis'"; | ||
57 | after = [ "network.target" ]; | ||
58 | wantedBy = [ "multi-user.target" ]; | ||
59 | |||
60 | serviceConfig = { | ||
61 | Slice = "redis.slice"; | ||
62 | Restart = "always"; | ||
63 | User = "spiped"; | ||
64 | PermissionsStartOnly = true; | ||
65 | SupplementaryGroups = "keys"; | ||
66 | }; | ||
67 | |||
68 | script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/redis.spec`"; | ||
69 | }; | ||
70 | |||
71 | services.filesWatcher.predixy = { | ||
72 | restart = true; | ||
73 | paths = [ config.secrets.fullPaths."redis/predixy.conf" ]; | ||
74 | }; | ||
75 | |||
76 | networking.firewall.allowedTCPPorts = [ 7617 16379 ]; | ||
77 | secrets.keys = { | ||
78 | "redis/predixy.conf" = { | ||
79 | user = "redis"; | ||
80 | group = "redis"; | ||
81 | permissions = "0400"; | ||
82 | text = '' | ||
83 | Name Predixy | ||
84 | Bind 127.0.0.1:7617 | ||
85 | ClientTimeout 300 | ||
86 | WorkerThreads 1 | ||
87 | |||
88 | Authority { | ||
89 | Auth "${config.myEnv.databases.redis.predixy.read}" { | ||
90 | Mode read | ||
91 | } | ||
92 | } | ||
93 | |||
94 | StandaloneServerPool { | ||
95 | Databases 16 | ||
96 | RefreshMethod fixed | ||
97 | Group shard001 { | ||
98 | + ${config.myEnv.databases.redis.socket} | ||
99 | } | ||
100 | } | ||
101 | ''; | ||
102 | }; | ||
103 | "redis/spiped_keyfile" = { | ||
104 | user = "spiped"; | ||
105 | group = "spiped"; | ||
106 | permissions = "0400"; | ||
107 | text = config.myEnv.databases.redis.spiped_key; | ||
108 | }; | ||
109 | }; | ||
110 | |||
111 | systemd.slices.redis = { | ||
112 | description = "Redis slice"; | ||
113 | }; | ||
114 | |||
115 | systemd.services.predixy = { | ||
116 | description = "Redis proxy"; | ||
117 | wantedBy = [ "multi-user.target" ]; | ||
118 | after = [ "redis.service" ]; | ||
119 | |||
120 | serviceConfig = { | ||
121 | Slice = "redis.slice"; | ||
122 | User = "redis"; | ||
123 | Group = "redis"; | ||
124 | SupplementaryGroups = "keys"; | ||
125 | Type = "simple"; | ||
126 | |||
127 | ExecStart = "${pkgs.predixy}/bin/predixy ${config.secrets.fullPaths."redis/predixy.conf"}"; | ||
128 | }; | ||
129 | |||
130 | }; | ||
131 | }; | ||
132 | } | ||
133 | |||
diff --git a/modules/private/databases/redis_replication.nix b/modules/private/databases/redis_replication.nix deleted file mode 100644 index 9e48939..0000000 --- a/modules/private/databases/redis_replication.nix +++ /dev/null | |||
@@ -1,171 +0,0 @@ | |||
1 | { pkgs, config, lib, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databasesReplication.redis; | ||
4 | in | ||
5 | { | ||
6 | options.myServices.databasesReplication.redis = { | ||
7 | enable = lib.mkEnableOption "Enable redis replication"; | ||
8 | base = lib.mkOption { | ||
9 | type = lib.types.path; | ||
10 | description = '' | ||
11 | Base path to put the replications | ||
12 | ''; | ||
13 | }; | ||
14 | hosts = lib.mkOption { | ||
15 | default = {}; | ||
16 | description = '' | ||
17 | Hosts to backup | ||
18 | ''; | ||
19 | type = lib.types.attrsOf (lib.types.submodule { | ||
20 | options = { | ||
21 | package = lib.mkOption { | ||
22 | type = lib.types.package; | ||
23 | default = pkgs.redis; | ||
24 | description = '' | ||
25 | Redis package for this host | ||
26 | ''; | ||
27 | }; | ||
28 | host = lib.mkOption { | ||
29 | type = lib.types.str; | ||
30 | description = '' | ||
31 | Host to connect to | ||
32 | ''; | ||
33 | }; | ||
34 | port = lib.mkOption { | ||
35 | type = lib.types.str; | ||
36 | description = '' | ||
37 | Port to connect to | ||
38 | ''; | ||
39 | }; | ||
40 | password = lib.mkOption { | ||
41 | type = lib.types.nullOr lib.types.str; | ||
42 | default = null; | ||
43 | description = '' | ||
44 | Password to use | ||
45 | ''; | ||
46 | }; | ||
47 | }; | ||
48 | }); | ||
49 | }; | ||
50 | }; | ||
51 | |||
52 | config = lib.mkIf cfg.enable { | ||
53 | users.users.redis = { | ||
54 | description = "Redis database user"; | ||
55 | group = "redis"; | ||
56 | uid = config.ids.uids.redis; | ||
57 | extraGroups = [ "keys" ]; | ||
58 | }; | ||
59 | users.groups.redis.gid = config.ids.gids.redis; | ||
60 | |||
61 | services.spiped = { # sync from eldiron | ||
62 | enable = true; | ||
63 | config.redis = { | ||
64 | encrypt = true; | ||
65 | source = "127.0.0.1:16379"; | ||
66 | target = "${config.myEnv.servers.eldiron.ips.main.ip4}:16379"; | ||
67 | keyfile = config.secrets.fullPaths."redis/spiped_eldiron_keyfile"; | ||
68 | }; | ||
69 | }; | ||
70 | |||
71 | secrets.keys = lib.mapAttrs' (name: hcfg: | ||
72 | lib.nameValuePair "redis_replication/${name}/config" { | ||
73 | user = "redis"; | ||
74 | group = "redis"; | ||
75 | permissions = "0400"; | ||
76 | text = '' | ||
77 | pidfile ${cfg.base}/${name}/redis/redis.pid | ||
78 | port 0 | ||
79 | unixsocket /run/redis_${name}/redis.sock | ||
80 | loglevel notice | ||
81 | logfile /dev/null | ||
82 | syslog-enabled yes | ||
83 | databases 16 | ||
84 | save 900 1 | ||
85 | save 300 10 | ||
86 | save 60 10000 | ||
87 | dbfilename dump.rdb | ||
88 | dir ${cfg.base}/${name}/redis/ | ||
89 | slaveof ${hcfg.host} ${hcfg.port} | ||
90 | ${if hcfg.password != null then "masterauth ${hcfg.password}" else ""} | ||
91 | appendOnly no | ||
92 | appendfsync everysec | ||
93 | slowlog-log-slower-than 10000 | ||
94 | slowlog-max-len 128 | ||
95 | unixsocketperm 777 | ||
96 | maxclients 1024 | ||
97 | ''; | ||
98 | } | ||
99 | ) cfg.hosts // { | ||
100 | "redis/spiped_eldiron_keyfile" = { # For eldiron only | ||
101 | user = "spiped"; | ||
102 | group = "spiped"; | ||
103 | permissions = "0400"; | ||
104 | text = config.myEnv.databases.redis.spiped_key; | ||
105 | }; | ||
106 | }; | ||
107 | |||
108 | services.cron = { | ||
109 | enable = true; | ||
110 | systemCronJobs = lib.flatten (lib.mapAttrsToList (name: hcfg: | ||
111 | let | ||
112 | dataDir = "${cfg.base}/${name}/redis"; | ||
113 | backupDir = "${cfg.base}/${name}/redis_backup"; | ||
114 | backup_script = pkgs.writeScript "backup_redis_${name}" '' | ||
115 | #!${pkgs.stdenv.shell} | ||
116 | |||
117 | ${pkgs.coreutils}/bin/cp ${cfg.base}/${name}/redis/dump.rdb \ | ||
118 | ${backupDir}/$(${pkgs.coreutils}/bin/date -Iminutes).rdb | ||
119 | ''; | ||
120 | u = pkgs.callPackage ./utils.nix {}; | ||
121 | cleanup_script = pkgs.writeScript "cleanup_redis_${name}" (u.exponentialDumps "rdb" backupDir); | ||
122 | in [ | ||
123 | "0 22,4,10,16 * * * root ${backup_script}" | ||
124 | "0 3 * * * root ${cleanup_script}" | ||
125 | ]) cfg.hosts); | ||
126 | }; | ||
127 | |||
128 | system.activationScripts = lib.attrsets.mapAttrs' (name: hcfg: | ||
129 | lib.attrsets.nameValuePair "redis_replication_${name}" { | ||
130 | deps = [ "users" "groups" ]; | ||
131 | text = '' | ||
132 | install -m 0700 -o redis -g redis -d ${cfg.base}/${name}/redis | ||
133 | install -m 0700 -o redis -g redis -d ${cfg.base}/${name}/redis_backup | ||
134 | ''; | ||
135 | }) cfg.hosts; | ||
136 | |||
137 | systemd.services = { | ||
138 | spiped_redis = { # For eldiron | ||
139 | description = "Secure pipe 'redis'"; | ||
140 | after = [ "network.target" ]; | ||
141 | wantedBy = [ "multi-user.target" ]; | ||
142 | |||
143 | serviceConfig = { | ||
144 | Restart = "always"; | ||
145 | User = "spiped"; | ||
146 | PermissionsStartOnly = true; | ||
147 | SupplementaryGroups = "keys"; | ||
148 | }; | ||
149 | |||
150 | script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/redis.spec`"; | ||
151 | }; | ||
152 | } // lib.attrsets.mapAttrs' (name: hcfg: | ||
153 | let | ||
154 | dataDir = "${cfg.base}/${name}/redis"; | ||
155 | in | ||
156 | lib.attrsets.nameValuePair "redis_backup_${name}" { | ||
157 | description = "Redis replication for ${name}"; | ||
158 | wantedBy = [ "multi-user.target" ]; | ||
159 | after = [ "network.target" ]; | ||
160 | unitConfig.RequiresMountsFor = dataDir; | ||
161 | |||
162 | serviceConfig = { | ||
163 | ExecStart = "${hcfg.package}/bin/redis-server ${config.secrets.fullPaths."redis_replication/${name}/config"}"; | ||
164 | User = "redis"; | ||
165 | RuntimeDirectory = "redis_${name}"; | ||
166 | }; | ||
167 | }) cfg.hosts; | ||
168 | }; | ||
169 | } | ||
170 | |||
171 | |||
diff --git a/modules/private/databases/utils.nix b/modules/private/databases/utils.nix deleted file mode 100644 index 47988fc..0000000 --- a/modules/private/databases/utils.nix +++ /dev/null | |||
@@ -1,30 +0,0 @@ | |||
1 | { pkgs }: | ||
2 | { | ||
3 | keepLastNDumps = ext: backupDir: n: '' | ||
4 | #!${pkgs.stdenv.shell} | ||
5 | |||
6 | cd ${backupDir} | ||
7 | ${pkgs.coreutils}/bin/rm -f \ | ||
8 | $(${pkgs.coreutils}/bin/ls -1 *.${ext} \ | ||
9 | | ${pkgs.coreutils}/bin/sort -r \ | ||
10 | | ${pkgs.gnused}/bin/sed -e '1,${builtins.toString n}d') | ||
11 | ''; | ||
12 | exponentialDumps = ext: backupDir: let | ||
13 | log2rotateSrc = builtins.fetchGit { | ||
14 | url = "https://github.com/avian2/pylog2rotate"; | ||
15 | ref = "master"; | ||
16 | rev = "061f0564757289d3bea553b16f8fd5c4a0319c5e"; | ||
17 | }; | ||
18 | log2rotate = pkgs.writeScript "log2rotate" '' | ||
19 | #!${pkgs.python3}/bin/python | ||
20 | |||
21 | ${builtins.readFile "${log2rotateSrc}/log2rotate.py"} | ||
22 | ''; | ||
23 | in '' | ||
24 | #!${pkgs.stdenv.shell} | ||
25 | |||
26 | cd ${backupDir} | ||
27 | ${pkgs.coreutils}/bin/rm -f $(ls -1 *.${ext} | grep -v 'T22:' | sort -r | sed -e '1,12d') | ||
28 | ${pkgs.coreutils}/bin/rm -f $(ls -1 *T22*.${ext} | ${log2rotate} --skip 7 --fuzz 7 --delete --format='%Y-%m-%dT%H:%M+00:00.${ext}') | ||
29 | ''; | ||
30 | } | ||