aboutsummaryrefslogtreecommitdiff
path: root/systems/backup-2
diff options
context:
space:
mode:
Diffstat (limited to 'systems/backup-2')
-rw-r--r--systems/backup-2/base.nix164
-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
-rw-r--r--systems/backup-2/flake.lock1159
-rw-r--r--systems/backup-2/flake.nix51
-rw-r--r--systems/backup-2/mail/relay.nix196
-rw-r--r--systems/backup-2/monitoring.nix117
10 files changed, 2527 insertions, 0 deletions
diff --git a/systems/backup-2/base.nix b/systems/backup-2/base.nix
new file mode 100644
index 0000000..97a364c
--- /dev/null
+++ b/systems/backup-2/base.nix
@@ -0,0 +1,164 @@
1{ config, pkgs, resources, name, lib, nixpkgs, secrets, ... }:
2{
3 # ssh-keyscan backup-2 | nix-shell -p ssh-to-age --run ssh-to-age
4 secrets.ageKeys = [ "age1kk3nr27qu42j28mcfdag5lhq0zu2pky7gfanvne8l4z2ctevjpgskmw0sr" ];
5 secrets.keys = {
6 "rsync_backup/identity" = {
7 user = "backup";
8 group = "backup";
9 permissions = "0400";
10 text = config.myEnv.rsync_backup.ssh_key.private;
11 };
12 "rsync_backup/identity.pub" = {
13 user = "backup";
14 group = "backup";
15 permissions = "0444";
16 text = config.myEnv.rsync_backup.ssh_key.public;
17 };
18 };
19 boot.kernelPackages = pkgs.linuxPackages_latest;
20
21 nixpkgs.config.permittedInsecurePackages = [
22 "python-2.7.18.6" # for nagios-cli
23 ];
24
25 imports =
26 [
27 secrets.nixosModules.users-config-backup-2
28 (nixpkgs + "/nixos/modules/profiles/qemu-guest.nix")
29 ./databases/mariadb_replication.nix
30 ./databases/openldap_replication.nix
31 ./databases/postgresql_replication.nix
32 ./databases/redis_replication.nix
33 ./mail/relay.nix
34 ./monitoring.nix
35 ];
36
37 fileSystems = {
38 "/backup2" = {
39 fsType = "ext4";
40 device = "UUID=b9425333-f567-435d-94d8-b26c22d93426";
41 };
42 "/" = { device = "/dev/sda1"; fsType = "ext4"; };
43 };
44
45 networking = {
46 firewall.enable = true;
47 interfaces."ens3".ipv4.addresses = pkgs.lib.flatten (pkgs.lib.attrsets.mapAttrsToList
48 (n: ips: map (ip: { address = ip; prefixLength = 32; }) (ips.ip4 or []))
49 (pkgs.lib.attrsets.filterAttrs (n: v: n != "main") config.hostEnv.ips));
50 interfaces."ens3".ipv6.addresses = pkgs.lib.flatten (pkgs.lib.attrsets.mapAttrsToList
51 (n: ips: map (ip: { address = ip; prefixLength = (if n == "main" && ip == pkgs.lib.head ips.ip6 then 64 else 128); }) (ips.ip6 or []))
52 config.hostEnv.ips);
53 defaultGateway6 = { address = "fe80::1"; interface = "ens3"; };
54 };
55
56 boot.loader.grub.device = "nodev";
57
58 security.acme.certs."${name}" = {
59 group = config.services.nginx.group;
60 };
61 services.nginx = {
62 enable = true;
63 recommendedOptimisation = true;
64 recommendedGzipSettings = true;
65 recommendedProxySettings = true;
66 };
67 networking.firewall.allowedTCPPorts = [ 80 443 ];
68
69 services.cron = {
70 mailto = "cron@immae.eu";
71 enable = true;
72 };
73
74 myServices.chatonsProperties.hostings.rsync-backup = {
75 file.datetime = "2022-08-27T16:00:00";
76 hosting = {
77 name = "Rsync backups";
78 description = "Remote initiated rsync backups";
79 website = "backup-2.v.immae.eu";
80 status.level = "OK";
81 status.description = "OK";
82 registration.load = "OPEN";
83 install.type = "PACKAGE";
84 };
85 software = {
86 name = "rsync";
87 website = "https://rsync.samba.org/";
88 license.url = "https://rsync.samba.org/GPL.html";
89 license.name = "GNU General Public License version 3";
90 version = pkgs.rsync.version;
91 source.url = "https://github.com/WayneD/rsync";
92 };
93 };
94
95 services.rsyncBackup = {
96 mountpoint = "/backup2";
97 profiles = config.myEnv.rsync_backup.profiles;
98 ssh_key_public = config.secrets.fullPaths."rsync_backup/identity.pub";
99 ssh_key_private = config.secrets.fullPaths."rsync_backup/identity";
100 };
101
102 myServices.mailRelay.enable = true;
103 myServices.mailBackup.enable = true;
104 myServices.monitoring.enable = true;
105 myServices.databasesReplication = {
106 postgresql = {
107 enable = true;
108 base = "/backup2";
109 mainPackage = pkgs.postgresql;
110 hosts = {
111 eldiron = {
112 slot = "backup_2";
113 connection = "postgresql://backup-2:${config.hostEnv.ldap.password}@eldiron.immae.eu";
114 package = pkgs.postgresql;
115 };
116 };
117 };
118 mariadb = {
119 enable = true;
120 base = "/backup2";
121 hosts = {
122 eldiron = {
123 serverId = 2;
124 # mysql resolves "backup-2" host and checks the ip, but uses /etc/hosts which only contains ip4
125 host = lib.head config.myEnv.servers.eldiron.ips.main.ip4;
126 port = config.myEnv.databases.mysql.port;
127 user = "backup-2";
128 password = config.hostEnv.ldap.password;
129 dumpUser = "root";
130 dumpPassword = config.myEnv.databases.mysql.systemUsers.root;
131 };
132 };
133 };
134 redis = {
135 enable = true;
136 base = "/backup2";
137 hosts = {
138 eldiron = {
139 host = "127.0.0.1";
140 port = "16379";
141 };
142 };
143 };
144 openldap = {
145 enable = true;
146 base = "/backup2";
147 hosts = {
148 eldiron = {
149 url = "ldaps://${config.myEnv.ldap.host}:636";
150 dn = config.myEnv.ldap.replication_dn;
151 password = config.myEnv.ldap.replication_pw;
152 base = config.myEnv.ldap.base;
153 };
154 };
155 };
156 };
157
158 # This value determines the NixOS release with which your system is
159 # to be compatible, in order to avoid breaking some software such as
160 # database servers. You should change this only after NixOS release
161 # notes say you should.
162 # https://nixos.org/nixos/manual/release-notes.html
163 system.stateVersion = "23.05"; # Did you read the comment?
164}
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}
diff --git a/systems/backup-2/flake.lock b/systems/backup-2/flake.lock
new file mode 100644
index 0000000..46003ba
--- /dev/null
+++ b/systems/backup-2/flake.lock
@@ -0,0 +1,1159 @@
1{
2 "nodes": {
3 "backports": {
4 "inputs": {
5 "flake-utils": "flake-utils_6",
6 "nixpkgs": "nixpkgs_10"
7 },
8 "locked": {
9 "lastModified": 1,
10 "narHash": "sha256-VewHWeZvwLvWVm2bMQk5UQ0G/HyO8X87BssvmbLWbrY=",
11 "path": "../../backports",
12 "type": "path"
13 },
14 "original": {
15 "path": "../../backports",
16 "type": "path"
17 }
18 },
19 "chatons": {
20 "inputs": {
21 "environment": "environment"
22 },
23 "locked": {
24 "lastModified": 1,
25 "narHash": "sha256-UNkS/IZGHCdSX4hCzpTZwNBj9B8RGCMr9Za+G9Xdm4Y=",
26 "path": "../../flakes/private/chatons",
27 "type": "path"
28 },
29 "original": {
30 "path": "../../flakes/private/chatons",
31 "type": "path"
32 }
33 },
34 "colmena": {
35 "inputs": {
36 "flake-compat": "flake-compat",
37 "flake-utils": "flake-utils_3",
38 "nixpkgs": "nixpkgs_3",
39 "stable": "stable"
40 },
41 "locked": {
42 "lastModified": 1687954574,
43 "narHash": "sha256-YasVTaNXq2xqZdejyIhuyqvNypmx+K/Y1ZZ4+raeeII=",
44 "owner": "immae",
45 "repo": "colmena",
46 "rev": "e427171150a35e23204c4c15a2483358d22a0eff",
47 "type": "github"
48 },
49 "original": {
50 "owner": "immae",
51 "ref": "add-lib-get-flake",
52 "repo": "colmena",
53 "type": "github"
54 }
55 },
56 "disko": {
57 "inputs": {
58 "nixpkgs": "nixpkgs_4"
59 },
60 "locked": {
61 "lastModified": 1687968164,
62 "narHash": "sha256-L9jr2zCB6NIaBE3towusjGBigsnE2pMID8wBGkYbTS4=",
63 "owner": "nix-community",
64 "repo": "disko",
65 "rev": "8002e7cb899bc2a02a2ebfb7f999fcd7c18b92a1",
66 "type": "github"
67 },
68 "original": {
69 "owner": "nix-community",
70 "repo": "disko",
71 "type": "github"
72 }
73 },
74 "environment": {
75 "locked": {
76 "lastModified": 1,
77 "narHash": "sha256-rMKbM7fHqWQbI7y59BsPG8KwoDj2jyrvN2niPWB24uE=",
78 "path": "../environment",
79 "type": "path"
80 },
81 "original": {
82 "path": "../environment",
83 "type": "path"
84 }
85 },
86 "environment_2": {
87 "locked": {
88 "lastModified": 1,
89 "narHash": "sha256-rMKbM7fHqWQbI7y59BsPG8KwoDj2jyrvN2niPWB24uE=",
90 "path": "../../flakes/private/environment",
91 "type": "path"
92 },
93 "original": {
94 "path": "../../flakes/private/environment",
95 "type": "path"
96 }
97 },
98 "environment_3": {
99 "locked": {
100 "lastModified": 1,
101 "narHash": "sha256-rMKbM7fHqWQbI7y59BsPG8KwoDj2jyrvN2niPWB24uE=",
102 "path": "../environment",
103 "type": "path"
104 },
105 "original": {
106 "path": "../environment",
107 "type": "path"
108 }
109 },
110 "environment_4": {
111 "locked": {
112 "lastModified": 1,
113 "narHash": "sha256-rMKbM7fHqWQbI7y59BsPG8KwoDj2jyrvN2niPWB24uE=",
114 "path": "../environment",
115 "type": "path"
116 },
117 "original": {
118 "path": "../environment",
119 "type": "path"
120 }
121 },
122 "environment_5": {
123 "locked": {
124 "lastModified": 1,
125 "narHash": "sha256-rMKbM7fHqWQbI7y59BsPG8KwoDj2jyrvN2niPWB24uE=",
126 "path": "../environment",
127 "type": "path"
128 },
129 "original": {
130 "path": "../environment",
131 "type": "path"
132 }
133 },
134 "environment_6": {
135 "locked": {
136 "lastModified": 1,
137 "narHash": "sha256-rMKbM7fHqWQbI7y59BsPG8KwoDj2jyrvN2niPWB24uE=",
138 "path": "../environment",
139 "type": "path"
140 },
141 "original": {
142 "path": "../environment",
143 "type": "path"
144 }
145 },
146 "environment_7": {
147 "locked": {
148 "lastModified": 1,
149 "narHash": "sha256-rMKbM7fHqWQbI7y59BsPG8KwoDj2jyrvN2niPWB24uE=",
150 "path": "../environment",
151 "type": "path"
152 },
153 "original": {
154 "path": "../environment",
155 "type": "path"
156 }
157 },
158 "files-watcher": {
159 "locked": {
160 "lastModified": 1,
161 "narHash": "sha256-ZsdumUVoSPkV/DB6gO6dNDttjzalye0ToVBF9bl5W0k=",
162 "path": "../../files-watcher",
163 "type": "path"
164 },
165 "original": {
166 "path": "../../files-watcher",
167 "type": "path"
168 }
169 },
170 "files-watcher_2": {
171 "locked": {
172 "lastModified": 1,
173 "narHash": "sha256-ZsdumUVoSPkV/DB6gO6dNDttjzalye0ToVBF9bl5W0k=",
174 "path": "../../files-watcher",
175 "type": "path"
176 },
177 "original": {
178 "path": "../../files-watcher",
179 "type": "path"
180 }
181 },
182 "files-watcher_3": {
183 "locked": {
184 "lastModified": 1,
185 "narHash": "sha256-ZsdumUVoSPkV/DB6gO6dNDttjzalye0ToVBF9bl5W0k=",
186 "path": "../../files-watcher",
187 "type": "path"
188 },
189 "original": {
190 "path": "../../files-watcher",
191 "type": "path"
192 }
193 },
194 "flake-compat": {
195 "flake": false,
196 "locked": {
197 "lastModified": 1650374568,
198 "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
199 "owner": "edolstra",
200 "repo": "flake-compat",
201 "rev": "b4a34015c698c7793d592d66adbab377907a2be8",
202 "type": "github"
203 },
204 "original": {
205 "owner": "edolstra",
206 "repo": "flake-compat",
207 "type": "github"
208 }
209 },
210 "flake-parts": {
211 "inputs": {
212 "nixpkgs-lib": "nixpkgs-lib_2"
213 },
214 "locked": {
215 "lastModified": 1687762428,
216 "narHash": "sha256-DIf7mi45PKo+s8dOYF+UlXHzE0Wl/+k3tXUyAoAnoGE=",
217 "owner": "hercules-ci",
218 "repo": "flake-parts",
219 "rev": "37dd7bb15791c86d55c5121740a1887ab55ee836",
220 "type": "github"
221 },
222 "original": {
223 "owner": "hercules-ci",
224 "repo": "flake-parts",
225 "type": "github"
226 }
227 },
228 "flake-parts_2": {
229 "inputs": {
230 "nixpkgs-lib": "nixpkgs-lib_3"
231 },
232 "locked": {
233 "lastModified": 1675295133,
234 "narHash": "sha256-dU8fuLL98WFXG0VnRgM00bqKX6CEPBLybhiIDIgO45o=",
235 "owner": "hercules-ci",
236 "repo": "flake-parts",
237 "rev": "bf53492df08f3178ce85e0c9df8ed8d03c030c9f",
238 "type": "github"
239 },
240 "original": {
241 "owner": "hercules-ci",
242 "repo": "flake-parts",
243 "type": "github"
244 }
245 },
246 "flake-utils": {
247 "locked": {
248 "lastModified": 1609246779,
249 "narHash": "sha256-eq6ZXE/VWo3EMC65jmIT6H/rrUc9UWOWVujkzav025k=",
250 "owner": "numtide",
251 "repo": "flake-utils",
252 "rev": "08c7ad4a0844adc4a7f9f5bb3beae482e789afa4",
253 "type": "github"
254 },
255 "original": {
256 "owner": "numtide",
257 "repo": "flake-utils",
258 "type": "github"
259 }
260 },
261 "flake-utils_2": {
262 "locked": {
263 "lastModified": 1609246779,
264 "narHash": "sha256-eq6ZXE/VWo3EMC65jmIT6H/rrUc9UWOWVujkzav025k=",
265 "owner": "numtide",
266 "repo": "flake-utils",
267 "rev": "08c7ad4a0844adc4a7f9f5bb3beae482e789afa4",
268 "type": "github"
269 },
270 "original": {
271 "owner": "numtide",
272 "repo": "flake-utils",
273 "type": "github"
274 }
275 },
276 "flake-utils_3": {
277 "locked": {
278 "lastModified": 1659877975,
279 "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
280 "owner": "numtide",
281 "repo": "flake-utils",
282 "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
283 "type": "github"
284 },
285 "original": {
286 "owner": "numtide",
287 "repo": "flake-utils",
288 "type": "github"
289 }
290 },
291 "flake-utils_4": {
292 "locked": {
293 "lastModified": 1609246779,
294 "narHash": "sha256-eq6ZXE/VWo3EMC65jmIT6H/rrUc9UWOWVujkzav025k=",
295 "owner": "numtide",
296 "repo": "flake-utils",
297 "rev": "08c7ad4a0844adc4a7f9f5bb3beae482e789afa4",
298 "type": "github"
299 },
300 "original": {
301 "owner": "numtide",
302 "repo": "flake-utils",
303 "type": "github"
304 }
305 },
306 "flake-utils_5": {
307 "locked": {
308 "lastModified": 1609246779,
309 "narHash": "sha256-eq6ZXE/VWo3EMC65jmIT6H/rrUc9UWOWVujkzav025k=",
310 "owner": "numtide",
311 "repo": "flake-utils",
312 "rev": "08c7ad4a0844adc4a7f9f5bb3beae482e789afa4",
313 "type": "github"
314 },
315 "original": {
316 "owner": "numtide",
317 "repo": "flake-utils",
318 "type": "github"
319 }
320 },
321 "flake-utils_6": {
322 "locked": {
323 "lastModified": 1667395993,
324 "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
325 "owner": "numtide",
326 "repo": "flake-utils",
327 "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
328 "type": "github"
329 },
330 "original": {
331 "owner": "numtide",
332 "repo": "flake-utils",
333 "type": "github"
334 }
335 },
336 "loginctl-linger": {
337 "locked": {
338 "lastModified": 1,
339 "narHash": "sha256-TLlUOhiQzYo6SwH0E3oPCDfhgW249qPZTlVar1VmpKw=",
340 "path": "../../flakes/loginctl-linger",
341 "type": "path"
342 },
343 "original": {
344 "path": "../../flakes/loginctl-linger",
345 "type": "path"
346 }
347 },
348 "mail-relay": {
349 "inputs": {
350 "environment": "environment_3",
351 "secrets": "secrets"
352 },
353 "locked": {
354 "lastModified": 1,
355 "narHash": "sha256-xISja892g6YTu9YjGwaD36BBWi/1+IcuREw6iUDqfVw=",
356 "path": "../../flakes/private/mail-relay",
357 "type": "path"
358 },
359 "original": {
360 "path": "../../flakes/private/mail-relay",
361 "type": "path"
362 }
363 },
364 "milters": {
365 "inputs": {
366 "environment": "environment_4",
367 "files-watcher": "files-watcher",
368 "openarc": "openarc",
369 "opendmarc": "opendmarc",
370 "secrets": "secrets_2"
371 },
372 "locked": {
373 "lastModified": 1,
374 "narHash": "sha256-+FlrtZ2sR58VeLsYFeQ6ccaAiGQRFoc9ofs/X/S0Bkg=",
375 "path": "../../flakes/private/milters",
376 "type": "path"
377 },
378 "original": {
379 "path": "../../flakes/private/milters",
380 "type": "path"
381 }
382 },
383 "monitoring": {
384 "inputs": {
385 "environment": "environment_5",
386 "naemon": "naemon",
387 "nixpkgs-lib": "nixpkgs-lib",
388 "secrets": "secrets_3"
389 },
390 "locked": {
391 "lastModified": 1,
392 "narHash": "sha256-K720bqCEHPK0F7GBaxo/ioJ3LVAyhjl/ZZobWwO4ebU=",
393 "path": "../../flakes/private/monitoring",
394 "type": "path"
395 },
396 "original": {
397 "path": "../../flakes/private/monitoring",
398 "type": "path"
399 }
400 },
401 "my-lib": {
402 "inputs": {
403 "colmena": "colmena",
404 "disko": "disko",
405 "flake-parts": "flake-parts",
406 "nixos-anywhere": "nixos-anywhere",
407 "nixpkgs": "nixpkgs_6"
408 },
409 "locked": {
410 "lastModified": 1,
411 "narHash": "sha256-wwpT+I5/zrln85BDzlZoEDC19GwYrcZSXbrJjyvC4jk=",
412 "path": "../../flakes/lib",
413 "type": "path"
414 },
415 "original": {
416 "path": "../../flakes/lib",
417 "type": "path"
418 }
419 },
420 "mypackages": {
421 "inputs": {
422 "flake-parts": "flake-parts_2",
423 "nixpkgs": "nixpkgs_11",
424 "webapps-ttrss": "webapps-ttrss"
425 },
426 "locked": {
427 "lastModified": 1,
428 "narHash": "sha256-C0plEL+g6kv5fo/VmTjMJK45RfFcGufqPKJVnviMyGY=",
429 "path": "../../mypackages",
430 "type": "path"
431 },
432 "original": {
433 "path": "../../mypackages",
434 "type": "path"
435 }
436 },
437 "myuids": {
438 "locked": {
439 "lastModified": 1,
440 "narHash": "sha256-HkW9YCLQCNBX3Em7J7MjraVEZO3I3PizkVV2QrUdULQ=",
441 "path": "../myuids",
442 "type": "path"
443 },
444 "original": {
445 "path": "../myuids",
446 "type": "path"
447 }
448 },
449 "myuids_2": {
450 "locked": {
451 "lastModified": 1,
452 "narHash": "sha256-HkW9YCLQCNBX3Em7J7MjraVEZO3I3PizkVV2QrUdULQ=",
453 "path": "../myuids",
454 "type": "path"
455 },
456 "original": {
457 "path": "../myuids",
458 "type": "path"
459 }
460 },
461 "myuids_3": {
462 "locked": {
463 "lastModified": 1,
464 "narHash": "sha256-HkW9YCLQCNBX3Em7J7MjraVEZO3I3PizkVV2QrUdULQ=",
465 "path": "../../flakes/myuids",
466 "type": "path"
467 },
468 "original": {
469 "path": "../../flakes/myuids",
470 "type": "path"
471 }
472 },
473 "myuids_4": {
474 "locked": {
475 "lastModified": 1,
476 "narHash": "sha256-HkW9YCLQCNBX3Em7J7MjraVEZO3I3PizkVV2QrUdULQ=",
477 "path": "../myuids",
478 "type": "path"
479 },
480 "original": {
481 "path": "../myuids",
482 "type": "path"
483 }
484 },
485 "myuids_5": {
486 "locked": {
487 "lastModified": 1,
488 "narHash": "sha256-HkW9YCLQCNBX3Em7J7MjraVEZO3I3PizkVV2QrUdULQ=",
489 "path": "../myuids",
490 "type": "path"
491 },
492 "original": {
493 "path": "../myuids",
494 "type": "path"
495 }
496 },
497 "myuids_6": {
498 "locked": {
499 "lastModified": 1,
500 "narHash": "sha256-HkW9YCLQCNBX3Em7J7MjraVEZO3I3PizkVV2QrUdULQ=",
501 "path": "../../myuids",
502 "type": "path"
503 },
504 "original": {
505 "path": "../../myuids",
506 "type": "path"
507 }
508 },
509 "naemon": {
510 "locked": {
511 "lastModified": 1,
512 "narHash": "sha256-6le57WLKj1HXdhe4cgYO6N0Z9nJZC+plQY8HhOwzEIk=",
513 "path": "../../naemon",
514 "type": "path"
515 },
516 "original": {
517 "path": "../../naemon",
518 "type": "path"
519 }
520 },
521 "nixos-2305": {
522 "locked": {
523 "lastModified": 1687938137,
524 "narHash": "sha256-Z00c0Pk3aE1aw9x44lVcqHmvx+oX7dxCXCvKcUuE150=",
525 "owner": "NixOS",
526 "repo": "nixpkgs",
527 "rev": "ba2ded3227a2992f2040fad4ba6f218a701884a5",
528 "type": "github"
529 },
530 "original": {
531 "owner": "NixOS",
532 "ref": "release-23.05",
533 "repo": "nixpkgs",
534 "type": "github"
535 }
536 },
537 "nixos-anywhere": {
538 "inputs": {
539 "disko": [
540 "my-lib",
541 "disko"
542 ],
543 "flake-parts": [
544 "my-lib",
545 "flake-parts"
546 ],
547 "nixos-2305": "nixos-2305",
548 "nixos-images": "nixos-images",
549 "nixpkgs": "nixpkgs_5",
550 "treefmt-nix": "treefmt-nix"
551 },
552 "locked": {
553 "lastModified": 1689945193,
554 "narHash": "sha256-+GPRt7ouE84A7GPNKnFYGU0cQL7skKxz0BAY0sUjUmw=",
555 "owner": "numtide",
556 "repo": "nixos-anywhere",
557 "rev": "27161266077a177ac116e2cb72cc70af5f145189",
558 "type": "github"
559 },
560 "original": {
561 "owner": "numtide",
562 "repo": "nixos-anywhere",
563 "type": "github"
564 }
565 },
566 "nixos-images": {
567 "inputs": {
568 "nixos-2305": [
569 "my-lib",
570 "nixos-anywhere",
571 "nixos-2305"
572 ],
573 "nixos-unstable": [
574 "my-lib",
575 "nixos-anywhere",
576 "nixpkgs"
577 ]
578 },
579 "locked": {
580 "lastModified": 1686819168,
581 "narHash": "sha256-IbRVStbKoMC2fUX6TxNO82KgpVfI8LL4Cq0bTgdYhnY=",
582 "owner": "nix-community",
583 "repo": "nixos-images",
584 "rev": "ccc1a2c08ce2fc38bcece85d2a6e7bf17bac9e37",
585 "type": "github"
586 },
587 "original": {
588 "owner": "nix-community",
589 "repo": "nixos-images",
590 "type": "github"
591 }
592 },
593 "nixpkgs": {
594 "locked": {
595 "lastModified": 1597943282,
596 "narHash": "sha256-G/VQBlqO7YeFOSvn29RqdvABZxmQBtiRYVA6kjqWZ6o=",
597 "owner": "NixOS",
598 "repo": "nixpkgs",
599 "rev": "c59ea8b8a0e7f927e7291c14ea6cd1bd3a16ff38",
600 "type": "github"
601 },
602 "original": {
603 "owner": "NixOS",
604 "repo": "nixpkgs",
605 "type": "github"
606 }
607 },
608 "nixpkgs-lib": {
609 "locked": {
610 "dir": "lib",
611 "lastModified": 1691269286,
612 "narHash": "sha256-7cPTz1bPhwq8smt9rHDcFtJsd1tFDcBukzj5jOXqjfk=",
613 "owner": "NixOS",
614 "repo": "nixpkgs",
615 "rev": "85d4248a4f5aa6bc55dd2cea8131bb68b2d43804",
616 "type": "github"
617 },
618 "original": {
619 "dir": "lib",
620 "owner": "NixOS",
621 "repo": "nixpkgs",
622 "type": "github"
623 }
624 },
625 "nixpkgs-lib_2": {
626 "locked": {
627 "dir": "lib",
628 "lastModified": 1685564631,
629 "narHash": "sha256-8ywr3AkblY4++3lIVxmrWZFzac7+f32ZEhH/A8pNscI=",
630 "owner": "NixOS",
631 "repo": "nixpkgs",
632 "rev": "4f53efe34b3a8877ac923b9350c874e3dcd5dc0a",
633 "type": "github"
634 },
635 "original": {
636 "dir": "lib",
637 "owner": "NixOS",
638 "ref": "nixos-unstable",
639 "repo": "nixpkgs",
640 "type": "github"
641 }
642 },
643 "nixpkgs-lib_3": {
644 "locked": {
645 "dir": "lib",
646 "lastModified": 1675183161,
647 "narHash": "sha256-Zq8sNgAxDckpn7tJo7V1afRSk2eoVbu3OjI1QklGLNg=",
648 "owner": "NixOS",
649 "repo": "nixpkgs",
650 "rev": "e1e1b192c1a5aab2960bf0a0bd53a2e8124fa18e",
651 "type": "github"
652 },
653 "original": {
654 "dir": "lib",
655 "owner": "NixOS",
656 "ref": "nixos-unstable",
657 "repo": "nixpkgs",
658 "type": "github"
659 }
660 },
661 "nixpkgs_10": {
662 "locked": {
663 "lastModified": 1687502512,
664 "narHash": "sha256-dBL/01TayOSZYxtY4cMXuNCBk8UMLoqRZA+94xiFpJA=",
665 "owner": "NixOS",
666 "repo": "nixpkgs",
667 "rev": "3ae20aa58a6c0d1ca95c9b11f59a2d12eebc511f",
668 "type": "github"
669 },
670 "original": {
671 "owner": "NixOS",
672 "ref": "nixos-unstable",
673 "repo": "nixpkgs",
674 "type": "github"
675 }
676 },
677 "nixpkgs_11": {
678 "locked": {
679 "lastModified": 1646497237,
680 "narHash": "sha256-Ccpot1h/rV8MgcngDp5OrdmLTMaUTbStZTR5/sI7zW0=",
681 "owner": "nixos",
682 "repo": "nixpkgs",
683 "rev": "062a0c5437b68f950b081bbfc8a699d57a4ee026",
684 "type": "github"
685 },
686 "original": {
687 "owner": "nixos",
688 "repo": "nixpkgs",
689 "rev": "062a0c5437b68f950b081bbfc8a699d57a4ee026",
690 "type": "github"
691 }
692 },
693 "nixpkgs_2": {
694 "locked": {
695 "lastModified": 1597943282,
696 "narHash": "sha256-G/VQBlqO7YeFOSvn29RqdvABZxmQBtiRYVA6kjqWZ6o=",
697 "owner": "NixOS",
698 "repo": "nixpkgs",
699 "rev": "c59ea8b8a0e7f927e7291c14ea6cd1bd3a16ff38",
700 "type": "github"
701 },
702 "original": {
703 "owner": "NixOS",
704 "repo": "nixpkgs",
705 "type": "github"
706 }
707 },
708 "nixpkgs_3": {
709 "locked": {
710 "lastModified": 1683408522,
711 "narHash": "sha256-9kcPh6Uxo17a3kK3XCHhcWiV1Yu1kYj22RHiymUhMkU=",
712 "owner": "NixOS",
713 "repo": "nixpkgs",
714 "rev": "897876e4c484f1e8f92009fd11b7d988a121a4e7",
715 "type": "github"
716 },
717 "original": {
718 "owner": "NixOS",
719 "ref": "nixos-unstable",
720 "repo": "nixpkgs",
721 "type": "github"
722 }
723 },
724 "nixpkgs_4": {
725 "locked": {
726 "lastModified": 1687701825,
727 "narHash": "sha256-aMC9hqsf+4tJL7aJWSdEUurW2TsjxtDcJBwM9Y4FIYM=",
728 "owner": "NixOS",
729 "repo": "nixpkgs",
730 "rev": "07059ee2fa34f1598758839b9af87eae7f7ae6ea",
731 "type": "github"
732 },
733 "original": {
734 "owner": "NixOS",
735 "ref": "nixpkgs-unstable",
736 "repo": "nixpkgs",
737 "type": "github"
738 }
739 },
740 "nixpkgs_5": {
741 "locked": {
742 "lastModified": 1687893427,
743 "narHash": "sha256-jJHj0Lxpvov1IPYQK441oLAKxxemHm16U9jf60bXAFU=",
744 "owner": "nixos",
745 "repo": "nixpkgs",
746 "rev": "4b14ab2a916508442e685089672681dff46805be",
747 "type": "github"
748 },
749 "original": {
750 "owner": "nixos",
751 "ref": "nixos-unstable-small",
752 "repo": "nixpkgs",
753 "type": "github"
754 }
755 },
756 "nixpkgs_6": {
757 "locked": {
758 "lastModified": 1648725829,
759 "narHash": "sha256-tXEzI38lLrzW2qCAIs0UAatE2xcsTsoKWaaXqAcF1NI=",
760 "owner": "NixOS",
761 "repo": "nixpkgs",
762 "rev": "72152ff5ad470ed1a5b97c0ba2737938c136c994",
763 "type": "github"
764 },
765 "original": {
766 "owner": "NixOS",
767 "repo": "nixpkgs",
768 "type": "github"
769 }
770 },
771 "nixpkgs_7": {
772 "locked": {
773 "lastModified": 1693158576,
774 "narHash": "sha256-aRTTXkYvhXosGx535iAFUaoFboUrZSYb1Ooih/auGp0=",
775 "owner": "nixos",
776 "repo": "nixpkgs",
777 "rev": "a999c1cc0c9eb2095729d5aa03e0d8f7ed256780",
778 "type": "github"
779 },
780 "original": {
781 "owner": "nixos",
782 "ref": "nixos-unstable",
783 "repo": "nixpkgs",
784 "type": "github"
785 }
786 },
787 "nixpkgs_8": {
788 "locked": {
789 "lastModified": 1597943282,
790 "narHash": "sha256-G/VQBlqO7YeFOSvn29RqdvABZxmQBtiRYVA6kjqWZ6o=",
791 "owner": "NixOS",
792 "repo": "nixpkgs",
793 "rev": "c59ea8b8a0e7f927e7291c14ea6cd1bd3a16ff38",
794 "type": "github"
795 },
796 "original": {
797 "owner": "NixOS",
798 "repo": "nixpkgs",
799 "type": "github"
800 }
801 },
802 "nixpkgs_9": {
803 "locked": {
804 "lastModified": 1597943282,
805 "narHash": "sha256-G/VQBlqO7YeFOSvn29RqdvABZxmQBtiRYVA6kjqWZ6o=",
806 "owner": "NixOS",
807 "repo": "nixpkgs",
808 "rev": "c59ea8b8a0e7f927e7291c14ea6cd1bd3a16ff38",
809 "type": "github"
810 },
811 "original": {
812 "owner": "NixOS",
813 "repo": "nixpkgs",
814 "type": "github"
815 }
816 },
817 "openarc": {
818 "inputs": {
819 "flake-utils": "flake-utils",
820 "myuids": "myuids",
821 "nixpkgs": "nixpkgs",
822 "openarc": "openarc_2"
823 },
824 "locked": {
825 "lastModified": 1,
826 "narHash": "sha256-+X3x0t7DSYBvgFAUGNnMV4F/vQOUWE+9Q4Az6V8/iTw=",
827 "path": "../../openarc",
828 "type": "path"
829 },
830 "original": {
831 "path": "../../openarc",
832 "type": "path"
833 }
834 },
835 "openarc_2": {
836 "flake": false,
837 "locked": {
838 "lastModified": 1537545083,
839 "narHash": "sha256-xUSRARC7875vFjtZ66t8KBlKmkEdIZblWHc4zqGZAQQ=",
840 "owner": "trusteddomainproject",
841 "repo": "OpenARC",
842 "rev": "355ee2a1ca85acccce494478991983b54f794f4e",
843 "type": "github"
844 },
845 "original": {
846 "owner": "trusteddomainproject",
847 "repo": "OpenARC",
848 "type": "github"
849 }
850 },
851 "openarc_3": {
852 "inputs": {
853 "files-watcher": "files-watcher_2",
854 "openarc": "openarc_4",
855 "secrets": "secrets_4"
856 },
857 "locked": {
858 "lastModified": 1,
859 "narHash": "sha256-08NmS2KKpthWHC7ob5cu1RBKA7JaPEMqcL5HHwH3vLA=",
860 "path": "../../flakes/private/openarc",
861 "type": "path"
862 },
863 "original": {
864 "path": "../../flakes/private/openarc",
865 "type": "path"
866 }
867 },
868 "openarc_4": {
869 "inputs": {
870 "flake-utils": "flake-utils_4",
871 "myuids": "myuids_4",
872 "nixpkgs": "nixpkgs_8",
873 "openarc": "openarc_5"
874 },
875 "locked": {
876 "lastModified": 1,
877 "narHash": "sha256-+X3x0t7DSYBvgFAUGNnMV4F/vQOUWE+9Q4Az6V8/iTw=",
878 "path": "../../openarc",
879 "type": "path"
880 },
881 "original": {
882 "path": "../../openarc",
883 "type": "path"
884 }
885 },
886 "openarc_5": {
887 "flake": false,
888 "locked": {
889 "lastModified": 1537545083,
890 "narHash": "sha256-xUSRARC7875vFjtZ66t8KBlKmkEdIZblWHc4zqGZAQQ=",
891 "owner": "trusteddomainproject",
892 "repo": "OpenARC",
893 "rev": "355ee2a1ca85acccce494478991983b54f794f4e",
894 "type": "github"
895 },
896 "original": {
897 "owner": "trusteddomainproject",
898 "repo": "OpenARC",
899 "type": "github"
900 }
901 },
902 "opendmarc": {
903 "inputs": {
904 "flake-utils": "flake-utils_2",
905 "myuids": "myuids_2",
906 "nixpkgs": "nixpkgs_2"
907 },
908 "locked": {
909 "lastModified": 1,
910 "narHash": "sha256-dDS9a1XujZU6KVCgz2RKbx2T3yT1k7z0EknUh1OyMdQ=",
911 "path": "../../opendmarc",
912 "type": "path"
913 },
914 "original": {
915 "path": "../../opendmarc",
916 "type": "path"
917 }
918 },
919 "opendmarc_2": {
920 "inputs": {
921 "environment": "environment_6",
922 "files-watcher": "files-watcher_3",
923 "opendmarc": "opendmarc_3",
924 "secrets": "secrets_5"
925 },
926 "locked": {
927 "lastModified": 1,
928 "narHash": "sha256-2lx6oVf/3OuqWdP8dHlA6f6+npwx6N/oFv/WkqIbV1Q=",
929 "path": "../../flakes/private/opendmarc",
930 "type": "path"
931 },
932 "original": {
933 "path": "../../flakes/private/opendmarc",
934 "type": "path"
935 }
936 },
937 "opendmarc_3": {
938 "inputs": {
939 "flake-utils": "flake-utils_5",
940 "myuids": "myuids_5",
941 "nixpkgs": "nixpkgs_9"
942 },
943 "locked": {
944 "lastModified": 1,
945 "narHash": "sha256-dDS9a1XujZU6KVCgz2RKbx2T3yT1k7z0EknUh1OyMdQ=",
946 "path": "../../opendmarc",
947 "type": "path"
948 },
949 "original": {
950 "path": "../../opendmarc",
951 "type": "path"
952 }
953 },
954 "openldap": {
955 "locked": {
956 "lastModified": 1,
957 "narHash": "sha256-Z4Gg8wU/wVVQDFwWAC9k1LW+yg0xI1iNhKB51K9Gq4c=",
958 "path": "../../flakes/private/openldap",
959 "type": "path"
960 },
961 "original": {
962 "path": "../../flakes/private/openldap",
963 "type": "path"
964 }
965 },
966 "root": {
967 "inputs": {
968 "chatons": "chatons",
969 "environment": "environment_2",
970 "loginctl-linger": "loginctl-linger",
971 "mail-relay": "mail-relay",
972 "milters": "milters",
973 "monitoring": "monitoring",
974 "my-lib": "my-lib",
975 "myuids": "myuids_3",
976 "nixpkgs": "nixpkgs_7",
977 "openarc": "openarc_3",
978 "opendmarc": "opendmarc_2",
979 "openldap": "openldap",
980 "rsync_backup": "rsync_backup",
981 "secrets": "secrets_6",
982 "system": "system"
983 }
984 },
985 "rsync_backup": {
986 "locked": {
987 "lastModified": 1,
988 "narHash": "sha256-TxLsFx4DTTScMHkvR0pJgzYea6ILiu1Dl6LA67LtYGo=",
989 "path": "../../flakes/rsync_backup",
990 "type": "path"
991 },
992 "original": {
993 "path": "../../flakes/rsync_backup",
994 "type": "path"
995 }
996 },
997 "secrets": {
998 "locked": {
999 "lastModified": 1,
1000 "narHash": "sha256-5AakznhrJFmwCD7lr4JEh55MtdAJL6WA/YuBks6ISSE=",
1001 "path": "../../secrets",
1002 "type": "path"
1003 },
1004 "original": {
1005 "path": "../../secrets",
1006 "type": "path"
1007 }
1008 },
1009 "secrets-public": {
1010 "locked": {
1011 "lastModified": 1,
1012 "narHash": "sha256-5AakznhrJFmwCD7lr4JEh55MtdAJL6WA/YuBks6ISSE=",
1013 "path": "../../secrets",
1014 "type": "path"
1015 },
1016 "original": {
1017 "path": "../../secrets",
1018 "type": "path"
1019 }
1020 },
1021 "secrets_2": {
1022 "locked": {
1023 "lastModified": 1,
1024 "narHash": "sha256-5AakznhrJFmwCD7lr4JEh55MtdAJL6WA/YuBks6ISSE=",
1025 "path": "../../secrets",
1026 "type": "path"
1027 },
1028 "original": {
1029 "path": "../../secrets",
1030 "type": "path"
1031 }
1032 },
1033 "secrets_3": {
1034 "locked": {
1035 "lastModified": 1,
1036 "narHash": "sha256-5AakznhrJFmwCD7lr4JEh55MtdAJL6WA/YuBks6ISSE=",
1037 "path": "../../secrets",
1038 "type": "path"
1039 },
1040 "original": {
1041 "path": "../../secrets",
1042 "type": "path"
1043 }
1044 },
1045 "secrets_4": {
1046 "locked": {
1047 "lastModified": 1,
1048 "narHash": "sha256-5AakznhrJFmwCD7lr4JEh55MtdAJL6WA/YuBks6ISSE=",
1049 "path": "../../secrets",
1050 "type": "path"
1051 },
1052 "original": {
1053 "path": "../../secrets",
1054 "type": "path"
1055 }
1056 },
1057 "secrets_5": {
1058 "locked": {
1059 "lastModified": 1,
1060 "narHash": "sha256-5AakznhrJFmwCD7lr4JEh55MtdAJL6WA/YuBks6ISSE=",
1061 "path": "../../secrets",
1062 "type": "path"
1063 },
1064 "original": {
1065 "path": "../../secrets",
1066 "type": "path"
1067 }
1068 },
1069 "secrets_6": {
1070 "locked": {
1071 "lastModified": 1,
1072 "narHash": "sha256-5AakznhrJFmwCD7lr4JEh55MtdAJL6WA/YuBks6ISSE=",
1073 "path": "../../flakes/secrets",
1074 "type": "path"
1075 },
1076 "original": {
1077 "path": "../../flakes/secrets",
1078 "type": "path"
1079 }
1080 },
1081 "stable": {
1082 "locked": {
1083 "lastModified": 1669735802,
1084 "narHash": "sha256-qtG/o/i5ZWZLmXw108N2aPiVsxOcidpHJYNkT45ry9Q=",
1085 "owner": "NixOS",
1086 "repo": "nixpkgs",
1087 "rev": "731cc710aeebecbf45a258e977e8b68350549522",
1088 "type": "github"
1089 },
1090 "original": {
1091 "owner": "NixOS",
1092 "ref": "nixos-22.11",
1093 "repo": "nixpkgs",
1094 "type": "github"
1095 }
1096 },
1097 "system": {
1098 "inputs": {
1099 "backports": "backports",
1100 "environment": "environment_7",
1101 "mypackages": "mypackages",
1102 "myuids": "myuids_6",
1103 "secrets-public": "secrets-public"
1104 },
1105 "locked": {
1106 "lastModified": 1,
1107 "narHash": "sha256-vOs7fcQVsOSl/gsyzFXfsWE7u0/O9mIKpHnwDwHxJTQ=",
1108 "path": "../../flakes/private/system",
1109 "type": "path"
1110 },
1111 "original": {
1112 "path": "../../flakes/private/system",
1113 "type": "path"
1114 }
1115 },
1116 "treefmt-nix": {
1117 "inputs": {
1118 "nixpkgs": [
1119 "my-lib",
1120 "nixos-anywhere",
1121 "nixpkgs"
1122 ]
1123 },
1124 "locked": {
1125 "lastModified": 1687940979,
1126 "narHash": "sha256-D4ZFkgIG2s9Fyi78T3fVG9mqMD+/UnFDB62jS4gjZKY=",
1127 "owner": "numtide",
1128 "repo": "treefmt-nix",
1129 "rev": "0a4f06c27610a99080b69433873885df82003aae",
1130 "type": "github"
1131 },
1132 "original": {
1133 "owner": "numtide",
1134 "repo": "treefmt-nix",
1135 "type": "github"
1136 }
1137 },
1138 "webapps-ttrss": {
1139 "flake": false,
1140 "locked": {
1141 "lastModified": 1546759381,
1142 "narHash": "sha256-urjf4EoLWS7G0s0hRtaErrs2B8DUatNK/eoneuB0anY=",
1143 "ref": "master",
1144 "rev": "986ca251f995f7754a0470d3e0c44538a545081f",
1145 "revCount": 9256,
1146 "type": "git",
1147 "url": "https://git.tt-rss.org/fox/tt-rss.git"
1148 },
1149 "original": {
1150 "ref": "master",
1151 "rev": "986ca251f995f7754a0470d3e0c44538a545081f",
1152 "type": "git",
1153 "url": "https://git.tt-rss.org/fox/tt-rss.git"
1154 }
1155 }
1156 },
1157 "root": "root",
1158 "version": 7
1159}
diff --git a/systems/backup-2/flake.nix b/systems/backup-2/flake.nix
new file mode 100644
index 0000000..e6807d6
--- /dev/null
+++ b/systems/backup-2/flake.nix
@@ -0,0 +1,51 @@
1{
2 inputs = {
3 nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
4
5 my-lib.url = "path:../../flakes/lib";
6
7 openldap.url = "path:../../flakes/private/openldap";
8 monitoring.url = "path:../../flakes/private/monitoring";
9 mail-relay.url = "path:../../flakes/private/mail-relay";
10 milters.url = "path:../../flakes/private/milters";
11 openarc.url = "path:../../flakes/private/openarc";
12 opendmarc.url = "path:../../flakes/private/opendmarc";
13 chatons.url = "path:../../flakes/private/chatons";
14 environment.url = "path:../../flakes/private/environment";
15 system.url = "path:../../flakes/private/system";
16
17 myuids.url = "path:../../flakes/myuids";
18 secrets.url = "path:../../flakes/secrets";
19 rsync_backup.url = "path:../../flakes/rsync_backup";
20 loginctl-linger.url = "path:../../flakes/loginctl-linger";
21 };
22 outputs = inputs@{ self, my-lib, nixpkgs, ...}:
23 my-lib.lib.mkColmenaFlake {
24 name = "backup-2";
25 inherit self nixpkgs;
26 system = "x86_64-linux";
27 targetHost = "95.217.19.143";
28 targetUser = "root";
29 nixosModules = {
30 base = ./base.nix;
31 system = inputs.system.nixosModule;
32 mail-relay = inputs.mail-relay.nixosModule;
33 milters = inputs.milters.nixosModule;
34 openarc = inputs.openarc.nixosModule;
35 opendmarc = inputs.opendmarc.nixosModule;
36 chatons = inputs.chatons.nixosModule;
37 monitoring = inputs.monitoring.nixosModule;
38 environment = inputs.environment.nixosModule;
39
40 myuids = inputs.myuids.nixosModule;
41 secrets = inputs.secrets.nixosModule;
42 rsync_backup = inputs.rsync_backup.nixosModule;
43 loginctl-linger = inputs.loginctl-linger.nixosModule;
44 };
45 moduleArgs = {
46 nixpkgs = inputs.nixpkgs;
47 openldap = inputs.openldap;
48 monitoring = inputs.monitoring;
49 };
50 };
51}
diff --git a/systems/backup-2/mail/relay.nix b/systems/backup-2/mail/relay.nix
new file mode 100644
index 0000000..1b7e25e
--- /dev/null
+++ b/systems/backup-2/mail/relay.nix
@@ -0,0 +1,196 @@
1{ lib, pkgs, config, name, nodes, ... }:
2let
3 getDomains = p: lib.mapAttrsToList (n: v: v.fqdn) (lib.filterAttrs (n: v: v.receive) p.emailPolicies);
4 bydomain = builtins.mapAttrs (n: getDomains) nodes.eldiron.config.myServices.dns.zones;
5 receiving_domains = lib.flatten (builtins.attrValues bydomain);
6in
7{
8 options.myServices.mailBackup.enable = lib.mkEnableOption "enable MX backup services";
9 config = lib.mkIf config.myServices.mailBackup.enable {
10 myServices.mail.milters.enable = true;
11 security.acme.certs."mail" = {
12 postRun = ''
13 systemctl restart postfix.service
14 '';
15 domain = config.hostEnv.fqdn;
16 extraDomainNames = let
17 zonesWithMx = builtins.attrNames (lib.filterAttrs (n: v: v.hasEmail) nodes.eldiron.config.myServices.dns.zones);
18 mxs = map (n: "${config.myEnv.servers."${name}".mx.subdomain}.${n}") zonesWithMx;
19 in mxs;
20 };
21 secrets.keys = {
22 "postfix/mysql_alias_maps" = {
23 user = config.services.postfix.user;
24 group = config.services.postfix.group;
25 permissions = "0440";
26 text = ''
27 # We need to specify that option to trigger ssl connection
28 tls_ciphers = TLSv1.2
29 user = ${config.myEnv.mail.postfix.mysql.user}
30 password = ${config.myEnv.mail.postfix.mysql.password}
31 hosts = ${config.myEnv.mail.postfix.mysql.remoteHost}
32 dbname = ${config.myEnv.mail.postfix.mysql.database}
33 query = SELECT DISTINCT 1
34 FROM forwardings
35 WHERE
36 ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s'))
37 AND active = 1
38 AND '%s' NOT IN
39 (
40 SELECT source
41 FROM forwardings_blacklisted
42 WHERE source = '%s'
43 ) UNION
44 SELECT 'devnull@immae.eu'
45 FROM forwardings_blacklisted
46 WHERE source = '%s'
47 '';
48 };
49 "postfix/ldap_mailboxes" = {
50 user = config.services.postfix.user;
51 group = config.services.postfix.group;
52 permissions = "0440";
53 text = ''
54 server_host = ldaps://${config.myEnv.mail.dovecot.ldap.host}:636
55 search_base = ${config.myEnv.mail.dovecot.ldap.base}
56 query_filter = ${config.myEnv.mail.dovecot.ldap.postfix_mailbox_filter}
57 bind_dn = ${config.myEnv.mail.dovecot.ldap.dn}
58 bind_pw = ${config.myEnv.mail.dovecot.ldap.password}
59 result_attribute = immaePostfixAddress
60 result_format = dummy
61 version = 3
62 '';
63 };
64 "postfix/sympa_mailbox_maps" = {
65 user = config.services.postfix.user;
66 group = config.services.postfix.group;
67 permissions = "0440";
68 text = ''
69 hosts = ${config.myEnv.mail.sympa.postgresql.host}
70 user = ${config.myEnv.mail.sympa.postgresql.user}
71 password = ${config.myEnv.mail.sympa.postgresql.password}
72 dbname = ${config.myEnv.mail.sympa.postgresql.database}
73 query = SELECT DISTINCT 1 FROM list_table WHERE '%s' IN (
74 CONCAT(name_list, '@', robot_list),
75 CONCAT(name_list, '-request@', robot_list),
76 CONCAT(name_list, '-editor@', robot_list),
77 CONCAT(name_list, '-unsubscribe@', robot_list),
78 CONCAT(name_list, '-owner@', robot_list),
79 CONCAT('sympa-request@', robot_list),
80 CONCAT('sympa-owner@', robot_list),
81 CONCAT('sympa@', robot_list),
82 CONCAT('listmaster@', robot_list),
83 CONCAT('bounce@', robot_list),
84 CONCAT('abuse-feedback-report@', robot_list)
85 )
86 '';
87 };
88 "postfix/ldap_ejabberd_users_immae_fr" = {
89 user = config.services.postfix.user;
90 group = config.services.postfix.group;
91 permissions = "0440";
92 text = ''
93 server_host = ldaps://${config.myEnv.jabber.ldap.host}:636
94 search_base = ${config.myEnv.jabber.ldap.base}
95 query_filter = ${config.myEnv.jabber.postfix_user_filter}
96 domain = immae.fr
97 bind_dn = ${config.myEnv.jabber.ldap.dn}
98 bind_pw = ${config.myEnv.jabber.ldap.password}
99 result_attribute = immaeXmppUid
100 result_format = ejabberd@localhost
101 version = 3
102 '';
103 };
104 };
105
106 networking.firewall.allowedTCPPorts = [ 25 ];
107
108 users.users."${config.services.postfix.user}".extraGroups = [ "keys" ];
109 services.filesWatcher.postfix = {
110 restart = true;
111 paths = [
112 config.secrets.fullPaths."postfix/mysql_alias_maps"
113 config.secrets.fullPaths."postfix/sympa_mailbox_maps"
114 config.secrets.fullPaths."postfix/ldap_ejabberd_users_immae_fr"
115 config.secrets.fullPaths."postfix/ldap_mailboxes"
116 ];
117 };
118 services.postfix = {
119 mapFiles = let
120 virtual_map = {
121 virtual = let
122 cfg = config.myEnv.monitoring.email_check.eldiron;
123 address = "${cfg.mail_address}@${cfg.mail_domain}";
124 aliases = config.myEnv.mail.postfix.common_aliases;
125 in pkgs.writeText "postfix-virtual" (
126 builtins.concatStringsSep "\n" (
127 [ "${address} 1"
128 ] ++
129 map (a: "${a} 1") config.myEnv.mail.postfix.other_aliases ++
130 lib.lists.flatten (map (domain: map (alias: "${alias}@${domain} 1") aliases) receiving_domains)
131 )
132 );
133 };
134 in
135 virtual_map;
136 config = {
137 ### postfix module overrides
138 readme_directory = "${pkgs.postfix}/share/postfix/doc";
139 smtp_tls_CAfile = lib.mkForce "";
140 smtp_tls_cert_file = lib.mkForce "";
141 smtp_tls_key_file = lib.mkForce "";
142
143 message_size_limit = "1073741824"; # Don't put 0 here, it's not equivalent to "unlimited"
144 mailbox_size_limit = "1073741825"; # Workaround, local delivered mails should all go through scripts
145 alias_database = "\$alias_maps";
146
147 ### Relay domains
148 relay_domains = receiving_domains;
149 relay_recipient_maps = let
150 virtual_alias_maps = [
151 "hash:/etc/postfix/virtual"
152 "mysql:${config.secrets.fullPaths."postfix/mysql_alias_maps"}"
153 "ldap:${config.secrets.fullPaths."postfix/ldap_ejabberd_users_immae_fr"}"
154 ];
155 virtual_mailbox_maps = [
156 "ldap:${config.secrets.fullPaths."postfix/ldap_mailboxes"}"
157 "pgsql:${config.secrets.fullPaths."postfix/sympa_mailbox_maps"}"
158 ];
159 in
160 virtual_alias_maps ++ virtual_mailbox_maps;
161 smtpd_relay_restrictions = [
162 "defer_unauth_destination"
163 ];
164
165 ### Additional smtpd configuration
166 smtpd_tls_received_header = "yes";
167 smtpd_tls_loglevel = "1";
168
169 ### Email sending configuration
170 smtp_tls_security_level = "may";
171 smtp_tls_loglevel = "1";
172
173 ### Force ip bind for smtp
174 smtp_bind_address = builtins.head config.myEnv.servers."${name}".ips.main.ip4;
175 smtp_bind_address6 = builtins.head config.myEnv.servers."${name}".ips.main.ip6;
176
177 smtpd_milters = [
178 "unix:${config.myServices.mail.milters.sockets.opendkim}"
179 "unix:${config.myServices.mail.milters.sockets.openarc}"
180 "unix:${config.myServices.mail.milters.sockets.opendmarc}"
181 ];
182 };
183 enable = true;
184 enableSmtp = true;
185 enableSubmission = false;
186 destination = ["localhost"];
187 # This needs to reverse DNS
188 hostname = config.hostEnv.fqdn;
189 setSendmail = false;
190 sslCert = "/var/lib/acme/mail/fullchain.pem";
191 sslKey = "/var/lib/acme/mail/key.pem";
192 recipientDelimiter = "+";
193 };
194 };
195}
196
diff --git a/systems/backup-2/monitoring.nix b/systems/backup-2/monitoring.nix
new file mode 100644
index 0000000..6d769e3
--- /dev/null
+++ b/systems/backup-2/monitoring.nix
@@ -0,0 +1,117 @@
1{ config, pkgs, lib, name, openldap, monitoring, ... }:
2let
3 hostFQDN = config.hostEnv.fqdn;
4 emailCheck = monitoring.lib.emailCheck config.myEnv.monitoring.email_check;
5in
6{
7 config.myServices.monitoring.activatedPlugins = [ "memory" "command" "bandwidth" "file_date" "mysql" "openldap" "redis" "emails" "notify-secondary"];
8 config.myServices.monitoring.objects = lib.mkMerge [
9 (monitoring.lib.objectsCommon {
10 inherit hostFQDN;
11 hostName = name;
12 master = false;
13 processWarn = "60"; processAlert = "70";
14 loadWarn = "4.0"; loadAlert = "6.0";
15 load15Warn = "1.0"; load15Alert = "1.0";
16 interface = builtins.head (builtins.attrNames config.networking.interfaces);
17 })
18
19 {
20 service = [
21 (emailCheck "backup-2" hostFQDN // {
22 __passive_servicegroups = "webstatus-email";
23 })
24 {
25 service_description = "Size on /backup2 partition";
26 use = "local-service";
27 check_command = ["check_local_disk" "10%" "5%" "/backup2"];
28 __passive_servicegroups = "webstatus-resources";
29 }
30 {
31 service_description = "Last backup in /backup2/phare is not too old";
32 use = "local-service";
33 check_command = ["check_last_file_date" "/backup2/phare" "14" "backup"];
34 __passive_servicegroups = "webstatus-backup";
35 }
36 {
37 service_description = "Last backup in /backup2/dilion is not too old";
38 use = "local-service";
39 check_command = ["check_last_file_date" "/backup2/dilion" "14" "backup"];
40 __passive_servicegroups = "webstatus-backup";
41 }
42 {
43 service_description = "Last backup in /backup2/ulminfo is not too old";
44 use = "local-service";
45 check_command = ["check_last_file_date" "/backup2/ulminfo" "14" "backup"];
46 __passive_servicegroups = "webstatus-backup";
47 }
48 {
49 service_description = "Last postgresql dump in /backup2/eldiron/postgresql_backup is not too old";
50 use = "local-service";
51 check_command = ["check_last_file_date" "/backup2/eldiron/postgresql_backup" "7" "postgres"];
52 __passive_servicegroups = "webstatus-databases,webstatus-backup";
53 }
54 {
55 service_description = "Redis replication for eldiron is up to date";
56 use = "local-service";
57 check_command = ["check_redis_replication" "/run/redis_eldiron/redis.sock"];
58 __passive_servicegroups = "webstatus-databases";
59 }
60 {
61 service_description = "Last redis dump in /backup2/eldiron/redis_backup is not too old";
62 use = "local-service";
63 check_command = ["check_last_file_date" "/backup2/eldiron/redis_backup" "7" "redis"];
64 __passive_servicegroups = "webstatus-databases,webstatus-backup";
65 }
66 {
67 service_description = "Mysql replication for eldiron is up to date";
68 use = "local-service";
69 check_command = ["check_mysql_replication" "/run/mysqld_eldiron/mysqld.sock" config.secrets.fullPaths."mysql_replication/eldiron/client"];
70 __passive_servicegroups = "webstatus-databases";
71 }
72 {
73 service_description = "Last mysql dump in /backup2/eldiron/mysql_backup is not too old";
74 use = "local-service";
75 check_command = ["check_last_file_date" "/backup2/eldiron/mysql_backup" "7" "mysql"];
76 __passive_servicegroups = "webstatus-databases,webstatus-backup";
77 }
78 {
79 service_description = "Openldap replication for eldiron is up to date";
80 use = "local-service";
81 check_command = let
82 name = "eldiron";
83 hcfg = config.myServices.databasesReplication.openldap.hosts.eldiron;
84 base = config.myServices.databasesReplication.openldap.base;
85 ldapConfig = pkgs.writeText "slapd.conf" ''
86 include ${pkgs.openldap}/etc/schema/core.schema
87 include ${pkgs.openldap}/etc/schema/cosine.schema
88 include ${pkgs.openldap}/etc/schema/inetorgperson.schema
89 include ${pkgs.openldap}/etc/schema/nis.schema
90 include ${openldap.immae-schema}
91 moduleload back_mdb
92 backend mdb
93 database mdb
94
95 suffix "${hcfg.base}"
96 directory ${base}/${name}/openldap
97 '';
98 in [
99 "check_openldap_replication"
100 hcfg.url
101 hcfg.dn
102 config.secrets.fullPaths."openldap_replication/eldiron/replication_password"
103 hcfg.base
104 "${ldapConfig}"
105 ];
106 __passive_servicegroups = "webstatus-databases";
107 }
108 {
109 service_description = "Last openldap dump in /backup2/eldiron/openldap_backup is not too old";
110 use = "local-service";
111 check_command = ["check_last_file_date" "/backup2/eldiron/openldap_backup" "7" "openldap"];
112 __passive_servicegroups = "webstatus-databases,webstatus-backup";
113 }
114 ];
115 }
116 ];
117}