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