981177e4dd
useful in case of network disruption
204 lines
7.6 KiB
Nix
204 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 ];
|
|
}
|