]> git.immae.eu Git - perso/Immae/Config/Nix.git/blame - modules/private/databases/postgresql_replication.nix
Reduce number of postgresql backups
[perso/Immae/Config/Nix.git] / modules / private / databases / postgresql_replication.nix
CommitLineData
ec9b6564
IB
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 };
9f16e659
IB
14 mainPackage = lib.mkOption {
15 type = lib.types.package;
16 default = pkgs.postgresql;
17 description = ''
18 Postgresql package available in shell
19 '';
20 };
ec9b6564
IB
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 {
ec9b6564
IB
53 users.users.postgres = {
54 name = "postgres";
55 uid = config.ids.uids.postgres;
56 group = "postgres";
57 description = "PostgreSQL server user";
58 home = "/var/lib/postgresql";
59 useDefaultShell = true;
60 extraGroups = [ "keys" ];
61 };
62 users.groups.postgres.gid = config.ids.gids.postgres;
9f16e659 63 environment.systemPackages = [ cfg.mainPackage ];
ec9b6564
IB
64
65 secrets.keys = lib.flatten (lib.mapAttrsToList (name: hcfg: [
66 {
67 dest = "postgresql_replication/${name}/recovery.conf";
68 user = "postgres";
69 group = "postgres";
70 permissions = "0400";
71 text = ''
72 standby_mode = on
73 primary_conninfo = '${hcfg.connection}?sslmode=require'
74 primary_slot_name = '${hcfg.slot}'
75 '';
76 }
77 {
78 dest = "postgresql_replication/${name}/connection_string";
79 user = "postgres";
80 group = "postgres";
81 permissions = "0400";
82 text = hcfg.connection;
83 }
84 {
85 dest = "postgresql_replication/${name}/postgresql.conf";
86 user = "postgres";
87 group = "postgres";
88 permissions = "0400";
89 text = let
90 dataDir = "${cfg.base}/${name}/postgresql";
91 in ''
92 listen_addresses = '''
93 unix_socket_directories = '${dataDir}'
94 data_directory = '${dataDir}'
95 wal_level = logical
96 '';
97 }
98 ]) cfg.hosts);
99
100 services.cron = {
101 enable = true;
102 systemCronJobs = lib.flatten (lib.mapAttrsToList (name: hcfg:
103 let
104 dataDir = "${cfg.base}/${name}/postgresql";
105 backupDir = "${cfg.base}/${name}/postgresql_backup";
106 backup_script = pkgs.writeScript "backup_psql_${name}" ''
107 #!${pkgs.stdenv.shell}
108
109 set -euo pipefail
110
111 resume_replication() {
112 ${hcfg.package}/bin/psql -h ${dataDir} -c "SELECT pg_wal_replay_resume();" >/dev/null || echo "impossible to resume replication"
113 }
114
115 trap resume_replication EXIT
116
117 ${hcfg.package}/bin/psql -h ${dataDir} -c "SELECT pg_wal_replay_pause();" >/dev/null || (echo "impossible to pause replication" && false)
118
4c853ba6 119 ${hcfg.package}/bin/pg_dumpall -h ${dataDir} -f ${backupDir}/$(${pkgs.coreutils}/bin/date -Iminutes).sql
ec9b6564 120 '';
9f6a7862 121 u = pkgs.callPackage ./utils.nix {};
0aa7e23c 122 cleanup_script = pkgs.writeScript "cleanup_postgresql_${name}" (u.keepLastNDumps "sql" backupDir 6);
ec9b6564
IB
123 in [
124 "0 22,4,10,16 * * * postgres ${backup_script}"
9f6a7862 125 "0 3 * * * postgres ${cleanup_script}"
ec9b6564
IB
126 ]) cfg.hosts);
127 };
128
129 system.activationScripts = lib.attrsets.mapAttrs' (name: hcfg:
130 lib.attrsets.nameValuePair "psql_replication_${name}" {
131 deps = [ "users" ];
132 text = ''
133 install -m 0700 -o postgres -g postgres -d ${cfg.base}/${name}/postgresql
134 install -m 0700 -o postgres -g postgres -d ${cfg.base}/${name}/postgresql_backup
135 '';
136 }) cfg.hosts;
137
138 systemd.services = lib.attrsets.mapAttrs' (name: hcfg:
139 let
140 dataDir = "${cfg.base}/${name}/postgresql";
141 in
142 lib.attrsets.nameValuePair "postgresql_backup_${name}" {
143 description = "Postgresql replication for ${name}";
144 wantedBy = [ "multi-user.target" ];
145 after = [ "network.target" ];
146
147 environment.PGDATA = dataDir;
148 path = [ hcfg.package ];
149
150 preStart = ''
151 if ! test -e ${dataDir}/PG_VERSION; then
152 mkdir -m 0700 -p ${dataDir}
153 chown -R postgres:postgres ${dataDir}
154 fi
155 '';
156 script = let
157 fp = n: config.secrets.fullPaths."postgresql_replication/${name}/${n}";
158 in ''
159 if ! test -e ${dataDir}/PG_VERSION; then
160 pg_basebackup -d $(cat ${fp "connection_string"}) -D ${dataDir} -S ${hcfg.slot}
161 fi
162 ln -sfn ${fp "recovery.conf"} ${dataDir}/recovery.conf
163 ln -sfn ${fp "postgresql.conf"} ${dataDir}/postgresql.conf
164
165 exec postgres
166 '';
167
168 serviceConfig = {
169 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
170 User = "postgres";
171 Group = "postgres";
172 PermissionsStartOnly = true;
173 RuntimeDirectory = "postgresql";
174 Type = "notify";
175
176 KillSignal = "SIGINT";
177 KillMode = "mixed";
178 # basebackup can take a long time
179 TimeoutStartSec="infinity";
180 TimeoutStopSec = 120;
181 };
182 unitConfig.RequiresMountsFor = dataDir;
183 }) cfg.hosts;
184 };
185}