aboutsummaryrefslogtreecommitdiff
path: root/systems/backup-2/databases
diff options
context:
space:
mode:
Diffstat (limited to 'systems/backup-2/databases')
-rw-r--r--systems/backup-2/databases/mariadb_replication.nix271
-rw-r--r--systems/backup-2/databases/openldap_replication.nix165
-rw-r--r--systems/backup-2/databases/postgresql_replication.nix203
-rw-r--r--systems/backup-2/databases/redis_replication.nix171
-rw-r--r--systems/backup-2/databases/utils.nix30
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, ... }:
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.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, ... }:
2let
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 '';
27in
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, ... }:
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 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, ... }:
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 = "${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}