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