1 { pkgs, config, lib, ... }:
3 cfg = config.myServices.databasesReplication.postgresql;
6 options.myServices.databasesReplication.postgresql = {
7 enable = lib.mkEnableOption "Enable postgresql replication";
11 Base path to put the replications
14 mainPackage = lib.mkOption {
15 type = lib.types.package;
16 default = pkgs.postgresql;
18 Postgresql package available in shell
21 hosts = lib.mkOption {
26 type = lib.types.attrsOf (lib.types.submodule {
28 package = lib.mkOption {
29 type = lib.types.package;
30 default = pkgs.postgresql;
32 Postgresql package for this host
38 Slot to use for replication
41 connection = lib.mkOption {
44 Connection string to access the psql master
52 config = lib.mkIf cfg.enable {
53 users.users.postgres = {
55 uid = config.ids.uids.postgres;
57 description = "PostgreSQL server user";
58 home = "/var/lib/postgresql";
59 useDefaultShell = true;
60 extraGroups = [ "keys" ];
62 users.groups.postgres.gid = config.ids.gids.postgres;
63 environment.systemPackages = [ cfg.mainPackage ];
65 secrets.keys = lib.flatten (lib.mapAttrsToList (name: hcfg: [
67 dest = "postgresql_replication/${name}/recovery.conf";
73 primary_conninfo = '${hcfg.connection}?sslmode=require'
74 primary_slot_name = '${hcfg.slot}'
78 dest = "postgresql_replication/${name}/connection_string";
82 text = hcfg.connection;
85 dest = "postgresql_replication/${name}/postgresql.conf";
90 dataDir = "${cfg.base}/${name}/postgresql";
92 listen_addresses = '''
93 unix_socket_directories = '${dataDir}'
94 data_directory = '${dataDir}'
102 systemCronJobs = lib.flatten (lib.mapAttrsToList (name: hcfg:
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}
111 resume_replication() {
112 ${hcfg.package}/bin/psql -h ${dataDir} -c "SELECT pg_wal_replay_resume();" >/dev/null || echo "impossible to resume replication"
115 trap resume_replication EXIT
117 ${hcfg.package}/bin/psql -h ${dataDir} -c "SELECT pg_wal_replay_pause();" >/dev/null || (echo "impossible to pause replication" && false)
119 ${hcfg.package}/bin/pg_dumpall -h ${dataDir} -f ${backupDir}/$(${pkgs.coreutils}/bin/date -Iminutes).sql
121 u = pkgs.callPackage ./utils.nix {};
122 cleanup_script = pkgs.writeScript "cleanup_postgresql_${name}" (u.keepLastNDumps "sql" backupDir 6);
124 "0 22,4,10,16 * * * postgres ${backup_script}"
125 "0 3 * * * postgres ${cleanup_script}"
129 system.activationScripts = lib.attrsets.mapAttrs' (name: hcfg:
130 lib.attrsets.nameValuePair "psql_replication_${name}" {
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
138 systemd.services = lib.attrsets.mapAttrs' (name: hcfg:
140 dataDir = "${cfg.base}/${name}/postgresql";
142 lib.attrsets.nameValuePair "postgresql_backup_${name}" {
143 description = "Postgresql replication for ${name}";
144 wantedBy = [ "multi-user.target" ];
145 after = [ "network.target" ];
147 environment.PGDATA = dataDir;
148 path = [ hcfg.package ];
151 if ! test -e ${dataDir}/PG_VERSION; then
152 mkdir -m 0700 -p ${dataDir}
153 chown -R postgres:postgres ${dataDir}
157 fp = n: config.secrets.fullPaths."postgresql_replication/${name}/${n}";
159 if ! test -e ${dataDir}/PG_VERSION; then
160 pg_basebackup -d $(cat ${fp "connection_string"}) -D ${dataDir} -S ${hcfg.slot}
162 ln -sfn ${fp "recovery.conf"} ${dataDir}/recovery.conf
163 ln -sfn ${fp "postgresql.conf"} ${dataDir}/postgresql.conf
169 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
172 PermissionsStartOnly = true;
173 RuntimeDirectory = "postgresql";
176 KillSignal = "SIGINT";
178 # basebackup can take a long time
179 TimeoutStartSec="infinity";
180 TimeoutStopSec = 120;
182 unitConfig.RequiresMountsFor = dataDir;