diff options
Diffstat (limited to 'systems/backup-2/databases')
-rw-r--r-- | systems/backup-2/databases/mariadb_replication.nix | 271 | ||||
-rw-r--r-- | systems/backup-2/databases/openldap_replication.nix | 165 | ||||
-rw-r--r-- | systems/backup-2/databases/postgresql_replication.nix | 203 | ||||
-rw-r--r-- | systems/backup-2/databases/redis_replication.nix | 171 | ||||
-rw-r--r-- | systems/backup-2/databases/utils.nix | 30 |
5 files changed, 840 insertions, 0 deletions
diff --git a/systems/backup-2/databases/mariadb_replication.nix b/systems/backup-2/databases/mariadb_replication.nix new file mode 100644 index 0000000..8d2b457 --- /dev/null +++ b/systems/backup-2/databases/mariadb_replication.nix | |||
@@ -0,0 +1,271 @@ | |||
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.int; | ||
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 | myServices.chatonsProperties.hostings.mysql-replication = { | ||
77 | file.datetime = "2022-08-27T15:00:00"; | ||
78 | hosting = { | ||
79 | name = "Mysql replication"; | ||
80 | description = "Replication of mysql database"; | ||
81 | website = "db-1.immae.eu"; | ||
82 | status.level = "OK"; | ||
83 | status.description = "OK"; | ||
84 | registration.load = "OPEN"; | ||
85 | install.type = "PACKAGE"; | ||
86 | }; | ||
87 | software = { | ||
88 | name = "MariaDB"; | ||
89 | website = "https://mariadb.org/"; | ||
90 | license.url = "https://github.com/MariaDB/server/blob/10.11/COPYING"; | ||
91 | license.name = "GNU General Public License v2.0"; | ||
92 | version = pkgs.mariadb.version; | ||
93 | source.url = "https://github.com/MariaDB/server"; | ||
94 | }; | ||
95 | }; | ||
96 | users.users.mysql = { | ||
97 | description = "MySQL server user"; | ||
98 | group = "mysql"; | ||
99 | uid = config.ids.uids.mysql; | ||
100 | extraGroups = [ "keys" ]; | ||
101 | }; | ||
102 | users.groups.mysql.gid = config.ids.gids.mysql; | ||
103 | |||
104 | secrets.keys = lib.listToAttrs (lib.flatten (lib.mapAttrsToList (name: hcfg: [ | ||
105 | (lib.nameValuePair "mysql_replication/${name}/slave_init_commands" { | ||
106 | user = "mysql"; | ||
107 | group = "mysql"; | ||
108 | permissions = "0400"; | ||
109 | text = '' | ||
110 | CHANGE MASTER TO master_host="${hcfg.host}", master_port=${builtins.toString hcfg.port}, master_user="${hcfg.user}", master_password="${hcfg.password}", master_ssl=1, master_use_gtid=slave_pos; | ||
111 | START SLAVE; | ||
112 | ''; | ||
113 | }) | ||
114 | (lib.nameValuePair "mysql_replication/${name}/mysqldump_remote" { | ||
115 | permissions = "0400"; | ||
116 | user = "root"; | ||
117 | group = "root"; | ||
118 | text = '' | ||
119 | [mysqldump] | ||
120 | user = ${hcfg.user} | ||
121 | password = ${hcfg.password} | ||
122 | ''; | ||
123 | }) | ||
124 | (lib.nameValuePair "mysql_replication/${name}/mysqldump" { | ||
125 | permissions = "0400"; | ||
126 | user = "root"; | ||
127 | group = "root"; | ||
128 | text = '' | ||
129 | [mysqldump] | ||
130 | user = ${hcfg.dumpUser} | ||
131 | password = ${hcfg.dumpPassword} | ||
132 | ''; | ||
133 | }) | ||
134 | (lib.nameValuePair "mysql_replication/${name}/client" { | ||
135 | permissions = "0400"; | ||
136 | user = "mysql"; | ||
137 | group = "mysql"; | ||
138 | text = '' | ||
139 | [client] | ||
140 | user = ${hcfg.dumpUser} | ||
141 | password = ${hcfg.dumpPassword} | ||
142 | ''; | ||
143 | }) | ||
144 | ]) cfg.hosts)); | ||
145 | |||
146 | services.cron = { | ||
147 | enable = true; | ||
148 | systemCronJobs = lib.flatten (lib.mapAttrsToList (name: hcfg: | ||
149 | let | ||
150 | dataDir = "${cfg.base}/${name}/mysql"; | ||
151 | backupDir = "${cfg.base}/${name}/mysql_backup"; | ||
152 | backup_script = pkgs.writeScript "backup_mysql_${name}" '' | ||
153 | #!${pkgs.stdenv.shell} | ||
154 | |||
155 | set -euo pipefail | ||
156 | |||
157 | filename=${backupDir}/$(${pkgs.coreutils}/bin/date -Iminutes).sql | ||
158 | ${hcfg.package}/bin/mysqldump \ | ||
159 | --defaults-file=${config.secrets.fullPaths."mysql_replication/${name}/mysqldump"} \ | ||
160 | -S /run/mysqld_${name}/mysqld.sock \ | ||
161 | --gtid \ | ||
162 | --master-data \ | ||
163 | --flush-privileges \ | ||
164 | --ignore-database=netdata \ | ||
165 | --all-databases > $filename | ||
166 | ${pkgs.gzip}/bin/gzip $filename | ||
167 | ''; | ||
168 | u = pkgs.callPackage ./utils.nix {}; | ||
169 | cleanup_script = pkgs.writeScript "cleanup_mysql_${name}" (u.exponentialDumps "sql.gz" backupDir); | ||
170 | in [ | ||
171 | "0 22,4,10,16 * * * root ${backup_script}" | ||
172 | "0 3 * * * root ${cleanup_script}" | ||
173 | ]) cfg.hosts); | ||
174 | }; | ||
175 | |||
176 | system.activationScripts = lib.attrsets.mapAttrs' (name: hcfg: | ||
177 | lib.attrsets.nameValuePair "mysql_replication_${name}" { | ||
178 | deps = [ "users" "groups" ]; | ||
179 | text = '' | ||
180 | install -m 0700 -o mysql -g mysql -d ${cfg.base}/${name}/mysql | ||
181 | install -m 0700 -o mysql -g mysql -d ${cfg.base}/${name}/mysql_backup | ||
182 | ''; | ||
183 | }) cfg.hosts; | ||
184 | |||
185 | environment.etc = lib.attrsets.mapAttrs' (name: hcfg: | ||
186 | lib.attrsets.nameValuePair "mysql/${name}_my.cnf" { | ||
187 | text = '' | ||
188 | [mysqld] | ||
189 | skip-networking | ||
190 | socket = /run/mysqld_${name}/mysqld.sock | ||
191 | datadir = ${cfg.base}/${name}/mysql/ | ||
192 | log-bin = mariadb-bin | ||
193 | server-id = ${builtins.toString hcfg.serverId} | ||
194 | ''; | ||
195 | } | ||
196 | ) cfg.hosts; | ||
197 | |||
198 | systemd.services = lib.attrsets.mapAttrs' (name: hcfg: | ||
199 | let | ||
200 | dataDir = "${cfg.base}/${name}/mysql"; | ||
201 | in | ||
202 | lib.attrsets.nameValuePair "mysql_backup_${name}" { | ||
203 | description = "Mysql replication for ${name}"; | ||
204 | wantedBy = [ "multi-user.target" ]; | ||
205 | after = [ "network.target" ]; | ||
206 | restartTriggers = [ config.environment.etc."mysql/${name}_my.cnf".source ]; | ||
207 | unitConfig.RequiresMountsFor = dataDir; | ||
208 | |||
209 | preStart = '' | ||
210 | if ! test -e ${dataDir}/mysql; then | ||
211 | if ! test -e ${dataDir}/initial.sql; then | ||
212 | ${hcfg.package}/bin/mysqldump \ | ||
213 | --defaults-file=${config.secrets.fullPaths."mysql_replication/${name}/mysqldump_remote"} \ | ||
214 | -h ${hcfg.host} \ | ||
215 | -P ${builtins.toString hcfg.port} \ | ||
216 | --ssl \ | ||
217 | --gtid \ | ||
218 | --flush-privileges \ | ||
219 | --master-data \ | ||
220 | --all-databases > ${dataDir}/initial.sql | ||
221 | fi | ||
222 | |||
223 | ${hcfg.package}/bin/mysql_install_db \ | ||
224 | --defaults-file=/etc/mysql/${name}_my.cnf \ | ||
225 | --user=mysql \ | ||
226 | --datadir=${dataDir} \ | ||
227 | --basedir=${hcfg.package} | ||
228 | fi | ||
229 | ''; | ||
230 | |||
231 | serviceConfig = { | ||
232 | User = "mysql"; | ||
233 | Group = "mysql"; | ||
234 | RuntimeDirectory = "mysqld_${name}"; | ||
235 | RuntimeDirectoryMode = "0755"; | ||
236 | SupplementaryGroups = "keys"; | ||
237 | PermissionsStartOnly = true; | ||
238 | Type = "notify"; | ||
239 | |||
240 | ExecStart = "${hcfg.package}/bin/mysqld --defaults-file=/etc/mysql/${name}_my.cnf --user=mysql --datadir=${dataDir} --basedir=${hcfg.package}"; | ||
241 | ExecStartPost = | ||
242 | let | ||
243 | sql_before = pkgs.writeText "mysql-initial-before" '' | ||
244 | DROP DATABASE test; | ||
245 | INSTALL SONAME 'auth_pam'; | ||
246 | ''; | ||
247 | setupScript = pkgs.writeScript "mysql-setup" '' | ||
248 | #!${pkgs.runtimeShell} -e | ||
249 | |||
250 | if test -e ${dataDir}/initial.sql; then | ||
251 | cat \ | ||
252 | ${sql_before} \ | ||
253 | ${dataDir}/initial.sql \ | ||
254 | ${config.secrets.fullPaths."mysql_replication/${name}/slave_init_commands"} \ | ||
255 | | ${hcfg.package}/bin/mysql \ | ||
256 | --defaults-file=/etc/mysql/${name}_my.cnf \ | ||
257 | -S /run/mysqld_${name}/mysqld.sock \ | ||
258 | --user=root | ||
259 | rm -f ${dataDir}/initial.sql | ||
260 | fi | ||
261 | ''; | ||
262 | in | ||
263 | "+${setupScript}"; | ||
264 | # initial dump can take a long time | ||
265 | TimeoutStartSec="infinity"; | ||
266 | TimeoutStopSec = 120; | ||
267 | }; | ||
268 | }) cfg.hosts; | ||
269 | }; | ||
270 | } | ||
271 | |||
diff --git a/systems/backup-2/databases/openldap_replication.nix b/systems/backup-2/databases/openldap_replication.nix new file mode 100644 index 0000000..b962224 --- /dev/null +++ b/systems/backup-2/databases/openldap_replication.nix | |||
@@ -0,0 +1,165 @@ | |||
1 | { pkgs, config, lib, openldap, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databasesReplication.openldap; | ||
4 | ldapConfig = hcfg: name: pkgs.writeText "slapd.conf" '' | ||
5 | include ${pkgs.openldap}/etc/schema/core.schema | ||
6 | include ${pkgs.openldap}/etc/schema/cosine.schema | ||
7 | include ${pkgs.openldap}/etc/schema/inetorgperson.schema | ||
8 | include ${pkgs.openldap}/etc/schema/nis.schema | ||
9 | include ${openldap.immae-schema} | ||
10 | pidfile /run/slapd_${name}/slapd.pid | ||
11 | argsfile /run/slapd_${name}/slapd.args | ||
12 | |||
13 | moduleload back_mdb | ||
14 | backend mdb | ||
15 | database mdb | ||
16 | |||
17 | suffix "${hcfg.base}" | ||
18 | rootdn "cn=root,${hcfg.base}" | ||
19 | directory ${cfg.base}/${name}/openldap | ||
20 | |||
21 | index objectClass eq | ||
22 | index uid pres,eq | ||
23 | index entryUUID eq | ||
24 | |||
25 | include ${config.secrets.fullPaths."openldap_replication/${name}/replication_config"} | ||
26 | ''; | ||
27 | in | ||
28 | { | ||
29 | options.myServices.databasesReplication.openldap = { | ||
30 | enable = lib.mkEnableOption "Enable openldap replication"; | ||
31 | base = lib.mkOption { | ||
32 | type = lib.types.path; | ||
33 | description = '' | ||
34 | Base path to put the replications | ||
35 | ''; | ||
36 | }; | ||
37 | hosts = lib.mkOption { | ||
38 | default = {}; | ||
39 | description = '' | ||
40 | Hosts to backup | ||
41 | ''; | ||
42 | type = lib.types.attrsOf (lib.types.submodule { | ||
43 | options = { | ||
44 | package = lib.mkOption { | ||
45 | type = lib.types.package; | ||
46 | default = pkgs.openldap; | ||
47 | description = '' | ||
48 | Openldap package for this host | ||
49 | ''; | ||
50 | }; | ||
51 | url = lib.mkOption { | ||
52 | type = lib.types.str; | ||
53 | description = '' | ||
54 | Host to connect to | ||
55 | ''; | ||
56 | }; | ||
57 | base = lib.mkOption { | ||
58 | type = lib.types.str; | ||
59 | description = '' | ||
60 | Base DN to replicate | ||
61 | ''; | ||
62 | }; | ||
63 | dn = lib.mkOption { | ||
64 | type = lib.types.str; | ||
65 | description = '' | ||
66 | DN to use | ||
67 | ''; | ||
68 | }; | ||
69 | password = lib.mkOption { | ||
70 | type = lib.types.str; | ||
71 | description = '' | ||
72 | Password to use | ||
73 | ''; | ||
74 | }; | ||
75 | }; | ||
76 | }); | ||
77 | }; | ||
78 | }; | ||
79 | |||
80 | config = lib.mkIf cfg.enable { | ||
81 | users.users.openldap = { | ||
82 | description = "Openldap database user"; | ||
83 | group = "openldap"; | ||
84 | uid = config.ids.uids.openldap; | ||
85 | extraGroups = [ "keys" ]; | ||
86 | }; | ||
87 | users.groups.openldap.gid = config.ids.gids.openldap; | ||
88 | |||
89 | secrets.keys = lib.listToAttrs (lib.flatten (lib.mapAttrsToList (name: hcfg: [ | ||
90 | (lib.nameValuePair "openldap_replication/${name}/replication_config" { | ||
91 | user = "openldap"; | ||
92 | group = "openldap"; | ||
93 | permissions = "0400"; | ||
94 | text = '' | ||
95 | syncrepl rid=000 | ||
96 | provider=${hcfg.url} | ||
97 | type=refreshAndPersist | ||
98 | searchbase="${hcfg.base}" | ||
99 | retry="5 10 300 +" | ||
100 | attrs="*,+" | ||
101 | schemachecking=off | ||
102 | bindmethod=simple | ||
103 | binddn="${hcfg.dn}" | ||
104 | credentials="${hcfg.password}" | ||
105 | ''; | ||
106 | }) | ||
107 | (lib.nameValuePair "openldap_replication/${name}/replication_password" { | ||
108 | user = "openldap"; | ||
109 | group = "openldap"; | ||
110 | permissions = "0400"; | ||
111 | text = hcfg.password; | ||
112 | }) | ||
113 | ]) cfg.hosts)); | ||
114 | |||
115 | services.cron = { | ||
116 | enable = true; | ||
117 | systemCronJobs = lib.flatten (lib.mapAttrsToList (name: hcfg: | ||
118 | let | ||
119 | dataDir = "${cfg.base}/${name}/openldap"; | ||
120 | backupDir = "${cfg.base}/${name}/openldap_backup"; | ||
121 | backup_script = pkgs.writeScript "backup_openldap_${name}" '' | ||
122 | #!${pkgs.stdenv.shell} | ||
123 | |||
124 | ${hcfg.package}/bin/slapcat -b "${hcfg.base}" -f ${ldapConfig hcfg name} -l ${backupDir}/$(${pkgs.coreutils}/bin/date -Iminutes).ldif | ||
125 | ''; | ||
126 | u = pkgs.callPackage ./utils.nix {}; | ||
127 | cleanup_script = pkgs.writeScript "cleanup_openldap_${name}" (u.exponentialDumps "ldif" backupDir); | ||
128 | in [ | ||
129 | "0 22,4,10,16 * * * root ${backup_script}" | ||
130 | "0 3 * * * root ${cleanup_script}" | ||
131 | ]) cfg.hosts); | ||
132 | }; | ||
133 | |||
134 | system.activationScripts = lib.attrsets.mapAttrs' (name: hcfg: | ||
135 | lib.attrsets.nameValuePair "openldap_replication_${name}" { | ||
136 | deps = [ "users" "groups" ]; | ||
137 | text = '' | ||
138 | install -m 0700 -o openldap -g openldap -d ${cfg.base}/${name}/openldap | ||
139 | install -m 0700 -o openldap -g openldap -d ${cfg.base}/${name}/openldap_backup | ||
140 | ''; | ||
141 | }) cfg.hosts; | ||
142 | |||
143 | systemd.services = lib.attrsets.mapAttrs' (name: hcfg: | ||
144 | let | ||
145 | dataDir = "${cfg.base}/${name}/openldap"; | ||
146 | in | ||
147 | lib.attrsets.nameValuePair "openldap_backup_${name}" { | ||
148 | description = "Openldap replication for ${name}"; | ||
149 | wantedBy = [ "multi-user.target" ]; | ||
150 | after = [ "network.target" ]; | ||
151 | unitConfig.RequiresMountsFor = dataDir; | ||
152 | |||
153 | preStart = '' | ||
154 | mkdir -p /run/slapd_${name} | ||
155 | chown -R "openldap:openldap" /run/slapd_${name} | ||
156 | ''; | ||
157 | |||
158 | serviceConfig = { | ||
159 | ExecStart = "${hcfg.package}/libexec/slapd -d 0 -u openldap -g openldap -f ${ldapConfig hcfg name}"; | ||
160 | }; | ||
161 | }) cfg.hosts; | ||
162 | }; | ||
163 | } | ||
164 | |||
165 | |||
diff --git a/systems/backup-2/databases/postgresql_replication.nix b/systems/backup-2/databases/postgresql_replication.nix new file mode 100644 index 0000000..5351a4f --- /dev/null +++ b/systems/backup-2/databases/postgresql_replication.nix | |||
@@ -0,0 +1,203 @@ | |||
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 | myServices.chatonsProperties.hostings.postgresql-replication = { | ||
54 | file.datetime = "2022-08-27T15:00:00"; | ||
55 | hosting = { | ||
56 | name = "PostgreSQL replication"; | ||
57 | description = "Replication of PostgreSQL database"; | ||
58 | website = "db-1.immae.eu"; | ||
59 | status.level = "OK"; | ||
60 | status.description = "OK"; | ||
61 | registration.load = "OPEN"; | ||
62 | install.type = "PACKAGE"; | ||
63 | }; | ||
64 | software = { | ||
65 | name = "PostgreSQL"; | ||
66 | website = "https://www.postgresql.org/"; | ||
67 | license.url = "https://www.postgresql.org/about/licence/"; | ||
68 | license.name = "The PostgreSQL Licence"; | ||
69 | version = pkgs.postgresql.version; | ||
70 | source.url = "https://git.postgresql.org/gitweb/?p=postgresql.git;a=summary"; | ||
71 | }; | ||
72 | }; | ||
73 | users.users.postgres = { | ||
74 | name = "postgres"; | ||
75 | uid = config.ids.uids.postgres; | ||
76 | group = "postgres"; | ||
77 | description = "PostgreSQL server user"; | ||
78 | home = "/var/lib/postgresql"; | ||
79 | useDefaultShell = true; | ||
80 | extraGroups = [ "keys" ]; | ||
81 | }; | ||
82 | users.groups.postgres.gid = config.ids.gids.postgres; | ||
83 | environment.systemPackages = [ cfg.mainPackage ]; | ||
84 | |||
85 | secrets.keys = lib.listToAttrs (lib.flatten (lib.mapAttrsToList (name: hcfg: [ | ||
86 | (lib.nameValuePair "postgresql_replication/${name}/recovery.conf" { | ||
87 | user = "postgres"; | ||
88 | group = "postgres"; | ||
89 | permissions = "0400"; | ||
90 | text = '' | ||
91 | standby_mode = on | ||
92 | primary_conninfo = '${hcfg.connection}?sslmode=require' | ||
93 | primary_slot_name = '${hcfg.slot}' | ||
94 | ''; | ||
95 | }) | ||
96 | (lib.nameValuePair "postgresql_replication/${name}/connection_string" { | ||
97 | user = "postgres"; | ||
98 | group = "postgres"; | ||
99 | permissions = "0400"; | ||
100 | text = hcfg.connection; | ||
101 | }) | ||
102 | (lib.nameValuePair "postgresql_replication/${name}/postgresql.conf" { | ||
103 | user = "postgres"; | ||
104 | group = "postgres"; | ||
105 | permissions = "0400"; | ||
106 | text = let | ||
107 | dataDir = "${cfg.base}/${name}/postgresql"; | ||
108 | in '' | ||
109 | listen_addresses = ''' | ||
110 | unix_socket_directories = '${dataDir}' | ||
111 | data_directory = '${dataDir}' | ||
112 | wal_level = logical | ||
113 | max_connections = 300 | ||
114 | ''; | ||
115 | }) | ||
116 | ]) cfg.hosts)); | ||
117 | |||
118 | services.cron = { | ||
119 | enable = true; | ||
120 | systemCronJobs = lib.flatten (lib.mapAttrsToList (name: hcfg: | ||
121 | let | ||
122 | dataDir = "${cfg.base}/${name}/postgresql"; | ||
123 | backupDir = "${cfg.base}/${name}/postgresql_backup"; | ||
124 | backup_script = pkgs.writeScript "backup_psql_${name}" '' | ||
125 | #!${pkgs.stdenv.shell} | ||
126 | |||
127 | set -euo pipefail | ||
128 | |||
129 | resume_replication() { | ||
130 | ${hcfg.package}/bin/psql -h ${dataDir} -c "SELECT pg_wal_replay_resume();" >/dev/null || echo "impossible to resume replication" | ||
131 | } | ||
132 | |||
133 | trap resume_replication EXIT | ||
134 | |||
135 | ${hcfg.package}/bin/psql -h ${dataDir} -c "SELECT pg_wal_replay_pause();" >/dev/null || (echo "impossible to pause replication" && false) | ||
136 | |||
137 | ${hcfg.package}/bin/pg_dumpall -h ${dataDir} -f ${backupDir}/$(${pkgs.coreutils}/bin/date -Iminutes).sql | ||
138 | ''; | ||
139 | u = pkgs.callPackage ./utils.nix {}; | ||
140 | cleanup_script = pkgs.writeScript "cleanup_postgresql_${name}" (u.keepLastNDumps "sql" backupDir 6); | ||
141 | in [ | ||
142 | "0 22,4,10,16 * * * postgres ${backup_script}" | ||
143 | "0 3 * * * postgres ${cleanup_script}" | ||
144 | ]) cfg.hosts); | ||
145 | }; | ||
146 | |||
147 | system.activationScripts = lib.attrsets.mapAttrs' (name: hcfg: | ||
148 | lib.attrsets.nameValuePair "psql_replication_${name}" { | ||
149 | deps = [ "users" ]; | ||
150 | text = '' | ||
151 | install -m 0700 -o postgres -g postgres -d ${cfg.base}/${name}/postgresql | ||
152 | install -m 0700 -o postgres -g postgres -d ${cfg.base}/${name}/postgresql_backup | ||
153 | ''; | ||
154 | }) cfg.hosts; | ||
155 | |||
156 | systemd.services = lib.attrsets.mapAttrs' (name: hcfg: | ||
157 | let | ||
158 | dataDir = "${cfg.base}/${name}/postgresql"; | ||
159 | in | ||
160 | lib.attrsets.nameValuePair "postgresql_backup_${name}" { | ||
161 | description = "Postgresql replication for ${name}"; | ||
162 | wantedBy = [ "multi-user.target" ]; | ||
163 | after = [ "network.target" ]; | ||
164 | |||
165 | environment.PGDATA = dataDir; | ||
166 | path = [ hcfg.package ]; | ||
167 | |||
168 | preStart = '' | ||
169 | if ! test -e ${dataDir}/PG_VERSION; then | ||
170 | mkdir -m 0700 -p ${dataDir} | ||
171 | chown -R postgres:postgres ${dataDir} | ||
172 | fi | ||
173 | ''; | ||
174 | script = let | ||
175 | fp = n: config.secrets.fullPaths."postgresql_replication/${name}/${n}"; | ||
176 | in '' | ||
177 | if ! test -e ${dataDir}/PG_VERSION; then | ||
178 | pg_basebackup -d $(cat ${fp "connection_string"}) -D ${dataDir} -S ${hcfg.slot} | ||
179 | fi | ||
180 | ln -sfn ${fp "recovery.conf"} ${dataDir}/recovery.conf | ||
181 | ln -sfn ${fp "postgresql.conf"} ${dataDir}/postgresql.conf | ||
182 | |||
183 | exec postgres | ||
184 | ''; | ||
185 | |||
186 | serviceConfig = { | ||
187 | ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; | ||
188 | User = "postgres"; | ||
189 | Group = "postgres"; | ||
190 | PermissionsStartOnly = true; | ||
191 | RuntimeDirectory = "postgresql"; | ||
192 | Type = "notify"; | ||
193 | |||
194 | KillSignal = "SIGINT"; | ||
195 | KillMode = "mixed"; | ||
196 | # basebackup can take a long time | ||
197 | TimeoutStartSec="infinity"; | ||
198 | TimeoutStopSec = 120; | ||
199 | }; | ||
200 | unitConfig.RequiresMountsFor = dataDir; | ||
201 | }) cfg.hosts; | ||
202 | }; | ||
203 | } | ||
diff --git a/systems/backup-2/databases/redis_replication.nix b/systems/backup-2/databases/redis_replication.nix new file mode 100644 index 0000000..53fa904 --- /dev/null +++ b/systems/backup-2/databases/redis_replication.nix | |||
@@ -0,0 +1,171 @@ | |||
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 = "${lib.head 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/systems/backup-2/databases/utils.nix b/systems/backup-2/databases/utils.nix new file mode 100644 index 0000000..1b3190f --- /dev/null +++ b/systems/backup-2/databases/utils.nix | |||
@@ -0,0 +1,30 @@ | |||
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.python38}/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 | } | ||