diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index 2792f3f2ad8b..649a28aede43 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -184,6 +184,8 @@ - `services.ddclient.use` has been deprecated: `ddclient` now supports separate IPv4 and IPv6 configuration. Use `services.ddclient.usev4` and `services.ddclient.usev6` instead. +- `services.pgbouncer` systemd service is configured with `Type=notify-reload` and allows reloading configuration without process restart. PgBouncer configuration options were moved to the free-form type option named [`services.pgbouncer.settings`](#opt-services.pgbouncer.settings) according to the NixOS RFC 0042. + - `teleport` has been upgraded from major version 15 to major version 16. Refer to upstream [upgrade instructions](https://goteleport.com/docs/management/operations/upgrading/) and [release notes for v16](https://goteleport.com/docs/changelog/#1600-061324). diff --git a/nixos/modules/services/databases/pgbouncer.nix b/nixos/modules/services/databases/pgbouncer.nix index 32538789fb17..7eafa502eb0c 100644 --- a/nixos/modules/services/databases/pgbouncer.nix +++ b/nixos/modules/services/databases/pgbouncer.nix @@ -1,543 +1,138 @@ -{ lib, pkgs, config, ... } : - -with lib; - +{ config, lib, utils, pkgs, ... }: let cfg = config.services.pgbouncer; - confFile = pkgs.writeTextFile { - name = "pgbouncer.ini"; - text = '' - [databases] - ${concatStringsSep "\n" - (mapAttrsToList (dbname : settings : "${dbname} = ${settings}") cfg.databases)} - - [users] - ${concatStringsSep "\n" - (mapAttrsToList (username : settings : "${username} = ${settings}") cfg.users)} - - [peers] - ${concatStringsSep "\n" - (mapAttrsToList (peerid : settings : "${peerid} = ${settings}") cfg.peers)} - - [pgbouncer] - # general - ${optionalString (cfg.ignoreStartupParameters != null) "ignore_startup_parameters = ${cfg.ignoreStartupParameters}"} - listen_port = ${toString cfg.listenPort} - ${optionalString (cfg.listenAddress != null) "listen_addr = ${cfg.listenAddress}"} - pool_mode = ${cfg.poolMode} - max_client_conn = ${toString cfg.maxClientConn} - default_pool_size = ${toString cfg.defaultPoolSize} - max_user_connections = ${toString cfg.maxUserConnections} - max_db_connections = ${toString cfg.maxDbConnections} - - #auth - auth_type = ${cfg.authType} - ${optionalString (cfg.authHbaFile != null) "auth_hba_file = ${cfg.authHbaFile}"} - ${optionalString (cfg.authFile != null) "auth_file = ${cfg.authFile}"} - ${optionalString (cfg.authUser != null) "auth_user = ${cfg.authUser}"} - ${optionalString (cfg.authQuery != null) "auth_query = ${cfg.authQuery}"} - ${optionalString (cfg.authDbname != null) "auth_dbname = ${cfg.authDbname}"} - - # TLS - ${optionalString (cfg.tls.client != null) '' - client_tls_sslmode = ${cfg.tls.client.sslmode} - client_tls_key_file = ${cfg.tls.client.keyFile} - client_tls_cert_file = ${cfg.tls.client.certFile} - client_tls_ca_file = ${cfg.tls.client.caFile} - ''} - ${optionalString (cfg.tls.server != null) '' - server_tls_sslmode = ${cfg.tls.server.sslmode} - server_tls_key_file = ${cfg.tls.server.keyFile} - server_tls_cert_file = ${cfg.tls.server.certFile} - server_tls_ca_file = ${cfg.tls.server.caFile} - ''} - - # log - ${optionalString (cfg.logFile != null) "logfile = ${cfg.homeDir}/${cfg.logFile}"} - ${optionalString (cfg.syslog != null) '' - syslog = ${if cfg.syslog.enable then "1" else "0"} - syslog_ident = ${cfg.syslog.syslogIdent} - syslog_facility = ${cfg.syslog.syslogFacility} - ''} - ${optionalString (cfg.verbose != null) "verbose = ${toString cfg.verbose}"} - - # console access - ${optionalString (cfg.adminUsers != null) "admin_users = ${cfg.adminUsers}"} - ${optionalString (cfg.statsUsers != null) "stats_users = ${cfg.statsUsers}"} - - # extra - ${cfg.extraConfig} - ''; - }; - -in { + settingsFormat = pkgs.formats.ini { }; + configFile = settingsFormat.generate "pgbouncer.ini" cfg.settings; + configPath = "pgbouncer/pgbouncer.ini"; +in +{ + imports = [ + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "logFile" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "log_file" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "listenAddress" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "listen_addr" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "listenPort" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "listen_port" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "poolMode" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "pool_mode" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "maxClientConn" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "max_client_conn" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "defaultPoolSize" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "default_pool_size" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "maxDbConnections" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "max_db_connections" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "maxUserConnections" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "max_user_connections" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "ignoreStartupParameters" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "ignore_startup_parameters" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "databases" ] + [ "services" "pgbouncer" "settings" "databases" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "users" ] + [ "services" "pgbouncer" "settings" "users" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "peers" ] + [ "services" "pgbouncer" "settings" "peers" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "authType" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "auth_type" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "authHbaFile" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "auth_hba_file" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "authFile" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "auth_file" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "authUser" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "auth_user" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "authQuery" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "auth_query" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "authDbname" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "auth_dbname" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "adminUsers" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "admin_users" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "statsUsers" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "stats_users" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "verbose" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "verbose" ]) + (lib.mkChangedOptionModule + [ "services" "pgbouncer" "syslog" "enable" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "syslog" ] + (config: + let + enable = lib.getAttrFromPath + [ "services" "pgbouncer" "syslog" "enable" ] + config; + in + if enable then 1 else 0)) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "syslog" "syslogIdent" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "syslog_ident" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "syslog" "syslogFacility" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "syslog_facility" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "tls" "client" "sslmode" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "client_tls_sslmode" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "tls" "client" "keyFile" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "client_tls_key_file" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "tls" "client" "certFile" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "client_tls_cert_file" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "tls" "client" "caFile" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "client_tls_ca_file" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "tls" "server" "sslmode" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "server_tls_sslmode" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "tls" "server" "keyFile" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "server_tls_key_file" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "tls" "server" "certFile" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "server_tls_cert_file" ]) + (lib.mkRenamedOptionModule + [ "services" "pgbouncer" "tls" "server" "caFile" ] + [ "services" "pgbouncer" "settings" "pgbouncer" "server_tls_ca_file" ]) + (lib.mkRemovedOptionModule [ "services" "pgbouncer" "extraConfig" ] "Use services.pgbouncer.settings instead.") + ]; options.services.pgbouncer = { + enable = lib.mkEnableOption "PostgreSQL connection pooler"; - # NixOS settings + package = lib.mkPackageOption pkgs "pgbouncer" { }; - enable = mkEnableOption "PostgreSQL connection pooler"; - - package = mkPackageOption pkgs "pgbouncer" { }; - - openFirewall = mkOption { - type = types.bool; + openFirewall = lib.mkOption { + type = lib.types.bool; default = false; description = '' Whether to automatically open the specified TCP port in the firewall. ''; }; - # Generic settings - - logFile = mkOption { - type = types.nullOr types.str; - default = null; + settings = lib.mkOption { + type = settingsFormat.type; + default = { }; description = '' - Specifies a log file in addition to journald. - ''; - }; - - listenAddress = mkOption { - type = types.nullOr types.commas; - example = "*"; - default = null; - description = '' - Specifies a list (comma-separated) of addresses where to listen for TCP connections. - You may also use * meaning “listen on all addresses”. - When not set, only Unix socket connections are accepted. - - Addresses can be specified numerically (IPv4/IPv6) or by name. - ''; - }; - - listenPort = mkOption { - type = types.port; - default = 6432; - description = '' - Which port to listen on. Applies to both TCP and Unix sockets. - ''; - }; - - poolMode = mkOption { - type = types.enum [ "session" "transaction" "statement" ]; - default = "session"; - description = '' - Specifies when a server connection can be reused by other clients. - - session - Server is released back to pool after client disconnects. Default. - transaction - Server is released back to pool after transaction finishes. - statement - Server is released back to pool after query finishes. - Transactions spanning multiple statements are disallowed in this mode. - ''; - }; - - maxClientConn = mkOption { - type = types.int; - default = 100; - description = '' - Maximum number of client connections allowed. - - When this setting is increased, then the file descriptor limits in the operating system - might also have to be increased. Note that the number of file descriptors potentially - used is more than maxClientConn. If each user connects under its own user name to the server, - the theoretical maximum used is: - maxClientConn + (max pool_size * total databases * total users) - - If a database user is specified in the connection string (all users connect under the same user name), - the theoretical maximum is: - maxClientConn + (max pool_size * total databases) - - The theoretical maximum should never be reached, unless somebody deliberately crafts a special load for it. - Still, it means you should set the number of file descriptors to a safely high number. - ''; - }; - - defaultPoolSize = mkOption { - type = types.int; - default = 20; - description = '' - How many server connections to allow per user/database pair. - Can be overridden in the per-database configuration. - ''; - }; - - maxDbConnections = mkOption { - type = types.int; - default = 0; - description = '' - Do not allow more than this many server connections per database (regardless of user). - This considers the PgBouncer database that the client has connected to, - not the PostgreSQL database of the outgoing connection. - - This can also be set per database in the [databases] section. - - Note that when you hit the limit, closing a client connection to one pool will - not immediately allow a server connection to be established for another pool, - because the server connection for the first pool is still open. - Once the server connection closes (due to idle timeout), - a new server connection will immediately be opened for the waiting pool. - - 0 = unlimited - ''; - }; - - maxUserConnections = mkOption { - type = types.int; - default = 0; - description = '' - Do not allow more than this many server connections per user (regardless of database). - This considers the PgBouncer user that is associated with a pool, - which is either the user specified for the server connection - or in absence of that the user the client has connected as. - - This can also be set per user in the [users] section. - - Note that when you hit the limit, closing a client connection to one pool - will not immediately allow a server connection to be established for another pool, - because the server connection for the first pool is still open. - Once the server connection closes (due to idle timeout), a new server connection - will immediately be opened for the waiting pool. - - 0 = unlimited - ''; - }; - - ignoreStartupParameters = mkOption { - type = types.nullOr types.commas; - example = "extra_float_digits"; - default = null; - description = '' - By default, PgBouncer allows only parameters it can keep track of in startup packets: - client_encoding, datestyle, timezone and standard_conforming_strings. - - All others parameters will raise an error. - To allow others parameters, they can be specified here, so that PgBouncer knows that - they are handled by the admin and it can ignore them. - - If you need to specify multiple values, use a comma-separated list. - - IMPORTANT: When using prometheus-pgbouncer-exporter, you need: - extra_float_digits - - ''; - }; - - # Section [databases] - databases = mkOption { - type = types.attrsOf types.str; - default = {}; - example = { - exampledb = "host=/run/postgresql/ port=5432 auth_user=exampleuser dbname=exampledb sslmode=require"; - bardb = "host=localhost dbname=bazdb"; - foodb = "host=host1.example.com port=5432"; - }; - description = '' - Detailed information about PostgreSQL database definitions: - - ''; - }; - - # Section [users] - users = mkOption { - type = types.attrsOf types.str; - default = {}; - example = { - user1 = "pool_mode=session"; - }; - description = '' - Optional. - - Detailed information about PostgreSQL user definitions: - - ''; - }; - - # Section [peers] - peers = mkOption { - type = types.attrsOf types.str; - default = {}; - example = { - "1" = "host=host1.example.com"; - "2" = "host=/tmp/pgbouncer-2 port=5555"; - }; - description = '' - Optional. - - Detailed information about PostgreSQL database definitions: - - ''; - }; - - # Authentication settings - authType = mkOption { - type = types.enum [ "cert" "md5" "scram-sha-256" "plain" "trust" "any" "hba" "pam" ]; - default = "md5"; - description = '' - How to authenticate users. - - cert - Client must connect over TLS connection with a valid client certificate. - The user name is then taken from the CommonName field from the certificate. - md5 - Use MD5-based password check. This is the default authentication method. - authFile may contain both MD5-encrypted and plain-text passwords. - If md5 is configured and a user has a SCRAM secret, then SCRAM authentication is used automatically instead. - scram-sha-256 - Use password check with SCRAM-SHA-256. authFile has to contain SCRAM secrets or plain-text passwords. - plain - The clear-text password is sent over the wire. Deprecated. - trust - No authentication is done. The user name must still exist in authFile. - any - Like the trust method, but the user name given is ignored. - Requires that all databases are configured to log in as a specific user. - Additionally, the console database allows any user to log in as admin. - hba - The actual authentication type is loaded from authHbaFile. - This allows different authentication methods for different access paths, - for example: connections over Unix socket use the peer auth method, connections over TCP must use TLS. - pam - PAM is used to authenticate users, authFile is ignored. - This method is not compatible with databases using the authUser option. - The service name reported to PAM is “pgbouncer”. pam is not supported in the HBA configuration file. - ''; - }; - - authHbaFile = mkOption { - type = types.nullOr types.path; - default = null; - example = "/secrets/pgbouncer_hba"; - description = '' - HBA configuration file to use when authType is hba. - - See HBA file format details: - - ''; - }; - - authFile = mkOption { - type = types.nullOr types.path; - default = null; - example = "/secrets/pgbouncer_authfile"; - description = '' - The name of the file to load user names and passwords from. - - See section Authentication file format details: - - - Most authentication types require that either authFile or authUser be set; - otherwise there would be no users defined. - ''; - }; - - authUser = mkOption { - type = types.nullOr types.str; - default = null; - example = "pgbouncer"; - description = '' - If authUser is set, then any user not specified in authFile will be queried - through the authQuery query from pg_shadow in the database, using authUser. - The password of authUser will be taken from authFile. - (If the authUser does not require a password then it does not need to be defined in authFile.) - - Direct access to pg_shadow requires admin rights. - It's preferable to use a non-superuser that calls a SECURITY DEFINER function instead. - ''; - }; - - authQuery = mkOption { - type = types.nullOr types.str; - default = null; - example = "SELECT usename, passwd FROM pg_shadow WHERE usename=$1"; - description = '' - Query to load user's password from database. - - Direct access to pg_shadow requires admin rights. - It's preferable to use a non-superuser that calls a SECURITY DEFINER function instead. - - Note that the query is run inside the target database. - So if a function is used, it needs to be installed into each database. - ''; - }; - - authDbname = mkOption { - type = types.nullOr types.str; - default = null; - example = "authdb"; - description = '' - Database name in the [database] section to be used for authentication purposes. - This option can be either global or overriden in the connection string if this parameter is specified. - ''; - }; - - # TLS settings - tls.client = mkOption { - type = types.nullOr (types.submodule { - options = { - sslmode = mkOption { - type = types.enum [ "disable" "allow" "prefer" "require" "verify-ca" "verify-full" ]; - default = "disable"; - description = '' - TLS mode to use for connections from clients. - TLS connections are disabled by default. - - When enabled, tls.client.keyFile and tls.client.certFile - must be also configured to set up the key and certificate - PgBouncer uses to accept client connections. - - disable - Plain TCP. If client requests TLS, it's ignored. Default. - allow - If client requests TLS, it is used. If not, plain TCP is used. - If the client presents a client certificate, it is not validated. - prefer - Same as allow. - require - Client must use TLS. If not, the client connection is rejected. - If the client presents a client certificate, it is not validated. - verify-ca - Client must use TLS with valid client certificate. - verify-full - Same as verify-ca - ''; - }; - certFile = mkOption { - type = types.path; - example = "/secrets/pgbouncer.key"; - description = "Path to certificate for private key. Clients can validate it"; - }; - keyFile = mkOption { - type = types.path; - example = "/secrets/pgbouncer.crt"; - description = "Path to private key for PgBouncer to accept client connections"; - }; - caFile = mkOption { - type = types.path; - example = "/secrets/pgbouncer.crt"; - description = "Path to root certificate file to validate client certificates"; - }; - }; - }); - default = null; - description = '' - - ''; - }; - - tls.server = mkOption { - type = types.nullOr (types.submodule { - options = { - sslmode = mkOption { - type = types.enum [ "disable" "allow" "prefer" "require" "verify-ca" "verify-full" ]; - default = "disable"; - description = '' - TLS mode to use for connections to PostgreSQL servers. - TLS connections are disabled by default. - - disable - Plain TCP. TLS is not even requested from the server. Default. - allow - FIXME: if server rejects plain, try TLS? - prefer - TLS connection is always requested first from PostgreSQL. - If refused, the connection will be established over plain TCP. - Server certificate is not validated. - require - Connection must go over TLS. If server rejects it, plain TCP is not attempted. - Server certificate is not validated. - verify-ca - Connection must go over TLS and server certificate must be valid according to tls.server.caFile. - Server host name is not checked against certificate. - verify-full - Connection must go over TLS and server certificate must be valid according to tls.server.caFile. - Server host name must match certificate information. - ''; - }; - certFile = mkOption { - type = types.path; - example = "/secrets/pgbouncer_server.key"; - description = "Certificate for private key. PostgreSQL server can validate it."; - }; - keyFile = mkOption { - type = types.path; - example = "/secrets/pgbouncer_server.crt"; - description = "Private key for PgBouncer to authenticate against PostgreSQL server."; - }; - caFile = mkOption { - type = types.path; - example = "/secrets/pgbouncer_server.crt"; - description = "Root certificate file to validate PostgreSQL server certificates."; - }; - }; - }); - default = null; - description = '' - - ''; - }; - - # Log settings - syslog = mkOption { - type = types.nullOr (types.submodule { - options = { - enable = mkOption { - type = types.bool; - default = false; - description = '' - Toggles syslog on/off. - ''; - }; - syslogIdent = mkOption { - type = types.str; - default = "pgbouncer"; - description = '' - Under what name to send logs to syslog. - ''; - }; - syslogFacility = mkOption { - type = types.enum [ "auth" "authpriv" "daemon" "user" "local0" "local1" "local2" "local3" "local4" "local5" "local6" "local7" ]; - default = "daemon"; - description = '' - Under what facility to send logs to syslog. - ''; - }; - }; - }); - default = null; - description = '' - - ''; - }; - - verbose = lib.mkOption { - type = lib.types.int; - default = 0; - description = '' - Increase verbosity. Mirrors the “-v” switch on the command line. - ''; - }; - - # Console access control - adminUsers = mkOption { - type = types.nullOr types.commas; - default = null; - description = '' - Comma-separated list of database users that are allowed to connect and run all commands on the console. - Ignored when authType is any, in which case any user name is allowed in as admin. - ''; - }; - - statsUsers = mkOption { - type = types.nullOr types.commas; - default = null; - description = '' - Comma-separated list of database users that are allowed to connect and run read-only queries on the console. - That means all SHOW commands except SHOW FDS. + Configuration for PgBouncer, see + for supported values. ''; }; @@ -550,42 +145,32 @@ in { ''; }; - user = mkOption { - type = types.str; + user = lib.mkOption { + type = lib.types.str; default = "pgbouncer"; description = '' The user pgbouncer is run as. ''; }; - group = mkOption { - type = types.str; + group = lib.mkOption { + type = lib.types.str; default = "pgbouncer"; description = '' The group pgbouncer is run as. ''; }; - homeDir = mkOption { - type = types.path; + homeDir = lib.mkOption { + type = lib.types.path; default = "/var/lib/pgbouncer"; description = '' Specifies the home directory. ''; }; - - # Extra settings - extraConfig = mkOption { - type = types.lines; - description = '' - Any additional text to be appended to config.ini - . - ''; - default = ""; - }; }; - config = mkIf cfg.enable { + config = lib.mkIf cfg.enable { users.groups.${cfg.group} = { }; users.users.${cfg.user} = { description = "PgBouncer service user"; @@ -595,26 +180,36 @@ in { isSystemUser = true; }; + environment.etc.${configPath}.source = configFile; + + # Default to RuntimeDirectory instead of /tmp. + services.pgbouncer.settings.pgbouncer.unix_socket_dir = lib.mkDefault "/run/pgbouncer"; + systemd.services.pgbouncer = { description = "PgBouncer - PostgreSQL connection pooler"; - wants = [ "network-online.target" ] ++ lib.optional config.services.postgresql.enable "postgresql.service"; - after = [ "network-online.target" ] ++ lib.optional config.services.postgresql.enable "postgresql.service"; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; + reloadTriggers = [ configFile ]; serviceConfig = { - Type = "notify"; + Type = "notify-reload"; User = cfg.user; Group = cfg.group; - ExecStart = "${lib.getExe pkgs.pgbouncer} ${confFile}"; - ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID"; + ExecStart = utils.escapeSystemdExecArgs [ + (lib.getExe pkgs.pgbouncer) + "/etc/${configPath}" + ]; RuntimeDirectory = "pgbouncer"; LimitNOFILE = cfg.openFilesLimit; }; }; - networking.firewall.allowedTCPPorts = optional cfg.openFirewall cfg.listenPort; - + networking.firewall = lib.mkIf cfg.openFirewall { + allowedTCPPorts = [ + (cfg.settings.pgbouncer.listen_port or 6432) + ]; + }; }; - meta.maintainers = [ maintainers._1000101 ]; - + meta.maintainers = [ lib.maintainers._1000101 ]; } diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix index 723116d2547b..7b02e815cdbb 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters.nix @@ -367,18 +367,6 @@ in message = '' PgBouncer exporter needs either connectionStringFile or connectionString configured" ''; - } { - assertion = cfg.pgbouncer.enable -> ( - config.services.pgbouncer.ignoreStartupParameters != null && builtins.match ".*extra_float_digits.*" config.services.pgbouncer.ignoreStartupParameters != null - ); - message = '' - Prometheus PgBouncer exporter requires including `extra_float_digits` in services.pgbouncer.ignoreStartupParameters - - Example: - services.pgbouncer.ignoreStartupParameters = extra_float_digits; - - See https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration - ''; } { assertion = cfg.sql.enable -> ( (cfg.sql.configFile == null) != (cfg.sql.configuration == null) @@ -437,11 +425,6 @@ in config.services.prometheus.exporters.pgbouncer.connectionString is insecure. Use connectionStringFile instead. '' ) - (mkIf - (cfg.pgbouncer.enable && config.services.pgbouncer.authType != "any") '' - Admin user (with password or passwordless) MUST exist in the services.pgbouncer.authFile if authType other than any is used. - '' - ) ] ++ config.services.prometheus.exporters.warnings; }] ++ [(mkIf config.services.prometheus.exporters.rtl_433.enable { hardware.rtl-sdr.enable = mkDefault true; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix b/nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix index 71b602638632..9cd261099a95 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix @@ -4,15 +4,21 @@ let cfg = config.services.prometheus.exporters.pgbouncer; inherit (lib) mkOption + mkPackageOption types optionals + optionalString + getExe + getExe' escapeShellArg + escapeShellArgs concatStringsSep ; in { port = 9127; extraOpts = { + package = mkPackageOption pkgs "prometheus-pgbouncer-exporter" { }; telemetryPath = mkOption { type = types.str; @@ -31,8 +37,10 @@ in NOTE: You MUST keep pgbouncer as database name (special internal db)!!! - NOTE: Admin user (with password or passwordless) MUST exist - in the services.pgbouncer.authFile if authType other than any is used. + NOTE: ignore_startup_parameters MUST contain "extra_float_digits". + + NOTE: Admin user (with password or passwordless) MUST exist in the + auth_file if auth_type other than "any" is used. WARNING: this secret is stored in the world-readable Nix store! Use {option}`connectionStringFile` instead. @@ -49,8 +57,10 @@ in NOTE: You MUST keep pgbouncer as database name (special internal db)!!! - NOTE: Admin user (with password or passwordless) MUST exist - in the services.pgbouncer.authFile if authType other than any is used. + NOTE: ignore_startup_parameters MUST contain "extra_float_digits". + + NOTE: Admin user (with password or passwordless) MUST exist in the + auth_file if auth_type other than "any" is used. {option}`connectionStringFile` takes precedence over {option}`connectionString` ''; @@ -81,7 +91,7 @@ in }; logLevel = mkOption { - type = types.enum ["debug" "info" "warn" "error" ]; + type = types.enum [ "debug" "info" "warn" "error" ]; default = "info"; description = '' Only log messages with the given severity or above. @@ -89,7 +99,7 @@ in }; logFormat = mkOption { - type = types.enum ["logfmt" "json"]; + type = types.enum [ "logfmt" "json" ]; default = "logfmt"; description = '' Output format of log messages. One of: [logfmt, json] @@ -116,35 +126,30 @@ in serviceOpts = { after = [ "pgbouncer.service" ]; - serviceConfig = let - startScript = pkgs.writeShellScriptBin "pgbouncer-start" "${concatStringsSep " " ([ - "${pkgs.prometheus-pgbouncer-exporter}/bin/pgbouncer_exporter" - "--web.listen-address ${cfg.listenAddress}:${toString cfg.port}" - "--pgBouncer.connectionString ${if cfg.connectionStringFile != null then - "$(head -n1 ${cfg.connectionStringFile})" else "${escapeShellArg cfg.connectionString}"}" - ] - ++ optionals (cfg.telemetryPath != null) [ - "--web.telemetry-path ${escapeShellArg cfg.telemetryPath}" - ] - ++ optionals (cfg.pidFile != null) [ - "--pgBouncer.pid-file= ${escapeShellArg cfg.pidFile}" - ] - ++ optionals (cfg.logLevel != null) [ - "--log.level ${escapeShellArg cfg.logLevel}" - ] - ++ optionals (cfg.logFormat != null) [ - "--log.format ${escapeShellArg cfg.logFormat}" - ] - ++ optionals (cfg.webSystemdSocket != false) [ - "--web.systemd-socket ${escapeShellArg cfg.webSystemdSocket}" - ] - ++ optionals (cfg.webConfigFile != null) [ - "--web.config.file ${escapeShellArg cfg.webConfigFile}" - ] - ++ cfg.extraFlags)}"; - in - { - ExecStart = "${startScript}/bin/pgbouncer-start"; - }; + script = optionalString (cfg.connectionStringFile != null) '' + connectionString=$(${escapeShellArgs [ + (getExe' pkgs.coreutils "cat") "--" cfg.connectionStringFile + ]}) + '' + concatStringsSep " " ([ + "exec -- ${escapeShellArg (getExe cfg.package)}" + "--web.listen-address ${cfg.listenAddress}:${toString cfg.port}" + "--pgBouncer.connectionString ${if cfg.connectionStringFile != null + then "\"$connectionString\"" + else "${escapeShellArg cfg.connectionString}"}" + ] ++ optionals (cfg.telemetryPath != null) [ + "--web.telemetry-path ${escapeShellArg cfg.telemetryPath}" + ] ++ optionals (cfg.pidFile != null) [ + "--pgBouncer.pid-file ${escapeShellArg cfg.pidFile}" + ] ++ optionals (cfg.logLevel != null) [ + "--log.level ${escapeShellArg cfg.logLevel}" + ] ++ optionals (cfg.logFormat != null) [ + "--log.format ${escapeShellArg cfg.logFormat}" + ] ++ optionals (cfg.webSystemdSocket != false) [ + "--web.systemd-socket ${escapeShellArg cfg.webSystemdSocket}" + ] ++ optionals (cfg.webConfigFile != null) [ + "--web.config.file ${escapeShellArg cfg.webConfigFile}" + ] ++ cfg.extraFlags); + + serviceConfig.RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; }; } diff --git a/nixos/tests/pgbouncer.nix b/nixos/tests/pgbouncer.nix index bb5afd35ee28..8d11c4b3f4bf 100644 --- a/nixos/tests/pgbouncer.nix +++ b/nixos/tests/pgbouncer.nix @@ -1,20 +1,12 @@ -import ./make-test-python.nix ({ pkgs, ... } : -let - testAuthFile = pkgs.writeTextFile { - name = "authFile"; - text = '' - "testuser" "testpass" - ''; - }; -in -{ +import ./make-test-python.nix ({ lib, pkgs, ... }: { name = "pgbouncer"; - meta = with pkgs.lib.maintainers; { + + meta = with lib.maintainers; { maintainers = [ _1000101 ]; }; - nodes = { - one = { config, pkgs, ... }: { + nodes = { + one = { pkgs, ... }: { systemd.services.postgresql = { postStart = '' ${pkgs.postgresql}/bin/psql -U postgres -c "ALTER ROLE testuser WITH LOGIN PASSWORD 'testpass'"; @@ -26,10 +18,7 @@ in postgresql = { enable = true; ensureDatabases = [ "testdb" ]; - ensureUsers = [ - { - name = "testuser"; - }]; + ensureUsers = [{ name = "testuser"; }]; authentication = '' local testdb testuser scram-sha-256 ''; @@ -37,10 +26,19 @@ in pgbouncer = { enable = true; - listenAddress = "localhost"; - databases = { test = "host=/run/postgresql/ port=5432 auth_user=testuser dbname=testdb"; }; - authType = "scram-sha-256"; - authFile = testAuthFile; + openFirewall = true; + settings = { + pgbouncer = { + listen_addr = "localhost"; + auth_type = "scram-sha-256"; + auth_file = builtins.toFile "pgbouncer-users.txt" '' + "testuser" "testpass" + ''; + }; + databases = { + test = "host=/run/postgresql port=5432 auth_user=testuser dbname=testdb"; + }; + }; }; }; }; diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix index fba6bd57d9c4..6a4abab7cc12 100644 --- a/nixos/tests/prometheus-exporters.nix +++ b/nixos/tests/prometheus-exporters.nix @@ -1001,13 +1001,24 @@ let metricProvider = { services.postgresql.enable = true; services.pgbouncer = { - # https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration - ignoreStartupParameters = "extra_float_digits"; enable = true; - listenAddress = "*"; - databases = { postgres = "host=/run/postgresql/ port=5432 auth_user=postgres dbname=postgres"; }; - authType = "any"; - maxClientConn = 99; + settings = { + pgbouncer = { + listen_addr = "*"; + auth_type = "any"; + max_client_conn = 99; + # https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration + ignore_startup_parameters = "extra_float_digits"; + }; + databases = { + postgres = concatStringsSep " " [ + "host=/run/postgresql" + "port=5432" + "auth_user=postgres" + "dbname=postgres" + ]; + }; + }; }; }; exporterTest = ''