diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2019-11-11 10:08:20 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2019-11-11 10:08:20 +0100 |
commit | ec9b656476d4af2784aea29b846bead85dc46e16 (patch) | |
tree | 5ccc67f136be73786d05ce28aeb0ec978637f5a3 /modules/private | |
parent | 285380fe566700ab3bf4f69b0a1a10fb4d9bba3a (diff) | |
download | Nix-ec9b656476d4af2784aea29b846bead85dc46e16.tar.gz Nix-ec9b656476d4af2784aea29b846bead85dc46e16.tar.zst Nix-ec9b656476d4af2784aea29b846bead85dc46e16.zip |
Add postgresql replication for backup-2
Diffstat (limited to 'modules/private')
-rw-r--r-- | modules/private/databases/default.nix | 4 | ||||
-rw-r--r-- | modules/private/databases/postgresql.nix | 21 | ||||
-rw-r--r-- | modules/private/databases/postgresql_replication.nix | 180 | ||||
-rw-r--r-- | modules/private/default.nix | 1 | ||||
-rw-r--r-- | modules/private/system/backup-2.nix | 13 |
5 files changed, 217 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, ... }: | ||
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 | }; | ||
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 | } | ||
diff --git a/modules/private/default.nix b/modules/private/default.nix index c418795..b4b8c4f 100644 --- a/modules/private/default.nix +++ b/modules/private/default.nix | |||
@@ -10,6 +10,7 @@ set = { | |||
10 | openldap = ./databases/openldap; | 10 | openldap = ./databases/openldap; |
11 | postgresql = ./databases/postgresql.nix; | 11 | postgresql = ./databases/postgresql.nix; |
12 | redis = ./databases/redis.nix; | 12 | redis = ./databases/redis.nix; |
13 | postgresqlReplication = ./databases/postgresql_replication.nix; | ||
13 | 14 | ||
14 | websites = ./websites; | 15 | websites = ./websites; |
15 | atenInte = ./websites/aten/integration.nix; | 16 | atenInte = ./websites/aten/integration.nix; |
diff --git a/modules/private/system/backup-2.nix b/modules/private/system/backup-2.nix index 6151671..1c5b7d8 100644 --- a/modules/private/system/backup-2.nix +++ b/modules/private/system/backup-2.nix | |||
@@ -48,6 +48,19 @@ | |||
48 | ssh_key_private = myconfig.env.rsync_backup.ssh_key.private; | 48 | ssh_key_private = myconfig.env.rsync_backup.ssh_key.private; |
49 | }; | 49 | }; |
50 | 50 | ||
51 | myServices.databasesReplication = { | ||
52 | postgresql = { | ||
53 | enable = true; | ||
54 | base = "/backup2"; | ||
55 | hosts = { | ||
56 | eldiron = { | ||
57 | slot = "backup_2"; | ||
58 | connection = "postgresql://backup-2:${myconfig.env.ldap.backup-2.password}@eldiron.immae.eu"; | ||
59 | }; | ||
60 | }; | ||
61 | }; | ||
62 | }; | ||
63 | |||
51 | # This value determines the NixOS release with which your system is | 64 | # This value determines the NixOS release with which your system is |
52 | # to be compatible, in order to avoid breaking some software such as | 65 | # to be compatible, in order to avoid breaking some software such as |
53 | # database servers. You should change this only after NixOS release | 66 | # database servers. You should change this only after NixOS release |