···2425- [Apache Guacamole](https://guacamole.apache.org/), a cross-platform, clientless remote desktop gateway. Available as [services.guacamole-server](#opt-services.guacamole-server.enable) and [services.guacamole-client](#opt-services.guacamole-client.enable) services.
260027- [trust-dns](https://trust-dns.org/), a Rust based DNS server built to be safe and secure from the ground up. Available as [services.trust-dns](#opt-services.trust-dns.enable).
2829- [osquery](https://www.osquery.io/), a SQL powered operating system instrumentation, monitoring, and analytics.
3031- [ebusd](https://ebusd.eu), a daemon for handling communication with eBUS devices connected to a 2-wire bus system (“energy bus” used by numerous heating systems). Available as [services.ebusd](#opt-services.ebusd.enable).
03233## Backward Incompatibilities {#sec-release-23.11-incompatibilities}
34
···2425- [Apache Guacamole](https://guacamole.apache.org/), a cross-platform, clientless remote desktop gateway. Available as [services.guacamole-server](#opt-services.guacamole-server.enable) and [services.guacamole-client](#opt-services.guacamole-client.enable) services.
2627+- [pgBouncer](https://www.pgbouncer.org), a PostgreSQL connection pooler. Available as [services.pgbouncer](#opt-services.pgbouncer.enable).
28+29- [trust-dns](https://trust-dns.org/), a Rust based DNS server built to be safe and secure from the ground up. Available as [services.trust-dns](#opt-services.trust-dns.enable).
3031- [osquery](https://www.osquery.io/), a SQL powered operating system instrumentation, monitoring, and analytics.
3233- [ebusd](https://ebusd.eu), a daemon for handling communication with eBUS devices connected to a 2-wire bus system (“energy bus” used by numerous heating systems). Available as [services.ebusd](#opt-services.ebusd.enable).
34+3536## Backward Incompatibilities {#sec-release-23.11-incompatibilities}
37
···1+{ lib, pkgs, config, ... } :
2+3+with lib;
4+5+let
6+ cfg = config.services.pgbouncer;
7+8+ confFile = pkgs.writeTextFile {
9+ name = "pgbouncer.ini";
10+ text = ''
11+ [databases]
12+ ${concatStringsSep "\n"
13+ (mapAttrsToList (dbname : settings : "${dbname} = ${settings}") cfg.databases)}
14+15+ [users]
16+ ${concatStringsSep "\n"
17+ (mapAttrsToList (username : settings : "${username} = ${settings}") cfg.users)}
18+19+ [peers]
20+ ${concatStringsSep "\n"
21+ (mapAttrsToList (peerid : settings : "${peerid} = ${settings}") cfg.peers)}
22+23+ [pgbouncer]
24+ # general
25+ ${optionalString (cfg.ignoreStartupParameters != null) "ignore_startup_parameters = ${cfg.ignoreStartupParameters}"}
26+ listen_port = ${toString cfg.listenPort}
27+ ${optionalString (cfg.listenAddress != null) "listen_addr = ${cfg.listenAddress}"}
28+ pool_mode = ${cfg.poolMode}
29+ max_client_conn = ${toString cfg.maxClientConn}
30+ default_pool_size = ${toString cfg.defaultPoolSize}
31+ max_user_connections = ${toString cfg.maxUserConnections}
32+ max_db_connections = ${toString cfg.maxDbConnections}
33+34+ #auth
35+ auth_type = ${cfg.authType}
36+ ${optionalString (cfg.authHbaFile != null) "auth_hba_file = ${cfg.authHbaFile}"}
37+ ${optionalString (cfg.authFile != null) "auth_file = ${cfg.authFile}"}
38+ ${optionalString (cfg.authUser != null) "auth_user = ${cfg.authUser}"}
39+ ${optionalString (cfg.authQuery != null) "auth_query = ${cfg.authQuery}"}
40+ ${optionalString (cfg.authDbname != null) "auth_dbname = ${cfg.authDbname}"}
41+42+ # TLS
43+ ${optionalString (cfg.tls.client != null) ''
44+ client_tls_sslmode = ${cfg.tls.client.sslmode}
45+ client_tls_key_file = ${cfg.tls.client.keyFile}
46+ client_tls_cert_file = ${cfg.tls.client.certFile}
47+ client_tls_ca_file = ${cfg.tls.client.caFile}
48+ ''}
49+ ${optionalString (cfg.tls.server != null) ''
50+ server_tls_sslmode = ${cfg.tls.server.sslmode}
51+ server_tls_key_file = ${cfg.tls.server.keyFile}
52+ server_tls_cert_file = ${cfg.tls.server.certFile}
53+ server_tls_ca_file = ${cfg.tls.server.caFile}
54+ ''}
55+56+ # log
57+ ${optionalString (cfg.logFile != null) "logfile = ${cfg.homeDir}/${cfg.logFile}"}
58+ ${optionalString (cfg.syslog != null) ''
59+ syslog = ${if cfg.syslog.enable then "1" else "0"}
60+ syslog_ident = ${cfg.syslog.syslogIdent}
61+ syslog_facility = ${cfg.syslog.syslogFacility}
62+ ''}
63+ ${optionalString (cfg.verbose != null) "verbose = ${toString cfg.verbose}"}
64+65+ # console access
66+ ${optionalString (cfg.adminUsers != null) "admin_users = ${cfg.adminUsers}"}
67+ ${optionalString (cfg.statsUsers != null) "stats_users = ${cfg.statsUsers}"}
68+69+ # linux
70+ pidfile = /run/pgbouncer/pgbouncer.pid
71+72+ # extra
73+ ${cfg.extraConfig}
74+ '';
75+ };
76+77+in {
78+79+ options.services.pgbouncer = {
80+81+ # NixOS settings
82+83+ enable = mkEnableOption (lib.mdDoc "PostgreSQL connection pooler");
84+85+ package = mkOption {
86+ type = types.package;
87+ default = pkgs.pgbouncer;
88+ defaultText = literalExpression "pkgs.pgbouncer";
89+ description = lib.mdDoc ''
90+ The pgbouncer package to use.
91+ '';
92+ };
93+94+ openFirewall = mkOption {
95+ type = types.bool;
96+ default = false;
97+ description = lib.mdDoc ''
98+ Whether to automatically open the specified TCP port in the firewall.
99+ '';
100+ };
101+102+ # Generic settings
103+104+ logFile = mkOption {
105+ type = types.nullOr types.str;
106+ default = "pgbouncer.log";
107+ description = lib.mdDoc ''
108+ Specifies the log file.
109+ Either this or syslog has to be specified.
110+ '';
111+ };
112+113+ listenAddress = mkOption {
114+ type = types.nullOr types.commas;
115+ example = "*";
116+ default = null;
117+ description = lib.mdDoc ''
118+ Specifies a list (comma-separated) of addresses where to listen for TCP connections.
119+ You may also use * meaning “listen on all addresses”.
120+ When not set, only Unix socket connections are accepted.
121+122+ Addresses can be specified numerically (IPv4/IPv6) or by name.
123+ '';
124+ };
125+126+ listenPort = mkOption {
127+ type = types.port;
128+ default = 6432;
129+ description = lib.mdDoc ''
130+ Which port to listen on. Applies to both TCP and Unix sockets.
131+ '';
132+ };
133+134+ poolMode = mkOption {
135+ type = types.enum [ "session" "transaction" "statement" ];
136+ default = "session";
137+ description = lib.mdDoc ''
138+ Specifies when a server connection can be reused by other clients.
139+140+ session
141+ Server is released back to pool after client disconnects. Default.
142+ transaction
143+ Server is released back to pool after transaction finishes.
144+ statement
145+ Server is released back to pool after query finishes.
146+ Transactions spanning multiple statements are disallowed in this mode.
147+ '';
148+ };
149+150+ maxClientConn = mkOption {
151+ type = types.int;
152+ default = 100;
153+ description = lib.mdDoc ''
154+ Maximum number of client connections allowed.
155+156+ When this setting is increased, then the file descriptor limits in the operating system
157+ might also have to be increased. Note that the number of file descriptors potentially
158+ used is more than maxClientConn. If each user connects under its own user name to the server,
159+ the theoretical maximum used is:
160+ maxClientConn + (max pool_size * total databases * total users)
161+162+ If a database user is specified in the connection string (all users connect under the same user name),
163+ the theoretical maximum is:
164+ maxClientConn + (max pool_size * total databases)
165+166+ The theoretical maximum should never be reached, unless somebody deliberately crafts a special load for it.
167+ Still, it means you should set the number of file descriptors to a safely high number.
168+ '';
169+ };
170+171+ defaultPoolSize = mkOption {
172+ type = types.int;
173+ default = 20;
174+ description = lib.mdDoc ''
175+ How many server connections to allow per user/database pair.
176+ Can be overridden in the per-database configuration.
177+ '';
178+ };
179+180+ maxDbConnections = mkOption {
181+ type = types.int;
182+ default = 0;
183+ description = lib.mdDoc ''
184+ Do not allow more than this many server connections per database (regardless of user).
185+ This considers the PgBouncer database that the client has connected to,
186+ not the PostgreSQL database of the outgoing connection.
187+188+ This can also be set per database in the [databases] section.
189+190+ Note that when you hit the limit, closing a client connection to one pool will
191+ not immediately allow a server connection to be established for another pool,
192+ because the server connection for the first pool is still open.
193+ Once the server connection closes (due to idle timeout),
194+ a new server connection will immediately be opened for the waiting pool.
195+196+ 0 = unlimited
197+ '';
198+ };
199+200+ maxUserConnections = mkOption {
201+ type = types.int;
202+ default = 0;
203+ description = lib.mdDoc ''
204+ Do not allow more than this many server connections per user (regardless of database).
205+ This considers the PgBouncer user that is associated with a pool,
206+ which is either the user specified for the server connection
207+ or in absence of that the user the client has connected as.
208+209+ This can also be set per user in the [users] section.
210+211+ Note that when you hit the limit, closing a client connection to one pool
212+ will not immediately allow a server connection to be established for another pool,
213+ because the server connection for the first pool is still open.
214+ Once the server connection closes (due to idle timeout), a new server connection
215+ will immediately be opened for the waiting pool.
216+217+ 0 = unlimited
218+ '';
219+ };
220+221+ ignoreStartupParameters = mkOption {
222+ type = types.nullOr types.commas;
223+ example = "extra_float_digits";
224+ default = null;
225+ description = lib.mdDoc ''
226+ By default, PgBouncer allows only parameters it can keep track of in startup packets:
227+ client_encoding, datestyle, timezone and standard_conforming_strings.
228+229+ All others parameters will raise an error.
230+ To allow others parameters, they can be specified here, so that PgBouncer knows that
231+ they are handled by the admin and it can ignore them.
232+233+ If you need to specify multiple values, use a comma-separated list.
234+235+ IMPORTANT: When using prometheus-pgbouncer-exporter, you need:
236+ extra_float_digits
237+ <https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration>
238+ '';
239+ };
240+241+ # Section [databases]
242+ databases = mkOption {
243+ type = types.attrsOf types.str;
244+ default = {};
245+ example = {
246+ exampledb = "host=/run/postgresql/ port=5432 auth_user=exampleuser dbname=exampledb sslmode=require";
247+ bardb = "host=localhost dbname=bazdb";
248+ foodb = "host=host1.example.com port=5432";
249+ };
250+ description = lib.mdDoc ''
251+ Detailed information about PostgreSQL database definitions:
252+ <https://www.pgbouncer.org/config.html#section-databases>
253+ '';
254+ };
255+256+ # Section [users]
257+ users = mkOption {
258+ type = types.attrsOf types.str;
259+ default = {};
260+ example = {
261+ user1 = "pool_mode=session";
262+ };
263+ description = lib.mdDoc ''
264+ Optional.
265+266+ Detailed information about PostgreSQL user definitions:
267+ <https://www.pgbouncer.org/config.html#section-users>
268+ '';
269+ };
270+271+ # Section [peers]
272+ peers = mkOption {
273+ type = types.attrsOf types.str;
274+ default = {};
275+ example = {
276+ "1" = "host=host1.example.com";
277+ "2" = "host=/tmp/pgbouncer-2 port=5555";
278+ };
279+ description = lib.mdDoc ''
280+ Optional.
281+282+ Detailed information about PostgreSQL database definitions:
283+ <https://www.pgbouncer.org/config.html#section-peers>
284+ '';
285+ };
286+287+ # Authentication settings
288+ authType = mkOption {
289+ type = types.enum [ "cert" "md5" "scram-sha-256" "plain" "trust" "any" "hba" "pam" ];
290+ default = "md5";
291+ description = lib.mdDoc ''
292+ How to authenticate users.
293+294+ cert
295+ Client must connect over TLS connection with a valid client certificate.
296+ The user name is then taken from the CommonName field from the certificate.
297+ md5
298+ Use MD5-based password check. This is the default authentication method.
299+ authFile may contain both MD5-encrypted and plain-text passwords.
300+ If md5 is configured and a user has a SCRAM secret, then SCRAM authentication is used automatically instead.
301+ scram-sha-256
302+ Use password check with SCRAM-SHA-256. authFile has to contain SCRAM secrets or plain-text passwords.
303+ plain
304+ The clear-text password is sent over the wire. Deprecated.
305+ trust
306+ No authentication is done. The user name must still exist in authFile.
307+ any
308+ Like the trust method, but the user name given is ignored.
309+ Requires that all databases are configured to log in as a specific user.
310+ Additionally, the console database allows any user to log in as admin.
311+ hba
312+ The actual authentication type is loaded from authHbaFile.
313+ This allows different authentication methods for different access paths,
314+ for example: connections over Unix socket use the peer auth method, connections over TCP must use TLS.
315+ pam
316+ PAM is used to authenticate users, authFile is ignored.
317+ This method is not compatible with databases using the authUser option.
318+ The service name reported to PAM is “pgbouncer”. pam is not supported in the HBA configuration file.
319+ '';
320+ };
321+322+ authHbaFile = mkOption {
323+ type = types.nullOr types.path;
324+ default = null;
325+ example = "/secrets/pgbouncer_hba";
326+ description = lib.mdDoc ''
327+ HBA configuration file to use when authType is hba.
328+329+ See HBA file format details:
330+ <https://www.pgbouncer.org/config.html#hba-file-format>
331+ '';
332+ };
333+334+ authFile = mkOption {
335+ type = types.nullOr types.path;
336+ default = null;
337+ example = "/secrets/pgbouncer_authfile";
338+ description = lib.mdDoc ''
339+ The name of the file to load user names and passwords from.
340+341+ See section Authentication file format details:
342+ <https://www.pgbouncer.org/config.html#authentication-file-format>
343+344+ Most authentication types require that either authFile or authUser be set;
345+ otherwise there would be no users defined.
346+ '';
347+ };
348+349+ authUser = mkOption {
350+ type = types.nullOr types.str;
351+ default = null;
352+ example = "pgbouncer";
353+ description = lib.mdDoc ''
354+ If authUser is set, then any user not specified in authFile will be queried
355+ through the authQuery query from pg_shadow in the database, using authUser.
356+ The password of authUser will be taken from authFile.
357+ (If the authUser does not require a password then it does not need to be defined in authFile.)
358+359+ Direct access to pg_shadow requires admin rights.
360+ It's preferable to use a non-superuser that calls a SECURITY DEFINER function instead.
361+ '';
362+ };
363+364+ authQuery = mkOption {
365+ type = types.nullOr types.str;
366+ default = null;
367+ example = "SELECT usename, passwd FROM pg_shadow WHERE usename=$1";
368+ description = lib.mdDoc ''
369+ Query to load user's password from database.
370+371+ Direct access to pg_shadow requires admin rights.
372+ It's preferable to use a non-superuser that calls a SECURITY DEFINER function instead.
373+374+ Note that the query is run inside the target database.
375+ So if a function is used, it needs to be installed into each database.
376+ '';
377+ };
378+379+ authDbname = mkOption {
380+ type = types.nullOr types.str;
381+ default = null;
382+ example = "authdb";
383+ description = lib.mdDoc ''
384+ Database name in the [database] section to be used for authentication purposes.
385+ This option can be either global or overriden in the connection string if this parameter is specified.
386+ '';
387+ };
388+389+ # TLS settings
390+ tls.client = mkOption {
391+ type = types.nullOr (types.submodule {
392+ options = {
393+ sslmode = mkOption {
394+ type = types.enum [ "disable" "allow" "prefer" "require" "verify-ca" "verify-full" ];
395+ default = "disable";
396+ description = lib.mdDoc ''
397+ TLS mode to use for connections from clients.
398+ TLS connections are disabled by default.
399+400+ When enabled, tls.client.keyFile and tls.client.certFile
401+ must be also configured to set up the key and certificate
402+ PgBouncer uses to accept client connections.
403+404+ disable
405+ Plain TCP. If client requests TLS, it's ignored. Default.
406+ allow
407+ If client requests TLS, it is used. If not, plain TCP is used.
408+ If the client presents a client certificate, it is not validated.
409+ prefer
410+ Same as allow.
411+ require
412+ Client must use TLS. If not, the client connection is rejected.
413+ If the client presents a client certificate, it is not validated.
414+ verify-ca
415+ Client must use TLS with valid client certificate.
416+ verify-full
417+ Same as verify-ca
418+ '';
419+ };
420+ certFile = mkOption {
421+ type = types.path;
422+ example = "/secrets/pgbouncer.key";
423+ description = lib.mdDoc "Path to certificate for private key. Clients can validate it";
424+ };
425+ keyFile = mkOption {
426+ type = types.path;
427+ example = "/secrets/pgbouncer.crt";
428+ description = lib.mdDoc "Path to private key for PgBouncer to accept client connections";
429+ };
430+ caFile = mkOption {
431+ type = types.path;
432+ example = "/secrets/pgbouncer.crt";
433+ description = lib.mdDoc "Path to root certificate file to validate client certificates";
434+ };
435+ };
436+ });
437+ default = null;
438+ description = lib.mdDoc ''
439+ <https://www.pgbouncer.org/config.html#tls-settings>
440+ '';
441+ };
442+443+ tls.server = mkOption {
444+ type = types.nullOr (types.submodule {
445+ options = {
446+ sslmode = mkOption {
447+ type = types.enum [ "disable" "allow" "prefer" "require" "verify-ca" "verify-full" ];
448+ default = "disable";
449+ description = lib.mdDoc ''
450+ TLS mode to use for connections to PostgreSQL servers.
451+ TLS connections are disabled by default.
452+453+ disable
454+ Plain TCP. TLS is not even requested from the server. Default.
455+ allow
456+ FIXME: if server rejects plain, try TLS?
457+ prefer
458+ TLS connection is always requested first from PostgreSQL.
459+ If refused, the connection will be established over plain TCP.
460+ Server certificate is not validated.
461+ require
462+ Connection must go over TLS. If server rejects it, plain TCP is not attempted.
463+ Server certificate is not validated.
464+ verify-ca
465+ Connection must go over TLS and server certificate must be valid according to tls.server.caFile.
466+ Server host name is not checked against certificate.
467+ verify-full
468+ Connection must go over TLS and server certificate must be valid according to tls.server.caFile.
469+ Server host name must match certificate information.
470+ '';
471+ };
472+ certFile = mkOption {
473+ type = types.path;
474+ example = "/secrets/pgbouncer_server.key";
475+ description = lib.mdDoc "Certificate for private key. PostgreSQL server can validate it.";
476+ };
477+ keyFile = mkOption {
478+ type = types.path;
479+ example = "/secrets/pgbouncer_server.crt";
480+ description = lib.mdDoc "Private key for PgBouncer to authenticate against PostgreSQL server.";
481+ };
482+ caFile = mkOption {
483+ type = types.path;
484+ example = "/secrets/pgbouncer_server.crt";
485+ description = lib.mdDoc "Root certificate file to validate PostgreSQL server certificates.";
486+ };
487+ };
488+ });
489+ default = null;
490+ description = lib.mdDoc ''
491+ <https://www.pgbouncer.org/config.html#tls-settings>
492+ '';
493+ };
494+495+ # Log settings
496+ syslog = mkOption {
497+ type = types.nullOr (types.submodule {
498+ options = {
499+ enable = mkOption {
500+ type = types.bool;
501+ default = false;
502+ description = lib.mdDoc ''
503+ Toggles syslog on/off.
504+ '';
505+ };
506+ syslogIdent = mkOption {
507+ type = types.str;
508+ default = "pgbouncer";
509+ description = lib.mdDoc ''
510+ Under what name to send logs to syslog.
511+ '';
512+ };
513+ syslogFacility = mkOption {
514+ type = types.enum [ "auth" "authpriv" "daemon" "user" "local0" "local1" "local2" "local3" "local4" "local5" "local6" "local7" ];
515+ default = "daemon";
516+ description = lib.mdDoc ''
517+ Under what facility to send logs to syslog.
518+ '';
519+ };
520+ };
521+ });
522+ default = null;
523+ description = lib.mdDoc ''
524+ <https://www.pgbouncer.org/config.html#log-settings>
525+ '';
526+ };
527+528+ verbose = lib.mkOption {
529+ type = lib.types.int;
530+ default = 0;
531+ description = lib.mdDoc ''
532+ Increase verbosity. Mirrors the “-v” switch on the command line.
533+ '';
534+ };
535+536+ # Console access control
537+ adminUsers = mkOption {
538+ type = types.nullOr types.commas;
539+ default = null;
540+ description = lib.mdDoc ''
541+ Comma-separated list of database users that are allowed to connect and run all commands on the console.
542+ Ignored when authType is any, in which case any user name is allowed in as admin.
543+ '';
544+ };
545+546+ statsUsers = mkOption {
547+ type = types.nullOr types.commas;
548+ default = null;
549+ description = lib.mdDoc ''
550+ Comma-separated list of database users that are allowed to connect and run read-only queries on the console.
551+ That means all SHOW commands except SHOW FDS.
552+ '';
553+ };
554+555+ # Linux settings
556+ openFilesLimit = lib.mkOption {
557+ type = lib.types.int;
558+ default = 65536;
559+ description = lib.mdDoc ''
560+ Maximum number of open files.
561+ '';
562+ };
563+564+ user = mkOption {
565+ type = types.str;
566+ default = "pgbouncer";
567+ description = lib.mdDoc ''
568+ The user pgbouncer is run as.
569+ '';
570+ };
571+572+ group = mkOption {
573+ type = types.str;
574+ default = "pgbouncer";
575+ description = lib.mdDoc ''
576+ The group pgbouncer is run as.
577+ '';
578+ };
579+580+ homeDir = mkOption {
581+ type = types.path;
582+ default = "/var/lib/pgbouncer";
583+ description = lib.mdDoc ''
584+ Specifies the home directory.
585+ '';
586+ };
587+588+ # Extra settings
589+ extraConfig = mkOption {
590+ type = types.lines;
591+ description = lib.mdDoc ''
592+ Any additional text to be appended to config.ini
593+ <https://www.pgbouncer.org/config.html>.
594+ '';
595+ default = "";
596+ };
597+ };
598+599+ config = mkIf cfg.enable {
600+ users.groups.${cfg.group} = { };
601+ users.users.${cfg.user} = {
602+ description = "PgBouncer service user";
603+ group = cfg.group;
604+ home = cfg.homeDir;
605+ createHome = true;
606+ isSystemUser = true;
607+ };
608+609+ systemd.services.pgbouncer = {
610+ description = "PgBouncer - PostgreSQL connection pooler";
611+ wants = [ "postgresql.service" ];
612+ after = [ "postgresql.service" ];
613+ wantedBy = [ "multi-user.target" ];
614+ serviceConfig = {
615+ Type = "forking";
616+ User = cfg.user;
617+ Group = cfg.group;
618+ ExecStart = "${pkgs.pgbouncer}/bin/pgbouncer -d ${confFile}";
619+ ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
620+ RuntimeDirectory = "pgbouncer";
621+ PIDFile = "/run/pgbouncer/pgbouncer.pid";
622+ LimitNOFILE = cfg.openFilesLimit;
623+ };
624+ };
625+626+ networking.firewall.allowedTCPPorts = optional cfg.openFirewall cfg.port;
627+628+ };
629+630+ meta.maintainers = [ maintainers._1000101 ];
631+632+}