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