]>
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 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 | } |