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