205 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{ config, lib, pkgs, ... }:
 | 
						|
 | 
						|
with lib;
 | 
						|
 | 
						|
let
 | 
						|
  receiverSubmodule = {
 | 
						|
    options = {
 | 
						|
      postgresqlPackage = mkOption {
 | 
						|
        type = types.package;
 | 
						|
        example = literalExample "pkgs.postgresql_11";
 | 
						|
        description = ''
 | 
						|
          PostgreSQL package to use.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      directory = mkOption {
 | 
						|
        type = types.path;
 | 
						|
        example = literalExample "/mnt/pg_wal/main/";
 | 
						|
        description = ''
 | 
						|
          Directory to write the output to.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      statusInterval = mkOption {
 | 
						|
        type = types.int;
 | 
						|
        default = 10;
 | 
						|
        description = ''
 | 
						|
          Specifies the number of seconds between status packets sent back to the server.
 | 
						|
          This allows for easier monitoring of the progress from server.
 | 
						|
          A value of zero disables the periodic status updates completely,
 | 
						|
          although an update will still be sent when requested by the server, to avoid timeout disconnect.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      slot = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "";
 | 
						|
        example = "some_slot_name";
 | 
						|
        description = ''
 | 
						|
          Require <command>pg_receivewal</command> to use an existing replication slot (see
 | 
						|
          <link xlink:href="https://www.postgresql.org/docs/current/warm-standby.html#STREAMING-REPLICATION-SLOTS">Section 26.2.6 of the PostgreSQL manual</link>).
 | 
						|
          When this option is used, <command>pg_receivewal</command> will report a flush position to the server,
 | 
						|
          indicating when each segment has been synchronized to disk so that the server can remove that segment if it is not otherwise needed.
 | 
						|
 | 
						|
          When the replication client of <command>pg_receivewal</command> is configured on the server as a synchronous standby,
 | 
						|
          then using a replication slot will report the flush position to the server, but only when a WAL file is closed.
 | 
						|
          Therefore, that configuration will cause transactions on the primary to wait for a long time and effectively not work satisfactorily.
 | 
						|
          The option <option>synchronous</option> must be specified in addition to make this work correctly.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      synchronous = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        default = false;
 | 
						|
        description = ''
 | 
						|
          Flush the WAL data to disk immediately after it has been received.
 | 
						|
          Also send a status packet back to the server immediately after flushing, regardless of <option>statusInterval</option>.
 | 
						|
 | 
						|
          This option should be specified if the replication client of <command>pg_receivewal</command> is configured on the server as a synchronous standby,
 | 
						|
          to ensure that timely feedback is sent to the server.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      compress = mkOption {
 | 
						|
        type = types.ints.between 0 9;
 | 
						|
        default = 0;
 | 
						|
        description = ''
 | 
						|
          Enables gzip compression of write-ahead logs, and specifies the compression level
 | 
						|
          (<literal>0</literal> through <literal>9</literal>, <literal>0</literal> being no compression and <literal>9</literal> being best compression).
 | 
						|
          The suffix <literal>.gz</literal> will automatically be added to all filenames.
 | 
						|
 | 
						|
          This option requires PostgreSQL >= 10.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      connection = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        example = "postgresql://user@somehost";
 | 
						|
        description = ''
 | 
						|
          Specifies parameters used to connect to the server, as a connection string.
 | 
						|
          See <link xlink:href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING">Section 34.1.1 of the PostgreSQL manual</link> for more information.
 | 
						|
 | 
						|
          Because <command>pg_receivewal</command> doesn't connect to any particular database in the cluster,
 | 
						|
          database name in the connection string will be ignored.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      extraArgs = mkOption {
 | 
						|
        type = with types; listOf str;
 | 
						|
        default = [ ];
 | 
						|
        example = literalExample ''
 | 
						|
          [
 | 
						|
            "--no-sync"
 | 
						|
          ]
 | 
						|
        '';
 | 
						|
        description = ''
 | 
						|
          A list of extra arguments to pass to the <command>pg_receivewal</command> command.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      environment = mkOption {
 | 
						|
        type = with types; attrsOf str;
 | 
						|
        default = { };
 | 
						|
        example = literalExample ''
 | 
						|
          {
 | 
						|
            PGPASSFILE = "/private/passfile";
 | 
						|
            PGSSLMODE = "require";
 | 
						|
          }
 | 
						|
        '';
 | 
						|
        description = ''
 | 
						|
          Environment variables passed to the service.
 | 
						|
          Usable parameters are listed in <link xlink:href="https://www.postgresql.org/docs/current/libpq-envars.html">Section 34.14 of the PostgreSQL manual</link>.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
    };
 | 
						|
  };
 | 
						|
 | 
						|
in {
 | 
						|
  options = {
 | 
						|
    services.postgresqlWalReceiver = {
 | 
						|
      receivers = mkOption {
 | 
						|
        type = with types; attrsOf (submodule receiverSubmodule);
 | 
						|
        default = { };
 | 
						|
        example = literalExample ''
 | 
						|
          {
 | 
						|
            main = {
 | 
						|
              postgresqlPackage = pkgs.postgresql_11;
 | 
						|
              directory = /mnt/pg_wal/main/;
 | 
						|
              slot = "main_wal_receiver";
 | 
						|
              connection = "postgresql://user@somehost";
 | 
						|
            };
 | 
						|
          }
 | 
						|
        '';
 | 
						|
        description = ''
 | 
						|
          PostgreSQL WAL receivers.
 | 
						|
          Stream write-ahead logs from a PostgreSQL server using <command>pg_receivewal</command> (formerly <command>pg_receivexlog</command>).
 | 
						|
          See <link xlink:href="https://www.postgresql.org/docs/current/app-pgreceivewal.html">the man page</link> for more information.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
    };
 | 
						|
  };
 | 
						|
 | 
						|
  config = let
 | 
						|
    receivers = config.services.postgresqlWalReceiver.receivers;
 | 
						|
  in mkIf (receivers != { }) {
 | 
						|
    users = {
 | 
						|
      users.postgres = {
 | 
						|
        uid = config.ids.uids.postgres;
 | 
						|
        group = "postgres";
 | 
						|
        description = "PostgreSQL server user";
 | 
						|
      };
 | 
						|
 | 
						|
      groups.postgres = {
 | 
						|
        gid = config.ids.gids.postgres;
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
    assertions = concatLists (attrsets.mapAttrsToList (name: config: [
 | 
						|
      {
 | 
						|
        assertion = config.compress > 0 -> versionAtLeast config.postgresqlPackage.version "10";
 | 
						|
        message = "Invalid configuration for WAL receiver \"${name}\": compress requires PostgreSQL version >= 10.";
 | 
						|
      }
 | 
						|
    ]) receivers);
 | 
						|
 | 
						|
    systemd.tmpfiles.rules = mapAttrsToList (name: config: ''
 | 
						|
      d ${escapeShellArg config.directory} 0750 postgres postgres - -
 | 
						|
    '') receivers;
 | 
						|
 | 
						|
    systemd.services = with attrsets; mapAttrs' (name: config: nameValuePair "postgresql-wal-receiver-${name}" {
 | 
						|
      description = "PostgreSQL WAL receiver (${name})";
 | 
						|
      wantedBy = [ "multi-user.target" ];
 | 
						|
      startLimitIntervalSec = 0; # retry forever, useful in case of network disruption
 | 
						|
 | 
						|
      serviceConfig = {
 | 
						|
        User = "postgres";
 | 
						|
        Group = "postgres";
 | 
						|
        KillSignal = "SIGINT";
 | 
						|
        Restart = "always";
 | 
						|
        RestartSec = 60;
 | 
						|
      };
 | 
						|
 | 
						|
      inherit (config) environment;
 | 
						|
 | 
						|
      script = let
 | 
						|
        receiverCommand = postgresqlPackage:
 | 
						|
         if (versionAtLeast postgresqlPackage.version "10")
 | 
						|
           then "${postgresqlPackage}/bin/pg_receivewal"
 | 
						|
           else "${postgresqlPackage}/bin/pg_receivexlog";
 | 
						|
      in ''
 | 
						|
        ${receiverCommand config.postgresqlPackage} \
 | 
						|
          --no-password \
 | 
						|
          --directory=${escapeShellArg config.directory} \
 | 
						|
          --status-interval=${toString config.statusInterval} \
 | 
						|
          --dbname=${escapeShellArg config.connection} \
 | 
						|
          ${optionalString (config.compress > 0) "--compress=${toString config.compress}"} \
 | 
						|
          ${optionalString (config.slot != "") "--slot=${escapeShellArg config.slot}"} \
 | 
						|
          ${optionalString config.synchronous "--synchronous"} \
 | 
						|
          ${concatStringsSep " " config.extraArgs}
 | 
						|
      '';
 | 
						|
    }) receivers;
 | 
						|
  };
 | 
						|
 | 
						|
  meta.maintainers = with maintainers; [ pacien ];
 | 
						|
}
 |