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";
123 description = ''
124 When this list contains elements a new store path is created.
125 PostgreSQL and the elments are symlinked into it. Then pg_config,
126 postgres and pc_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 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 = [ pkgs.su postgresql ];
191
192 preStart =
193 ''
194 # Initialise the database.
195 if ! test -e ${cfg.dataDir}/PG_VERSION; then
196 mkdir -m 0700 -p ${cfg.dataDir}
197 rm -f ${cfg.dataDir}/*.conf
198 if [ "$(id -u)" = 0 ]; then
199 chown -R postgres ${cfg.dataDir}
200 su -s ${pkgs.stdenv.shell} postgres -c 'initdb -U root'
201 else
202 # For non-root operation.
203 initdb
204 fi
205 fi
206
207 ln -sfn "${configFile}" "${cfg.dataDir}/postgresql.conf"
208 ${optionalString (cfg.recoveryConfig != null) ''
209 ln -sfn "${pkgs.writeText "recovery.conf" cfg.recoveryConfig}" \
210 "${cfg.dataDir}/recovery.conf"
211 ''}
212 ''; # */
213
214 serviceConfig =
215 { ExecStart = "@${postgresql}/bin/postgres postgres ${toString flags}";
216 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
217 User = "postgres";
218 Group = "postgres";
219 PermissionsStartOnly = true;
220
221 # Shut down Postgres using SIGINT ("Fast Shutdown mode"). See
222 # http://www.postgresql.org/docs/current/static/server-shutdown.html
223 KillSignal = "SIGINT";
224 KillMode = "mixed";
225
226 # Give Postgres a decent amount of time to clean up after
227 # receiving systemd's SIGINT.
228 TimeoutSec = 120;
229 };
230
231 # Wait for PostgreSQL to be ready to accept connections.
232 postStart =
233 ''
234 while ! psql --port=${toString cfg.port} postgres -c "" 2> /dev/null; do
235 if ! kill -0 "$MAINPID"; then exit 1; fi
236 sleep 0.1
237 done
238
239 if test -e "${cfg.dataDir}/.first_startup"; then
240 ${optionalString (cfg.initialScript != null) ''
241 cat "${cfg.initialScript}" | psql --port=${toString cfg.port} postgres
242 ''}
243 rm -f "${cfg.dataDir}/.first_startup"
244 fi
245 '';
246
247 unitConfig.RequiresMountsFor = "${cfg.dataDir}";
248 };
249
250 };
251
252}