aboutsummaryrefslogtreecommitdiff
path: root/modules/private/databases
diff options
context:
space:
mode:
Diffstat (limited to 'modules/private/databases')
-rw-r--r--modules/private/databases/default.nix57
-rw-r--r--modules/private/databases/mariadb.nix182
-rw-r--r--modules/private/databases/mariadb_replication.nix251
-rw-r--r--modules/private/databases/openldap/default.nix147
-rw-r--r--modules/private/databases/openldap/eldiron_schemas.nix21
-rw-r--r--modules/private/databases/openldap/immae.schema179
-rw-r--r--modules/private/databases/openldap_replication.nix166
-rw-r--r--modules/private/databases/postgresql.nix228
-rw-r--r--modules/private/databases/postgresql_replication.nix182
-rw-r--r--modules/private/databases/redis.nix133
-rw-r--r--modules/private/databases/redis_replication.nix171
-rw-r--r--modules/private/databases/utils.nix30
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, ... }:
2let
3 cfg = config.myServices.databases;
4in
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, ... }:
2let
3 cfg = config.myServices.databases.mariadb;
4in {
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, ... }:
2let
3 cfg = config.myServices.databasesReplication.mariadb;
4in
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, ... }:
2let
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 '';
24in
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 }:
2let
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 ];
20in
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:
2objectIdentifier Immaeroot 1.3.6.1.4.1.50071
3
4objectIdentifier Immae Immaeroot:2
5objectIdentifier ImmaeattributeType Immae:3
6objectIdentifier ImmaeobjectClass Immae:4
7
8# TT-RSS
9attributetype ( 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
15objectclass ( ImmaeobjectClass:1 NAME 'immaeTtrssClass'
16 DESC 'Expansion of the existing object classes for ttrss'
17 SUP top AUXILIARY
18 MUST ( immaeTtrssLogin ) )
19
20# FTP
21attributetype ( 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
26attributetype ( 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
31attributetype ( 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
36objectclass ( 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
43attributetype ( 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
48objectClass ( ImmaeobjectClass:3 NAME 'immaeSshClass'
49 DESC 'OpenSSH class'
50 SUP top AUXILIARY
51 MAy ( immaeSSHKey ) )
52
53# Specific access
54attributetype (ImmaeattributeType:6 NAME 'immaeAccessDn'
55 EQUALITY distinguishedNameMatch
56 SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
57
58attributetype (ImmaeattributeType:17 NAME 'immaeAccessWriteDn'
59 EQUALITY distinguishedNameMatch
60 SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
61
62attributetype (ImmaeattributeType:18 NAME 'immaeAccessReadSubtree'
63 EQUALITY distinguishedNameMatch
64 SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
65
66objectClass ( ImmaeobjectClass:4 NAME 'immaeAccessClass'
67 DESC 'Access class'
68 SUP top AUXILIARY
69 MAY ( immaeAccessDn $ immaeAccessWriteDn $ immaeAccessReadSubtree ) )
70
71# Xmpp uid
72attributetype ( 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
78objectclass ( 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
84attributetype ( 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
90attributetype ( 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
95attributetype ( 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
100attributetype ( 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
105attributetype ( 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
110objectclass ( 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
141attributetype (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
146objectclass ( ImmaeobjectClass:8 NAME 'immaePuppetClass'
147 DESC 'Expansion of the existing object classes for Puppet'
148 SUP top AUXILIARY
149 MUST ( immaePuppetJson )
150 )
151
152attributetype (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
158objectclass ( 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
165attributetype ( 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
171objectclass ( 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, ... }:
2let
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 '';
28in
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, ... }:
2let
3 cfg = config.myServices.databases.postgresql;
4in {
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, ... }:
2let
3 cfg = config.myServices.databasesReplication.postgresql;
4in
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, ... }:
2let
3 cfg = config.myServices.databases.redis;
4in {
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, ... }:
2let
3 cfg = config.myServices.databasesReplication.redis;
4in
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}