at 25.11-pre 33 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 inherit (lib) 10 any 11 attrValues 12 concatMapStrings 13 concatStringsSep 14 const 15 elem 16 escapeShellArgs 17 filter 18 filterAttrs 19 getAttr 20 getName 21 hasPrefix 22 isString 23 literalExpression 24 mapAttrs 25 mapAttrsToList 26 mkAfter 27 mkBefore 28 mkDefault 29 mkEnableOption 30 mkIf 31 mkMerge 32 mkOption 33 mkPackageOption 34 mkRemovedOptionModule 35 mkRenamedOptionModule 36 optionalString 37 pipe 38 sortProperties 39 types 40 versionAtLeast 41 warn 42 ; 43 44 cfg = config.services.postgresql; 45 46 # ensure that 47 # services.postgresql = { 48 # enableJIT = true; 49 # package = pkgs.postgresql_<major>; 50 # }; 51 # works. 52 basePackage = if cfg.enableJIT then cfg.package.withJIT else cfg.package.withoutJIT; 53 54 postgresql = if cfg.extensions == [ ] then basePackage else basePackage.withPackages cfg.extensions; 55 56 toStr = 57 value: 58 if true == value then 59 "yes" 60 else if false == value then 61 "no" 62 else if isString value then 63 "'${lib.replaceStrings [ "'" ] [ "''" ] value}'" 64 else 65 builtins.toString value; 66 67 # The main PostgreSQL configuration file. 68 configFile = pkgs.writeTextDir "postgresql.conf" ( 69 concatStringsSep "\n" ( 70 mapAttrsToList (n: v: "${n} = ${toStr v}") (filterAttrs (const (x: x != null)) cfg.settings) 71 ) 72 ); 73 74 configFileCheck = pkgs.runCommand "postgresql-configfile-check" { } '' 75 ${cfg.package}/bin/postgres -D${configFile} -C config_file >/dev/null 76 touch $out 77 ''; 78 79 groupAccessAvailable = versionAtLeast cfg.finalPackage.version "11.0"; 80 81 extensionNames = map getName postgresql.installedExtensions; 82 extensionInstalled = extension: elem extension extensionNames; 83in 84 85{ 86 imports = [ 87 (mkRemovedOptionModule [ 88 "services" 89 "postgresql" 90 "extraConfig" 91 ] "Use services.postgresql.settings instead.") 92 93 (mkRemovedOptionModule [ 94 "services" 95 "postgresql" 96 "recoveryConfig" 97 ] "PostgreSQL v12+ doesn't support recovery.conf.") 98 99 (mkRenamedOptionModule 100 [ "services" "postgresql" "logLinePrefix" ] 101 [ "services" "postgresql" "settings" "log_line_prefix" ] 102 ) 103 (mkRenamedOptionModule 104 [ "services" "postgresql" "port" ] 105 [ "services" "postgresql" "settings" "port" ] 106 ) 107 (mkRenamedOptionModule 108 [ "services" "postgresql" "extraPlugins" ] 109 [ "services" "postgresql" "extensions" ] 110 ) 111 ]; 112 113 ###### interface 114 115 options = { 116 117 services.postgresql = { 118 119 enable = mkEnableOption "PostgreSQL Server"; 120 121 enableJIT = mkEnableOption "JIT support"; 122 123 package = mkPackageOption pkgs "postgresql" { 124 example = "postgresql_15"; 125 }; 126 127 finalPackage = mkOption { 128 type = types.package; 129 readOnly = true; 130 default = postgresql; 131 defaultText = "with config.services.postgresql; package.withPackages extensions"; 132 description = '' 133 The postgresql package that will effectively be used in the system. 134 It consists of the base package with plugins applied to it. 135 ''; 136 }; 137 138 systemCallFilter = mkOption { 139 type = types.attrsOf ( 140 types.coercedTo types.bool (enable: { inherit enable; }) ( 141 types.submodule ( 142 { name, ... }: 143 { 144 options = { 145 enable = mkEnableOption "${name} in postgresql's syscall filter"; 146 priority = mkOption { 147 default = 148 if hasPrefix "@" name then 149 500 150 else if hasPrefix "~@" name then 151 1000 152 else 153 1500; 154 defaultText = literalExpression '' 155 if hasPrefix "@" name then 500 else if hasPrefix "~@" name then 1000 else 1500 156 ''; 157 type = types.int; 158 description = '' 159 Set the priority of the system call filter setting. Later declarations 160 override earlier ones, e.g. 161 162 ```ini 163 [Service] 164 SystemCallFilter=~read write 165 SystemCallFilter=write 166 ``` 167 168 results in a service where _only_ `read` is not allowed. 169 170 The ordering in the unit file is controlled by this option: the higher 171 the number, the later it will be added to the filterset. 172 173 By default, depending on the prefix a priority is assigned: usually, call-groups 174 (starting with `@`) are used to allow/deny a larger set of syscalls and later 175 on single syscalls are configured for exceptions. Hence, syscall groups 176 and negative groups are placed before individual syscalls by default. 177 ''; 178 }; 179 }; 180 } 181 ) 182 ) 183 ); 184 defaultText = literalExpression '' 185 { 186 "@system-service" = true; 187 "~@privileged" = true; 188 "~@resources" = true; 189 } 190 ''; 191 description = '' 192 Configures the syscall filter for `postgresql.service`. The keys are 193 declarations for `SystemCallFilter` as described in {manpage}`systemd.exec(5)`. 194 195 The value is a boolean: `true` adds the attribute name to the syscall filter-set, 196 `false` doesn't. This is done to allow downstream configurations to turn off 197 restrictions made here. E.g. with 198 199 ```nix 200 { 201 services.postgresql.systemCallFilter."~@resources" = false; 202 } 203 ``` 204 205 it's possible to remove the restriction on `@resources` (keep in mind that 206 `@system-service` implies `@resources`). 207 208 As described in the section for [](#opt-services.postgresql.systemCallFilter._name_.priority), 209 the ordering matters. Hence, it's also possible to specify customizations with 210 211 ```nix 212 { 213 services.postgresql.systemCallFilter = { 214 "foobar" = { enable = true; priority = 23; }; 215 }; 216 } 217 ``` 218 219 [](#opt-services.postgresql.systemCallFilter._name_.enable) is the flag whether 220 or not it will be added to the `SystemCallFilter` of `postgresql.service`. 221 222 Settings with a higher priority are added after filter settings with a lower 223 priority. Hence, syscall groups with a higher priority can discard declarations 224 with a lower priority. 225 226 By default, syscall groups (i.e. attribute names starting with `@`) are added 227 _before_ negated groups (i.e. `~@` as prefix) _before_ syscall names 228 and negations. 229 ''; 230 }; 231 232 checkConfig = mkOption { 233 type = types.bool; 234 default = true; 235 description = "Check the syntax of the configuration file at compile time"; 236 }; 237 238 dataDir = mkOption { 239 type = types.path; 240 defaultText = literalExpression ''"/var/lib/postgresql/''${config.services.postgresql.package.psqlSchema}"''; 241 example = "/var/lib/postgresql/15"; 242 description = '' 243 The data directory for PostgreSQL. If left as the default value 244 this directory will automatically be created before the PostgreSQL server starts, otherwise 245 the sysadmin is responsible for ensuring the directory exists with appropriate ownership 246 and permissions. 247 ''; 248 }; 249 250 authentication = mkOption { 251 type = types.lines; 252 default = ""; 253 description = '' 254 Defines how users authenticate themselves to the server. See the 255 [PostgreSQL documentation for pg_hba.conf](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html) 256 for details on the expected format of this option. By default, 257 peer based authentication will be used for users connecting 258 via the Unix socket, and md5 password authentication will be 259 used for users connecting via TCP. Any added rules will be 260 inserted above the default rules. If you'd like to replace the 261 default rules entirely, you can use `lib.mkForce` in your 262 module. 263 ''; 264 }; 265 266 identMap = mkOption { 267 type = types.lines; 268 default = ""; 269 example = '' 270 map-name-0 system-username-0 database-username-0 271 map-name-1 system-username-1 database-username-1 272 ''; 273 description = '' 274 Defines the mapping from system users to database users. 275 276 See the [auth doc](https://postgresql.org/docs/current/auth-username-maps.html). 277 278 There is a default map "postgres" which is used for local peer authentication 279 as the postgres superuser role. 280 For example, to allow the root user to login as the postgres superuser, add: 281 282 ``` 283 postgres root postgres 284 ``` 285 ''; 286 }; 287 288 initdbArgs = mkOption { 289 type = with types; listOf str; 290 default = [ ]; 291 example = [ 292 "--data-checksums" 293 "--allow-group-access" 294 ]; 295 description = '' 296 Additional arguments passed to `initdb` during data dir 297 initialisation. 298 ''; 299 }; 300 301 initialScript = mkOption { 302 type = types.nullOr types.path; 303 default = null; 304 example = literalExpression '' 305 pkgs.writeText "init-sql-script" ''' 306 alter user postgres with password 'myPassword'; 307 ''';''; 308 309 description = '' 310 A file containing SQL statements to execute on first startup. 311 ''; 312 }; 313 314 ensureDatabases = mkOption { 315 type = types.listOf types.str; 316 default = [ ]; 317 description = '' 318 Ensures that the specified databases exist. 319 This option will never delete existing databases, especially not when the value of this 320 option is changed. This means that databases created once through this option or 321 otherwise have to be removed manually. 322 ''; 323 example = [ 324 "gitea" 325 "nextcloud" 326 ]; 327 }; 328 329 ensureUsers = mkOption { 330 type = types.listOf ( 331 types.submodule { 332 options = { 333 name = mkOption { 334 type = types.str; 335 description = '' 336 Name of the user to ensure. 337 ''; 338 }; 339 340 ensureDBOwnership = mkOption { 341 type = types.bool; 342 default = false; 343 description = '' 344 Grants the user ownership to a database with the same name. 345 This database must be defined manually in 346 [](#opt-services.postgresql.ensureDatabases). 347 ''; 348 }; 349 350 ensureClauses = mkOption { 351 description = '' 352 An attrset of clauses to grant to the user. Under the hood this uses the 353 [ALTER USER syntax](https://www.postgresql.org/docs/current/sql-alteruser.html) for each attrName where 354 the attrValue is true in the attrSet: 355 `ALTER USER user.name WITH attrName` 356 ''; 357 example = literalExpression '' 358 { 359 superuser = true; 360 createrole = true; 361 createdb = true; 362 } 363 ''; 364 default = { }; 365 defaultText = lib.literalMD '' 366 The default, `null`, means that the user created will have the default permissions assigned by PostgreSQL. Subsequent server starts will not set or unset the clause, so imperative changes are preserved. 367 ''; 368 type = types.submodule { 369 options = 370 let 371 defaultText = lib.literalMD '' 372 `null`: do not set. For newly created roles, use PostgreSQL's default. For existing roles, do not touch this clause. 373 ''; 374 in 375 { 376 superuser = mkOption { 377 type = types.nullOr types.bool; 378 description = '' 379 Grants the user, created by the ensureUser attr, superuser permissions. From the postgres docs: 380 381 A database superuser bypasses all permission checks, 382 except the right to log in. This is a dangerous privilege 383 and should not be used carelessly; it is best to do most 384 of your work as a role that is not a superuser. To create 385 a new database superuser, use CREATE ROLE name SUPERUSER. 386 You must do this as a role that is already a superuser. 387 388 More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html) 389 ''; 390 default = null; 391 inherit defaultText; 392 }; 393 createrole = mkOption { 394 type = types.nullOr types.bool; 395 description = '' 396 Grants the user, created by the ensureUser attr, createrole permissions. From the postgres docs: 397 398 A role must be explicitly given permission to create more 399 roles (except for superusers, since those bypass all 400 permission checks). To create such a role, use CREATE 401 ROLE name CREATEROLE. A role with CREATEROLE privilege 402 can alter and drop other roles, too, as well as grant or 403 revoke membership in them. However, to create, alter, 404 drop, or change membership of a superuser role, superuser 405 status is required; CREATEROLE is insufficient for that. 406 407 More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html) 408 ''; 409 default = null; 410 inherit defaultText; 411 }; 412 createdb = mkOption { 413 type = types.nullOr types.bool; 414 description = '' 415 Grants the user, created by the ensureUser attr, createdb permissions. From the postgres docs: 416 417 A role must be explicitly given permission to create 418 databases (except for superusers, since those bypass all 419 permission checks). To create such a role, use CREATE 420 ROLE name CREATEDB. 421 422 More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html) 423 ''; 424 default = null; 425 inherit defaultText; 426 }; 427 "inherit" = mkOption { 428 type = types.nullOr types.bool; 429 description = '' 430 Grants the user created inherit permissions. From the postgres docs: 431 432 A role is given permission to inherit the privileges of 433 roles it is a member of, by default. However, to create a 434 role without the permission, use CREATE ROLE name 435 NOINHERIT. 436 437 More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html) 438 ''; 439 default = null; 440 inherit defaultText; 441 }; 442 login = mkOption { 443 type = types.nullOr types.bool; 444 description = '' 445 Grants the user, created by the ensureUser attr, login permissions. From the postgres docs: 446 447 Only roles that have the LOGIN attribute can be used as 448 the initial role name for a database connection. A role 449 with the LOGIN attribute can be considered the same as a 450 database user. To create a role with login privilege, 451 use either: 452 453 CREATE ROLE name LOGIN; CREATE USER name; 454 455 (CREATE USER is equivalent to CREATE ROLE except that 456 CREATE USER includes LOGIN by default, while CREATE ROLE 457 does not.) 458 459 More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html) 460 ''; 461 default = null; 462 inherit defaultText; 463 }; 464 replication = mkOption { 465 type = types.nullOr types.bool; 466 description = '' 467 Grants the user, created by the ensureUser attr, replication permissions. From the postgres docs: 468 469 A role must explicitly be given permission to initiate 470 streaming replication (except for superusers, since those 471 bypass all permission checks). A role used for streaming 472 replication must have LOGIN permission as well. To create 473 such a role, use CREATE ROLE name REPLICATION LOGIN. 474 475 More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html) 476 ''; 477 default = null; 478 inherit defaultText; 479 }; 480 bypassrls = mkOption { 481 type = types.nullOr types.bool; 482 description = '' 483 Grants the user, created by the ensureUser attr, replication permissions. From the postgres docs: 484 485 A role must be explicitly given permission to bypass 486 every row-level security (RLS) policy (except for 487 superusers, since those bypass all permission checks). To 488 create such a role, use CREATE ROLE name BYPASSRLS as a 489 superuser. 490 491 More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html) 492 ''; 493 default = null; 494 inherit defaultText; 495 }; 496 }; 497 }; 498 }; 499 }; 500 } 501 ); 502 default = [ ]; 503 description = '' 504 Ensures that the specified users exist. 505 The PostgreSQL users will be identified using peer authentication. This authenticates the Unix user with the 506 same name only, and that without the need for a password. 507 This option will never delete existing users or remove DB ownership of databases 508 once granted with `ensureDBOwnership = true;`. This means that this must be 509 cleaned up manually when changing after changing the config in here. 510 ''; 511 example = literalExpression '' 512 [ 513 { 514 name = "nextcloud"; 515 } 516 { 517 name = "superuser"; 518 ensureDBOwnership = true; 519 } 520 ] 521 ''; 522 }; 523 524 enableTCPIP = mkOption { 525 type = types.bool; 526 default = false; 527 description = '' 528 Whether PostgreSQL should listen on all network interfaces. 529 If disabled, the database can only be accessed via its Unix 530 domain socket or via TCP connections to localhost. 531 ''; 532 }; 533 534 extensions = mkOption { 535 type = with types; coercedTo (listOf path) (path: _ignorePg: path) (functionTo (listOf path)); 536 default = _: [ ]; 537 example = literalExpression "ps: with ps; [ postgis pg_repack ]"; 538 description = '' 539 List of PostgreSQL extensions to install. 540 ''; 541 }; 542 543 settings = mkOption { 544 type = 545 with types; 546 submodule { 547 freeformType = attrsOf (oneOf [ 548 bool 549 float 550 int 551 str 552 ]); 553 options = { 554 shared_preload_libraries = mkOption { 555 type = nullOr (coercedTo (listOf str) (concatStringsSep ",") commas); 556 default = null; 557 example = literalExpression ''[ "auto_explain" "anon" ]''; 558 description = '' 559 List of libraries to be preloaded. 560 ''; 561 }; 562 563 log_line_prefix = mkOption { 564 type = types.str; 565 default = "[%p] "; 566 example = "%m [%p] "; 567 description = '' 568 A printf-style string that is output at the beginning of each log line. 569 Upstream default is `'%m [%p] '`, i.e. it includes the timestamp. We do 570 not include the timestamp, because journal has it anyway. 571 ''; 572 }; 573 574 port = mkOption { 575 type = types.port; 576 default = 5432; 577 description = '' 578 The port on which PostgreSQL listens. 579 ''; 580 }; 581 }; 582 }; 583 default = { }; 584 description = '' 585 PostgreSQL configuration. Refer to 586 <https://www.postgresql.org/docs/current/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE> 587 for an overview of `postgresql.conf`. 588 589 ::: {.note} 590 String values will automatically be enclosed in single quotes. Single quotes will be 591 escaped with two single quotes as described by the upstream documentation linked above. 592 ::: 593 ''; 594 example = literalExpression '' 595 { 596 log_connections = true; 597 log_statement = "all"; 598 logging_collector = true; 599 log_disconnections = true; 600 log_destination = lib.mkForce "syslog"; 601 } 602 ''; 603 }; 604 605 superUser = mkOption { 606 type = types.str; 607 default = "postgres"; 608 internal = true; 609 readOnly = true; 610 description = '' 611 PostgreSQL superuser account to use for various operations. Internal since changing 612 this value would lead to breakage while setting up databases. 613 ''; 614 }; 615 }; 616 617 }; 618 619 ###### implementation 620 621 config = mkIf cfg.enable { 622 623 assertions = map ( 624 { name, ensureDBOwnership, ... }: 625 { 626 assertion = ensureDBOwnership -> elem name cfg.ensureDatabases; 627 message = '' 628 For each database user defined with `services.postgresql.ensureUsers` and 629 `ensureDBOwnership = true;`, a database with the same name must be defined 630 in `services.postgresql.ensureDatabases`. 631 632 Offender: ${name} has not been found among databases. 633 ''; 634 } 635 ) cfg.ensureUsers; 636 637 services.postgresql.settings = { 638 hba_file = "${pkgs.writeText "pg_hba.conf" cfg.authentication}"; 639 ident_file = "${pkgs.writeText "pg_ident.conf" cfg.identMap}"; 640 log_destination = "stderr"; 641 listen_addresses = if cfg.enableTCPIP then "*" else "localhost"; 642 jit = mkDefault (if cfg.enableJIT then "on" else "off"); 643 }; 644 645 services.postgresql.package = 646 let 647 mkThrow = ver: throw "postgresql_${ver} was removed, please upgrade your postgresql version."; 648 mkWarn = 649 ver: 650 warn '' 651 The postgresql package is not pinned and selected automatically by 652 `system.stateVersion`. Right now this is `pkgs.postgresql_${ver}`, the 653 oldest postgresql version available and thus the next that will be 654 removed when EOL on the next stable cycle. 655 656 See also https://endoflife.date/postgresql 657 ''; 658 base = 659 if versionAtLeast config.system.stateVersion "24.11" then 660 pkgs.postgresql_16 661 else if versionAtLeast config.system.stateVersion "23.11" then 662 pkgs.postgresql_15 663 else if versionAtLeast config.system.stateVersion "22.05" then 664 pkgs.postgresql_14 665 else if versionAtLeast config.system.stateVersion "21.11" then 666 mkWarn "13" pkgs.postgresql_13 667 else if versionAtLeast config.system.stateVersion "20.03" then 668 mkThrow "11" 669 else if versionAtLeast config.system.stateVersion "17.09" then 670 mkThrow "9_6" 671 else 672 mkThrow "9_5"; 673 in 674 # Note: when changing the default, make it conditional on 675 # ‘system.stateVersion’ to maintain compatibility with existing 676 # systems! 677 mkDefault (if cfg.enableJIT then base.withJIT else base); 678 679 services.postgresql.dataDir = mkDefault "/var/lib/postgresql/${cfg.package.psqlSchema}"; 680 681 services.postgresql.authentication = mkMerge [ 682 (mkBefore "# Generated file; do not edit!") 683 (mkAfter '' 684 # default value of services.postgresql.authentication 685 local all postgres peer map=postgres 686 local all all peer 687 host all all 127.0.0.1/32 md5 688 host all all ::1/128 md5 689 '') 690 ]; 691 692 # The default allows to login with the same database username as the current system user. 693 # This is the default for peer authentication without a map, but needs to be made explicit 694 # once a map is used. 695 services.postgresql.identMap = mkAfter '' 696 postgres postgres postgres 697 ''; 698 699 services.postgresql.systemCallFilter = mkMerge [ 700 (mapAttrs (const mkDefault) { 701 "@system-service" = true; 702 "~@privileged" = true; 703 "~@resources" = true; 704 }) 705 (mkIf (any extensionInstalled [ "plv8" ]) { 706 "@pkey" = true; 707 }) 708 (mkIf (any extensionInstalled [ "citus" ]) { 709 "getpriority" = true; 710 "setpriority" = true; 711 }) 712 ]; 713 714 users.users.postgres = { 715 name = "postgres"; 716 uid = config.ids.uids.postgres; 717 group = "postgres"; 718 description = "PostgreSQL server user"; 719 home = "${cfg.dataDir}"; 720 useDefaultShell = true; 721 }; 722 723 users.groups.postgres.gid = config.ids.gids.postgres; 724 725 environment.systemPackages = [ cfg.finalPackage ]; 726 727 environment.pathsToLink = [ 728 "/share/postgresql" 729 ]; 730 731 system.checks = lib.optional ( 732 cfg.checkConfig && pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform 733 ) configFileCheck; 734 735 systemd.services.postgresql = { 736 description = "PostgreSQL Server"; 737 738 wantedBy = [ "multi-user.target" ]; 739 after = [ "network.target" ]; 740 741 environment.PGDATA = cfg.dataDir; 742 743 path = [ cfg.finalPackage ]; 744 745 preStart = '' 746 if ! test -e ${cfg.dataDir}/PG_VERSION; then 747 # Cleanup the data directory. 748 rm -f ${cfg.dataDir}/*.conf 749 750 # Initialise the database. 751 initdb -U ${cfg.superUser} ${escapeShellArgs cfg.initdbArgs} 752 753 # See postStart! 754 touch "${cfg.dataDir}/.first_startup" 755 fi 756 757 ln -sfn "${configFile}/postgresql.conf" "${cfg.dataDir}/postgresql.conf" 758 ''; 759 760 # Wait for PostgreSQL to be ready to accept connections. 761 postStart = 762 '' 763 PSQL="psql --port=${builtins.toString cfg.settings.port}" 764 765 while ! $PSQL -d postgres -c "" 2> /dev/null; do 766 if ! kill -0 "$MAINPID"; then exit 1; fi 767 sleep 0.1 768 done 769 770 if test -e "${cfg.dataDir}/.first_startup"; then 771 ${optionalString (cfg.initialScript != null) '' 772 $PSQL -f "${cfg.initialScript}" -d postgres 773 ''} 774 rm -f "${cfg.dataDir}/.first_startup" 775 fi 776 '' 777 + optionalString (cfg.ensureDatabases != [ ]) '' 778 ${concatMapStrings (database: '' 779 $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${database}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${database}"' 780 '') cfg.ensureDatabases} 781 '' 782 + '' 783 ${concatMapStrings ( 784 user: 785 let 786 dbOwnershipStmt = optionalString user.ensureDBOwnership ''$PSQL -tAc 'ALTER DATABASE "${user.name}" OWNER TO "${user.name}";' ''; 787 788 filteredClauses = filterAttrs (name: value: value != null) user.ensureClauses; 789 790 clauseSqlStatements = attrValues (mapAttrs (n: v: if v then n else "no${n}") filteredClauses); 791 792 userClauses = ''$PSQL -tAc 'ALTER ROLE "${user.name}" ${concatStringsSep " " clauseSqlStatements}' ''; 793 in 794 '' 795 $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"' 796 ${userClauses} 797 798 ${dbOwnershipStmt} 799 '' 800 ) cfg.ensureUsers} 801 ''; 802 803 serviceConfig = mkMerge [ 804 { 805 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 806 User = "postgres"; 807 Group = "postgres"; 808 RuntimeDirectory = "postgresql"; 809 Type = if versionAtLeast cfg.package.version "9.6" then "notify" else "simple"; 810 811 # Shut down Postgres using SIGINT ("Fast Shutdown mode"). See 812 # https://www.postgresql.org/docs/current/server-shutdown.html 813 KillSignal = "SIGINT"; 814 KillMode = "mixed"; 815 816 # Give Postgres a decent amount of time to clean up after 817 # receiving systemd's SIGINT. 818 TimeoutSec = 120; 819 820 ExecStart = "${cfg.finalPackage}/bin/postgres"; 821 822 # Hardening 823 CapabilityBoundingSet = [ "" ]; 824 DevicePolicy = "closed"; 825 PrivateTmp = true; 826 ProtectHome = true; 827 ProtectSystem = "strict"; 828 MemoryDenyWriteExecute = lib.mkDefault ( 829 cfg.settings.jit == "off" && (!any extensionInstalled [ "plv8" ]) 830 ); 831 NoNewPrivileges = true; 832 LockPersonality = true; 833 PrivateDevices = true; 834 PrivateMounts = true; 835 ProcSubset = "pid"; 836 ProtectClock = true; 837 ProtectControlGroups = true; 838 ProtectHostname = true; 839 ProtectKernelLogs = true; 840 ProtectKernelModules = true; 841 ProtectKernelTunables = true; 842 ProtectProc = "invisible"; 843 RemoveIPC = true; 844 RestrictAddressFamilies = [ 845 "AF_INET" 846 "AF_INET6" 847 "AF_NETLINK" # used for network interface enumeration 848 "AF_UNIX" 849 ]; 850 RestrictNamespaces = true; 851 RestrictRealtime = true; 852 RestrictSUIDSGID = true; 853 SystemCallArchitectures = "native"; 854 SystemCallFilter = pipe cfg.systemCallFilter [ 855 (mapAttrsToList (name: v: v // { inherit name; })) 856 (filter (getAttr "enable")) 857 sortProperties 858 (map (getAttr "name")) 859 ]; 860 UMask = if groupAccessAvailable then "0027" else "0077"; 861 } 862 (mkIf (cfg.dataDir != "/var/lib/postgresql/${cfg.package.psqlSchema}") { 863 # The user provides their own data directory 864 ReadWritePaths = [ cfg.dataDir ]; 865 }) 866 (mkIf (cfg.dataDir == "/var/lib/postgresql/${cfg.package.psqlSchema}") { 867 # Provision the default data directory 868 StateDirectory = "postgresql postgresql/${cfg.package.psqlSchema}"; 869 StateDirectoryMode = if groupAccessAvailable then "0750" else "0700"; 870 }) 871 ]; 872 873 unitConfig.RequiresMountsFor = "${cfg.dataDir}"; 874 }; 875 }; 876 877 meta.doc = ./postgresql.md; 878 meta.maintainers = with lib.maintainers; [ 879 thoughtpolice 880 danbst 881 ]; 882}