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