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}