at 21.11-pre 15 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.postgresql; 8 9 postgresql = 10 if cfg.extraPlugins == [] 11 then cfg.package 12 else cfg.package.withPackages (_: cfg.extraPlugins); 13 14 toStr = value: 15 if true == value then "yes" 16 else if false == value then "no" 17 else if isString value then "'${lib.replaceStrings ["'"] ["''"] value}'" 18 else toString value; 19 20 # The main PostgreSQL configuration file. 21 configFile = pkgs.writeTextDir "postgresql.conf" (concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings)); 22 23 configFileCheck = pkgs.runCommand "postgresql-configfile-check" {} '' 24 ${cfg.package}/bin/postgres -D${configFile} -C config_file >/dev/null 25 touch $out 26 ''; 27 28 groupAccessAvailable = versionAtLeast postgresql.version "11.0"; 29 30in 31 32{ 33 imports = [ 34 (mkRemovedOptionModule [ "services" "postgresql" "extraConfig" ] "Use services.postgresql.settings instead.") 35 ]; 36 37 ###### interface 38 39 options = { 40 41 services.postgresql = { 42 43 enable = mkEnableOption "PostgreSQL Server"; 44 45 package = mkOption { 46 type = types.package; 47 example = literalExample "pkgs.postgresql_11"; 48 description = '' 49 PostgreSQL package to use. 50 ''; 51 }; 52 53 port = mkOption { 54 type = types.int; 55 default = 5432; 56 description = '' 57 The port on which PostgreSQL listens. 58 ''; 59 }; 60 61 checkConfig = mkOption { 62 type = types.bool; 63 default = true; 64 description = "Check the syntax of the configuration file at compile time"; 65 }; 66 67 dataDir = mkOption { 68 type = types.path; 69 defaultText = "/var/lib/postgresql/\${config.services.postgresql.package.psqlSchema}"; 70 example = "/var/lib/postgresql/11"; 71 description = '' 72 The data directory for PostgreSQL. If left as the default value 73 this directory will automatically be created before the PostgreSQL server starts, otherwise 74 the sysadmin is responsible for ensuring the directory exists with appropriate ownership 75 and permissions. 76 ''; 77 }; 78 79 authentication = mkOption { 80 type = types.lines; 81 default = ""; 82 description = '' 83 Defines how users authenticate themselves to the server. See the 84 <link xlink:href="https://www.postgresql.org/docs/current/auth-pg-hba-conf.html"> 85 PostgreSQL documentation for pg_hba.conf</link> 86 for details on the expected format of this option. By default, 87 peer based authentication will be used for users connecting 88 via the Unix socket, and md5 password authentication will be 89 used for users connecting via TCP. Any added rules will be 90 inserted above the default rules. If you'd like to replace the 91 default rules entirely, you can use <function>lib.mkForce</function> in your 92 module. 93 ''; 94 }; 95 96 identMap = mkOption { 97 type = types.lines; 98 default = ""; 99 description = '' 100 Defines the mapping from system users to database users. 101 102 The general form is: 103 104 map-name system-username database-username 105 ''; 106 }; 107 108 initdbArgs = mkOption { 109 type = with types; listOf str; 110 default = []; 111 example = [ "--data-checksums" "--allow-group-access" ]; 112 description = '' 113 Additional arguments passed to <literal>initdb</literal> during data dir 114 initialisation. 115 ''; 116 }; 117 118 initialScript = mkOption { 119 type = types.nullOr types.path; 120 default = null; 121 description = '' 122 A file containing SQL statements to execute on first startup. 123 ''; 124 }; 125 126 ensureDatabases = mkOption { 127 type = types.listOf types.str; 128 default = []; 129 description = '' 130 Ensures that the specified databases exist. 131 This option will never delete existing databases, especially not when the value of this 132 option is changed. This means that databases created once through this option or 133 otherwise have to be removed manually. 134 ''; 135 example = [ 136 "gitea" 137 "nextcloud" 138 ]; 139 }; 140 141 ensureUsers = mkOption { 142 type = types.listOf (types.submodule { 143 options = { 144 name = mkOption { 145 type = types.str; 146 description = '' 147 Name of the user to ensure. 148 ''; 149 }; 150 ensurePermissions = mkOption { 151 type = types.attrsOf types.str; 152 default = {}; 153 description = '' 154 Permissions to ensure for the user, specified as an attribute set. 155 The attribute names specify the database and tables to grant the permissions for. 156 The attribute values specify the permissions to grant. You may specify one or 157 multiple comma-separated SQL privileges here. 158 159 For more information on how to specify the target 160 and on which privileges exist, see the 161 <link xlink:href="https://www.postgresql.org/docs/current/sql-grant.html">GRANT syntax</link>. 162 The attributes are used as <code>GRANT ''${attrValue} ON ''${attrName}</code>. 163 ''; 164 example = literalExample '' 165 { 166 "DATABASE \"nextcloud\"" = "ALL PRIVILEGES"; 167 "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES"; 168 } 169 ''; 170 }; 171 }; 172 }); 173 default = []; 174 description = '' 175 Ensures that the specified users exist and have at least the ensured permissions. 176 The PostgreSQL users will be identified using peer authentication. This authenticates the Unix user with the 177 same name only, and that without the need for a password. 178 This option will never delete existing users or remove permissions, especially not when the value of this 179 option is changed. This means that users created and permissions assigned once through this option or 180 otherwise have to be removed manually. 181 ''; 182 example = literalExample '' 183 [ 184 { 185 name = "nextcloud"; 186 ensurePermissions = { 187 "DATABASE nextcloud" = "ALL PRIVILEGES"; 188 }; 189 } 190 { 191 name = "superuser"; 192 ensurePermissions = { 193 "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES"; 194 }; 195 } 196 ] 197 ''; 198 }; 199 200 enableTCPIP = mkOption { 201 type = types.bool; 202 default = false; 203 description = '' 204 Whether PostgreSQL should listen on all network interfaces. 205 If disabled, the database can only be accessed via its Unix 206 domain socket or via TCP connections to localhost. 207 ''; 208 }; 209 210 logLinePrefix = mkOption { 211 type = types.str; 212 default = "[%p] "; 213 example = "%m [%p] "; 214 description = '' 215 A printf-style string that is output at the beginning of each log line. 216 Upstream default is <literal>'%m [%p] '</literal>, i.e. it includes the timestamp. We do 217 not include the timestamp, because journal has it anyway. 218 ''; 219 }; 220 221 extraPlugins = mkOption { 222 type = types.listOf types.path; 223 default = []; 224 example = literalExample "with pkgs.postgresql_11.pkgs; [ postgis pg_repack ]"; 225 description = '' 226 List of PostgreSQL plugins. PostgreSQL version for each plugin should 227 match version for <literal>services.postgresql.package</literal> value. 228 ''; 229 }; 230 231 settings = mkOption { 232 type = with types; attrsOf (oneOf [ bool float int str ]); 233 default = {}; 234 description = '' 235 PostgreSQL configuration. Refer to 236 <link xlink:href="https://www.postgresql.org/docs/11/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE"/> 237 for an overview of <literal>postgresql.conf</literal>. 238 239 <note><para> 240 String values will automatically be enclosed in single quotes. Single quotes will be 241 escaped with two single quotes as described by the upstream documentation linked above. 242 </para></note> 243 ''; 244 example = literalExample '' 245 { 246 log_connections = true; 247 log_statement = "all"; 248 logging_collector = true 249 log_disconnections = true 250 log_destination = lib.mkForce "syslog"; 251 } 252 ''; 253 }; 254 255 recoveryConfig = mkOption { 256 type = types.nullOr types.lines; 257 default = null; 258 description = '' 259 Contents of the <filename>recovery.conf</filename> file. 260 ''; 261 }; 262 263 superUser = mkOption { 264 type = types.str; 265 default = "postgres"; 266 internal = true; 267 readOnly = true; 268 description = '' 269 PostgreSQL superuser account to use for various operations. Internal since changing 270 this value would lead to breakage while setting up databases. 271 ''; 272 }; 273 }; 274 275 }; 276 277 278 ###### implementation 279 280 config = mkIf cfg.enable { 281 282 services.postgresql.settings = 283 { 284 hba_file = "${pkgs.writeText "pg_hba.conf" cfg.authentication}"; 285 ident_file = "${pkgs.writeText "pg_ident.conf" cfg.identMap}"; 286 log_destination = "stderr"; 287 log_line_prefix = cfg.logLinePrefix; 288 listen_addresses = if cfg.enableTCPIP then "*" else "localhost"; 289 port = cfg.port; 290 }; 291 292 services.postgresql.package = 293 # Note: when changing the default, make it conditional on 294 # ‘system.stateVersion’ to maintain compatibility with existing 295 # systems! 296 mkDefault (if versionAtLeast config.system.stateVersion "20.03" then pkgs.postgresql_11 297 else if versionAtLeast config.system.stateVersion "17.09" then pkgs.postgresql_9_6 298 else throw "postgresql_9_5 was removed, please upgrade your postgresql version."); 299 300 services.postgresql.dataDir = mkDefault "/var/lib/postgresql/${cfg.package.psqlSchema}"; 301 302 services.postgresql.authentication = mkAfter 303 '' 304 # Generated file; do not edit! 305 local all all peer 306 host all all 127.0.0.1/32 md5 307 host all all ::1/128 md5 308 ''; 309 310 users.users.postgres = 311 { name = "postgres"; 312 uid = config.ids.uids.postgres; 313 group = "postgres"; 314 description = "PostgreSQL server user"; 315 home = "${cfg.dataDir}"; 316 useDefaultShell = true; 317 }; 318 319 users.groups.postgres.gid = config.ids.gids.postgres; 320 321 environment.systemPackages = [ postgresql ]; 322 323 environment.pathsToLink = [ 324 "/share/postgresql" 325 ]; 326 327 system.extraDependencies = lib.optional (cfg.checkConfig && pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) configFileCheck; 328 329 systemd.services.postgresql = 330 { description = "PostgreSQL Server"; 331 332 wantedBy = [ "multi-user.target" ]; 333 after = [ "network.target" ]; 334 335 environment.PGDATA = cfg.dataDir; 336 337 path = [ postgresql ]; 338 339 preStart = 340 '' 341 if ! test -e ${cfg.dataDir}/PG_VERSION; then 342 # Cleanup the data directory. 343 rm -f ${cfg.dataDir}/*.conf 344 345 # Initialise the database. 346 initdb -U ${cfg.superUser} ${concatStringsSep " " cfg.initdbArgs} 347 348 # See postStart! 349 touch "${cfg.dataDir}/.first_startup" 350 fi 351 352 ln -sfn "${configFile}/postgresql.conf" "${cfg.dataDir}/postgresql.conf" 353 ${optionalString (cfg.recoveryConfig != null) '' 354 ln -sfn "${pkgs.writeText "recovery.conf" cfg.recoveryConfig}" \ 355 "${cfg.dataDir}/recovery.conf" 356 ''} 357 ''; 358 359 # Wait for PostgreSQL to be ready to accept connections. 360 postStart = 361 '' 362 PSQL="psql --port=${toString cfg.port}" 363 364 while ! $PSQL -d postgres -c "" 2> /dev/null; do 365 if ! kill -0 "$MAINPID"; then exit 1; fi 366 sleep 0.1 367 done 368 369 if test -e "${cfg.dataDir}/.first_startup"; then 370 ${optionalString (cfg.initialScript != null) '' 371 $PSQL -f "${cfg.initialScript}" -d postgres 372 ''} 373 rm -f "${cfg.dataDir}/.first_startup" 374 fi 375 '' + optionalString (cfg.ensureDatabases != []) '' 376 ${concatMapStrings (database: '' 377 $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${database}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${database}"' 378 '') cfg.ensureDatabases} 379 '' + '' 380 ${concatMapStrings (user: '' 381 $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"' 382 ${concatStringsSep "\n" (mapAttrsToList (database: permission: '' 383 $PSQL -tAc 'GRANT ${permission} ON ${database} TO "${user.name}"' 384 '') user.ensurePermissions)} 385 '') cfg.ensureUsers} 386 ''; 387 388 serviceConfig = mkMerge [ 389 { ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 390 User = "postgres"; 391 Group = "postgres"; 392 RuntimeDirectory = "postgresql"; 393 Type = if versionAtLeast cfg.package.version "9.6" 394 then "notify" 395 else "simple"; 396 397 # Shut down Postgres using SIGINT ("Fast Shutdown mode"). See 398 # http://www.postgresql.org/docs/current/static/server-shutdown.html 399 KillSignal = "SIGINT"; 400 KillMode = "mixed"; 401 402 # Give Postgres a decent amount of time to clean up after 403 # receiving systemd's SIGINT. 404 TimeoutSec = 120; 405 406 ExecStart = "${postgresql}/bin/postgres"; 407 } 408 (mkIf (cfg.dataDir == "/var/lib/postgresql/${cfg.package.psqlSchema}") { 409 StateDirectory = "postgresql postgresql/${cfg.package.psqlSchema}"; 410 StateDirectoryMode = if groupAccessAvailable then "0750" else "0700"; 411 }) 412 ]; 413 414 unitConfig.RequiresMountsFor = "${cfg.dataDir}"; 415 }; 416 417 }; 418 419 meta.doc = ./postgresql.xml; 420 meta.maintainers = with lib.maintainers; [ thoughtpolice danbst ]; 421}