1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.postgresql;
8
9 # see description of extraPlugins
10 postgresqlAndPlugins = pg:
11 if cfg.extraPlugins == [] then pg
12 else pkgs.buildEnv {
13 name = "postgresql-and-plugins-${(builtins.parseDrvName pg.name).version}";
14 paths = [ pg ] ++ cfg.extraPlugins;
15 postBuild =
16 ''
17 mkdir -p $out/bin
18 rm $out/bin/{pg_config,postgres,pg_ctl}
19 cp --target-directory=$out/bin ${pg}/bin/{postgres,pg_config,pg_ctl}
20 '';
21 };
22
23 postgresql = postgresqlAndPlugins cfg.package;
24
25 flags = optional cfg.enableTCPIP "-i";
26
27 # The main PostgreSQL configuration file.
28 configFile = pkgs.writeText "postgresql.conf"
29 ''
30 hba_file = '${pkgs.writeText "pg_hba.conf" cfg.authentication}'
31 ident_file = '${pkgs.writeText "pg_ident.conf" cfg.identMap}'
32 log_destination = 'stderr'
33 port = ${toString cfg.port}
34 ${cfg.extraConfig}
35 '';
36
37 pre84 = versionOlder (builtins.parseDrvName postgresql.name).version "8.4";
38
39in
40
41{
42
43 ###### interface
44
45 options = {
46
47 services.postgresql = {
48
49 enable = mkOption {
50 type = types.bool;
51 default = false;
52 description = ''
53 Whether to run PostgreSQL.
54 '';
55 };
56
57 package = mkOption {
58 type = types.package;
59 example = literalExample "pkgs.postgresql92";
60 description = ''
61 PostgreSQL package to use.
62 '';
63 };
64
65 port = mkOption {
66 type = types.int;
67 default = 5432;
68 description = ''
69 The port on which PostgreSQL listens.
70 '';
71 };
72
73 dataDir = mkOption {
74 type = types.path;
75 default = "/var/db/postgresql";
76 description = ''
77 Data directory for PostgreSQL.
78 '';
79 };
80
81 authentication = mkOption {
82 type = types.lines;
83 default = "";
84 description = ''
85 Defines how users authenticate themselves to the server. By
86 default, "trust" access to local users will always be granted
87 along with any other custom options. If you do not want this,
88 set this option using "lib.mkForce" to override this
89 behaviour.
90 '';
91 };
92
93 identMap = mkOption {
94 type = types.lines;
95 default = "";
96 description = ''
97 Defines the mapping from system users to database users.
98 '';
99 };
100
101 initialScript = mkOption {
102 type = types.nullOr types.path;
103 default = null;
104 description = ''
105 A file containing SQL statements to execute on first startup.
106 '';
107 };
108
109 enableTCPIP = mkOption {
110 type = types.bool;
111 default = false;
112 description = ''
113 Whether PostgreSQL should listen on all network interfaces.
114 If disabled, the database can only be accessed via its Unix
115 domain socket or via TCP connections to localhost.
116 '';
117 };
118
119 extraPlugins = mkOption {
120 type = types.listOf types.path;
121 default = [];
122 example = literalExample "[ (pkgs.postgis.override { postgresql = pkgs.postgresql94; }).v_2_1_4 ]";
123 description = ''
124 When this list contains elements a new store path is created.
125 PostgreSQL and the elements are symlinked into it. Then pg_config,
126 postgres and pg_ctl are copied to make them use the new
127 $out/lib directory as pkglibdir. This makes it possible to use postgis
128 without patching the .sql files which reference $libdir/postgis-1.5.
129 '';
130 # Note: the duplication of executables is about 4MB size.
131 # So a nicer solution was patching postgresql to allow setting the
132 # libdir explicitely.
133 };
134
135 extraConfig = mkOption {
136 type = types.lines;
137 default = "";
138 description = "Additional text to be appended to <filename>postgresql.conf</filename>.";
139 };
140
141 recoveryConfig = mkOption {
142 type = types.nullOr types.lines;
143 default = null;
144 description = ''
145 Contents of the <filename>recovery.conf</filename> file.
146 '';
147 };
148 };
149
150 };
151
152
153 ###### implementation
154
155 config = mkIf config.services.postgresql.enable {
156
157 services.postgresql.package =
158 # Note: when changing the default, make it conditional on
159 # ‘system.stateVersion’ to maintain compatibility with existing
160 # systems!
161 mkDefault (if versionAtLeast config.system.stateVersion "16.03" then pkgs.postgresql95 else pkgs.postgresql94);
162
163 services.postgresql.authentication = mkAfter
164 ''
165 # Generated file; do not edit!
166 local all all ident ${optionalString pre84 "sameuser"}
167 host all all 127.0.0.1/32 md5
168 host all all ::1/128 md5
169 '';
170
171 users.extraUsers.postgres =
172 { name = "postgres";
173 uid = config.ids.uids.postgres;
174 group = "postgres";
175 description = "PostgreSQL server user";
176 };
177
178 users.extraGroups.postgres.gid = config.ids.gids.postgres;
179
180 environment.systemPackages = [ postgresql ];
181
182 systemd.services.postgresql =
183 { description = "PostgreSQL Server";
184
185 wantedBy = [ "multi-user.target" ];
186 after = [ "network.target" ];
187
188 environment.PGDATA = cfg.dataDir;
189
190 path = [ postgresql ];
191
192 preStart =
193 ''
194 # Create data directory.
195 if ! test -e ${cfg.dataDir}/PG_VERSION; then
196 mkdir -m 0700 -p ${cfg.dataDir}
197 rm -f ${cfg.dataDir}/*.conf
198 chown -R postgres:postgres ${cfg.dataDir}
199 fi
200 ''; # */
201
202 script =
203 ''
204 # Initialise the database.
205 if ! test -e ${cfg.dataDir}/PG_VERSION; then
206 initdb -U root
207 # See postStart!
208 touch "${cfg.dataDir}/.first_startup"
209 fi
210 ln -sfn "${configFile}" "${cfg.dataDir}/postgresql.conf"
211 ${optionalString (cfg.recoveryConfig != null) ''
212 ln -sfn "${pkgs.writeText "recovery.conf" cfg.recoveryConfig}" \
213 "${cfg.dataDir}/recovery.conf"
214 ''}
215
216 exec postgres ${toString flags}
217 '';
218
219 serviceConfig =
220 { ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
221 User = "postgres";
222 Group = "postgres";
223 PermissionsStartOnly = true;
224
225 # Shut down Postgres using SIGINT ("Fast Shutdown mode"). See
226 # http://www.postgresql.org/docs/current/static/server-shutdown.html
227 KillSignal = "SIGINT";
228 KillMode = "mixed";
229
230 # Give Postgres a decent amount of time to clean up after
231 # receiving systemd's SIGINT.
232 TimeoutSec = 120;
233 };
234
235 # Wait for PostgreSQL to be ready to accept connections.
236 postStart =
237 ''
238 while ! psql --port=${toString cfg.port} postgres -c "" 2> /dev/null; do
239 if ! kill -0 "$MAINPID"; then exit 1; fi
240 sleep 0.1
241 done
242
243 if test -e "${cfg.dataDir}/.first_startup"; then
244 ${optionalString (cfg.initialScript != null) ''
245 cat "${cfg.initialScript}" | psql --port=${toString cfg.port} postgres
246 ''}
247 rm -f "${cfg.dataDir}/.first_startup"
248 fi
249 '';
250
251 unitConfig.RequiresMountsFor = "${cfg.dataDir}";
252 };
253
254 };
255
256}