aboutsummaryrefslogtreecommitdiff
path: root/modules/private/databases
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2019-11-11 10:08:20 +0100
committerIsmaël Bouya <ismael.bouya@normalesup.org>2019-11-11 10:08:20 +0100
commitec9b656476d4af2784aea29b846bead85dc46e16 (patch)
tree5ccc67f136be73786d05ce28aeb0ec978637f5a3 /modules/private/databases
parent285380fe566700ab3bf4f69b0a1a10fb4d9bba3a (diff)
downloadNix-ec9b656476d4af2784aea29b846bead85dc46e16.tar.gz
Nix-ec9b656476d4af2784aea29b846bead85dc46e16.tar.zst
Nix-ec9b656476d4af2784aea29b846bead85dc46e16.zip
Add postgresql replication for backup-2
Diffstat (limited to 'modules/private/databases')
-rw-r--r--modules/private/databases/default.nix4
-rw-r--r--modules/private/databases/postgresql.nix21
-rw-r--r--modules/private/databases/postgresql_replication.nix180
3 files changed, 203 insertions, 2 deletions
diff --git a/modules/private/databases/default.nix b/modules/private/databases/default.nix
index b2739bf..d1d6a2b 100644
--- a/modules/private/databases/default.nix
+++ b/modules/private/databases/default.nix
@@ -38,8 +38,8 @@ in
38 inherit (myconfig.env.databases.postgresql.pam) dn filter password; 38 inherit (myconfig.env.databases.postgresql.pam) dn filter password;
39 }; 39 };
40 replicationLdapConfig = { 40 replicationLdapConfig = {
41 inherit (myconfig.env.ldap) host base password; 41 inherit (myconfig.env.ldap) host base;
42 dn = myconfig.env.ldap.host_dn; 42 inherit (myconfig.env.ldap.eldiron) dn password;
43 }; 43 };
44 authorizedHosts = { 44 authorizedHosts = {
45 immaeEu = [{ 45 immaeEu = [{
diff --git a/modules/private/databases/postgresql.nix b/modules/private/databases/postgresql.nix
index 6d1901d..1932a82 100644
--- a/modules/private/databases/postgresql.nix
+++ b/modules/private/databases/postgresql.nix
@@ -121,6 +121,27 @@ in {
121 SupplementaryGroups = "keys"; 121 SupplementaryGroups = "keys";
122 RuntimeDirectory = cfg.systemdRuntimeDirectory; 122 RuntimeDirectory = cfg.systemdRuntimeDirectory;
123 }; 123 };
124 systemd.services.postgresql.postStart = lib.mkAfter ''
125 # This line is already defined in 19.09
126 PSQL="${pkgs.sudo}/bin/sudo -u postgres psql --port=5432"
127
128 ${builtins.concatStringsSep "\n" (lib.mapAttrsToList (role: _: ''
129 $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${role}'" \
130 | grep -q 1 \
131 || $PSQL -tAc 'CREATE USER "${role}" WITH REPLICATION'
132 '') cfg.replicationHosts)}
133
134 ${builtins.concatStringsSep "\n" (lib.mapAttrsToList (role: _:
135 let
136 sname = builtins.replaceStrings ["-"] ["_"] role;
137 in
138 ''
139 $PSQL -tAc "SELECT 1 FROM pg_replication_slots WHERE slot_name='${sname}'" \
140 | grep -q 1 \
141 || $PSQL -tAc "SELECT * FROM pg_create_physical_replication_slot('${sname}')"
142 '') cfg.replicationHosts)}
143 '';
144
124 services.postgresql = { 145 services.postgresql = {
125 enable = true; 146 enable = true;
126 package = cfg.package; 147 package = cfg.package;
diff --git a/modules/private/databases/postgresql_replication.nix b/modules/private/databases/postgresql_replication.nix
new file mode 100644
index 0000000..145fcac
--- /dev/null
+++ b/modules/private/databases/postgresql_replication.nix
@@ -0,0 +1,180 @@
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 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.postgresql;
24 description = ''
25 Postgresql package for this host
26 '';
27 };
28 slot = lib.mkOption {
29 type = lib.types.str;
30 description = ''
31 Slot to use for replication
32 '';
33 };
34 connection = lib.mkOption {
35 type = lib.types.str;
36 description = ''
37 Connection string to access the psql master
38 '';
39 };
40 };
41 });
42 };
43 };
44
45 config = lib.mkIf cfg.enable {
46 nixpkgs.overlays = [ (self: super: {
47 postgresql = self.postgresql_11_custom;
48 }) ];
49
50 users.users.postgres = {
51 name = "postgres";
52 uid = config.ids.uids.postgres;
53 group = "postgres";
54 description = "PostgreSQL server user";
55 home = "/var/lib/postgresql";
56 useDefaultShell = true;
57 extraGroups = [ "keys" ];
58 };
59 users.groups.postgres.gid = config.ids.gids.postgres;
60 environment.systemPackages = [ pkgs.postgresql ];
61
62 secrets.keys = lib.flatten (lib.mapAttrsToList (name: hcfg: [
63 {
64 dest = "postgresql_replication/${name}/recovery.conf";
65 user = "postgres";
66 group = "postgres";
67 permissions = "0400";
68 text = ''
69 standby_mode = on
70 primary_conninfo = '${hcfg.connection}?sslmode=require'
71 primary_slot_name = '${hcfg.slot}'
72 '';
73 }
74 {
75 dest = "postgresql_replication/${name}/connection_string";
76 user = "postgres";
77 group = "postgres";
78 permissions = "0400";
79 text = hcfg.connection;
80 }
81 {
82 dest = "postgresql_replication/${name}/postgresql.conf";
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 '';
94 }
95 ]) cfg.hosts);
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
116 ${hcfg.package}/bin/pg_dumpall -h ${dataDir} -f ${backupDir}/$(${pkgs.coreutils}/bin/date -Iseconds).sql
117 '';
118 in [
119 "0 22,4,10,16 * * * postgres ${backup_script}"
120 "0 3 * * * postgres ${pkgs.coreutils}/bin/rm -f $(${pkgs.coreutils}/bin/ls -1 ${backupDir}/*.sql | ${pkgs.coreutils}/bin/sort -r | ${pkgs.gnused}/bin/sed -e '1,12d')"
121 ]) cfg.hosts);
122 };
123
124 system.activationScripts = lib.attrsets.mapAttrs' (name: hcfg:
125 lib.attrsets.nameValuePair "psql_replication_${name}" {
126 deps = [ "users" ];
127 text = ''
128 install -m 0700 -o postgres -g postgres -d ${cfg.base}/${name}/postgresql
129 install -m 0700 -o postgres -g postgres -d ${cfg.base}/${name}/postgresql_backup
130 '';
131 }) cfg.hosts;
132
133 systemd.services = lib.attrsets.mapAttrs' (name: hcfg:
134 let
135 dataDir = "${cfg.base}/${name}/postgresql";
136 in
137 lib.attrsets.nameValuePair "postgresql_backup_${name}" {
138 description = "Postgresql replication for ${name}";
139 wantedBy = [ "multi-user.target" ];
140 after = [ "network.target" ];
141
142 environment.PGDATA = dataDir;
143 path = [ hcfg.package ];
144
145 preStart = ''
146 if ! test -e ${dataDir}/PG_VERSION; then
147 mkdir -m 0700 -p ${dataDir}
148 chown -R postgres:postgres ${dataDir}
149 fi
150 '';
151 script = let
152 fp = n: config.secrets.fullPaths."postgresql_replication/${name}/${n}";
153 in ''
154 if ! test -e ${dataDir}/PG_VERSION; then
155 pg_basebackup -d $(cat ${fp "connection_string"}) -D ${dataDir} -S ${hcfg.slot}
156 fi
157 ln -sfn ${fp "recovery.conf"} ${dataDir}/recovery.conf
158 ln -sfn ${fp "postgresql.conf"} ${dataDir}/postgresql.conf
159
160 exec postgres
161 '';
162
163 serviceConfig = {
164 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
165 User = "postgres";
166 Group = "postgres";
167 PermissionsStartOnly = true;
168 RuntimeDirectory = "postgresql";
169 Type = "notify";
170
171 KillSignal = "SIGINT";
172 KillMode = "mixed";
173 # basebackup can take a long time
174 TimeoutStartSec="infinity";
175 TimeoutStopSec = 120;
176 };
177 unitConfig.RequiresMountsFor = dataDir;
178 }) cfg.hosts;
179 };
180}