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 hosts = lib.mkOption {
19 type = lib.types.attrsOf (lib.types.submodule {
21 package = lib.mkOption {
22 type = lib.types.package;
23 default = pkgs.postgresql;
25 Postgresql package for this host
31 Slot to use for replication
34 connection = lib.mkOption {
37 Connection string to access the psql master
45 config = lib.mkIf cfg.enable {
46 nixpkgs.overlays = [ (self: super: {
47 postgresql = self.postgresql_11_custom;
50 users.users.postgres = {
52 uid = config.ids.uids.postgres;
54 description = "PostgreSQL server user";
55 home = "/var/lib/postgresql";
56 useDefaultShell = true;
57 extraGroups = [ "keys" ];
59 users.groups.postgres.gid = config.ids.gids.postgres;
60 environment.systemPackages = [ pkgs.postgresql ];
62 secrets.keys = lib.flatten (lib.mapAttrsToList (name: hcfg: [
64 dest = "postgresql_replication/${name}/recovery.conf";
70 primary_conninfo = '${hcfg.connection}?sslmode=require'
71 primary_slot_name = '${hcfg.slot}'
75 dest = "postgresql_replication/${name}/connection_string";
79 text = hcfg.connection;
82 dest = "postgresql_replication/${name}/postgresql.conf";
87 dataDir = "${cfg.base}/${name}/postgresql";
89 listen_addresses = '''
90 unix_socket_directories = '${dataDir}'
91 data_directory = '${dataDir}'
99 systemCronJobs = lib.flatten (lib.mapAttrsToList (name: hcfg:
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}
108 resume_replication() {
109 ${hcfg.package}/bin/psql -h ${dataDir} -c "SELECT pg_wal_replay_resume();" >/dev/null || echo "impossible to resume replication"
112 trap resume_replication EXIT
114 ${hcfg.package}/bin/psql -h ${dataDir} -c "SELECT pg_wal_replay_pause();" >/dev/null || (echo "impossible to pause replication" && false)
116 ${hcfg.package}/bin/pg_dumpall -h ${dataDir} -f ${backupDir}/$(${pkgs.coreutils}/bin/date -Iseconds).sql
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')"
124 system.activationScripts = lib.attrsets.mapAttrs' (name: hcfg:
125 lib.attrsets.nameValuePair "psql_replication_${name}" {
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
133 systemd.services = lib.attrsets.mapAttrs' (name: hcfg:
135 dataDir = "${cfg.base}/${name}/postgresql";
137 lib.attrsets.nameValuePair "postgresql_backup_${name}" {
138 description = "Postgresql replication for ${name}";
139 wantedBy = [ "multi-user.target" ];
140 after = [ "network.target" ];
142 environment.PGDATA = dataDir;
143 path = [ hcfg.package ];
146 if ! test -e ${dataDir}/PG_VERSION; then
147 mkdir -m 0700 -p ${dataDir}
148 chown -R postgres:postgres ${dataDir}
152 fp = n: config.secrets.fullPaths."postgresql_replication/${name}/${n}";
154 if ! test -e ${dataDir}/PG_VERSION; then
155 pg_basebackup -d $(cat ${fp "connection_string"}) -D ${dataDir} -S ${hcfg.slot}
157 ln -sfn ${fp "recovery.conf"} ${dataDir}/recovery.conf
158 ln -sfn ${fp "postgresql.conf"} ${dataDir}/postgresql.conf
164 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
167 PermissionsStartOnly = true;
168 RuntimeDirectory = "postgresql";
171 KillSignal = "SIGINT";
173 # basebackup can take a long time
174 TimeoutStartSec="infinity";
175 TimeoutStopSec = 120;
177 unitConfig.RequiresMountsFor = dataDir;