1 {pkgs, lib, config, ...}:
2 let inherit (lib) types;
3 inherit (pkgs.lib) unlines unlinesAttrs;
4 inherit (config) networking users;
5 inherit (config.services) postgresql;
6 psql = "${pkgs.postgresql}/bin/psql --set ON_ERROR_STOP=1 -U ${postgresql.superUser}";
7 psqlRun = args: sql: "${psql} ${args} -f - <<EOF\n" + sql + "\nEOF\n";
8 initLanguages = initLanguagePLPGSQL;
10 psqlRun "template1" ''
11 CREATE OR REPLACE FUNCTION create_language_plpgsql()
12 RETURNS BOOLEAN AS \$\$
13 CREATE LANGUAGE plpgsql;
16 SELECT CASE WHEN NOT (
19 WHERE lanname = 'plpgsql'
21 SELECT FALSE AS exists
26 create_language_plpgsql()
29 END AS plpgsql_created;
30 DROP FUNCTION create_language_plpgsql();
34 psqlRun "" (unlinesAttrs (schema: {owner, extraConfig, ...}: ''
35 DO LANGUAGE plpgsql \$\$
37 IF NOT EXISTS (SELECT * FROM pg_catalog.pg_namespace WHERE nspname = "${schema}" LIMIT 1) THEN
38 CREATE SCHEMA ${schema}
39 AUTHORIZATION ${owner};
44 '') postgresql.schemas);
46 psqlRun "template1" ''
47 -- NOTE: deny access to public schema,
48 -- so that users do not see others' databases
49 REVOKE ALL ON DATABASE template1 FROM public;
50 REVOKE ALL ON SCHEMA public FROM public;
51 GRANT ALL ON SCHEMA public TO ${postgresql.superUser};
53 psqlRun "template1" ''
54 -- NOTE: deny public access to all tables
55 REVOKE ALL ON ALL TABLES IN SCHEMA pg_catalog FROM public;
56 REVOKE ALL ON SCHEMA pg_catalog FROM public;
58 initUsers = psqlRun ""
59 (unlinesAttrs (user: {auth, extraConfig, ...}: ''
60 DO LANGUAGE plpgsql \$\$
62 IF NOT EXISTS (SELECT * FROM pg_catalog.pg_user WHERE usename = '${user}' LIMIT 1) THEN
72 GRANT USAGE ON SCHEMA public TO ${user};
74 '') postgresql.users);
75 initRoles = psqlRun ""
76 (unlinesAttrs (role: {...}: ''
77 DO LANGUAGE plpgsql \$\$
79 IF NOT EXISTS (SELECT * FROM pg_catalog.pg_roles WHERE rolname = "${role}" LIMIT 1) THEN
89 '') postgresql.roles);
91 unlinesAttrs (db: {owner ? db, users, extraConfig, ...}: ''
92 # NOTE: CREATE DATABASE cannot be done inside a function or multi-lines input.
93 exists="$(${psql} template1 -t -c "SELECT datname FROM pg_catalog.pg_database WHERE datname = '${db}' LIMIT 1")"
95 ${psql} template1 -c "CREATE DATABASE ${db} WITH OWNER=${owner};"
96 '' + psqlRun "template1" ''
97 REVOKE ALL ON DATABASE ${db} FROM public;
98 ${unlines (map (user: "GRANT CONNECT,TEMPORARY ON DATABASE ${db} TO ${user};") users)}
101 GRANT ALL ON SCHEMA public TO ${owner} WITH GRANT OPTION;
104 ) postgresql.databases;
108 services.postgresql.databases = lib.mkOption {
110 type = types.attrsOf (types.submodule ({name, options, config, ...}: {
112 data = lib.mkOption {
114 description = "The database's data in SQL.";
116 owner = lib.mkOption {
118 description = "The database's owner.";
121 users = lib.mkOption {
122 type = types.listOf types.str;
123 description = "Databases' users.";
126 resetData = lib.mkOption {
128 description = "Whether to reset the data at each start of the postgresql service.";
131 extraConfig = lib.mkOption {
133 description = "Extra SQL config code (run on the database).";
139 services.postgresql.schemas = lib.mkOption {
141 type = types.attrsOf (types.submodule ({name, options, config, ...}: {
143 data = lib.mkOption {
145 description = "The schema's data in SQL.";
147 resetData = lib.mkOption {
149 description = "Whether to reset the data at each start of the postgresql service.";
152 extraConfig = lib.mkOption {
154 description = "Extra SQL config code.";
160 services.postgresql.users = lib.mkOption {
162 type = types.attrsOf (types.submodule ({name, options, config, ...}: {
164 auth = lib.mkOption {
165 type = types.enum [ "unix" "password" ];
166 description = "Authentification method.";
169 extraConfig = lib.mkOption {
171 description = "Extra SQL config code.";
177 services.postgresql.roles = lib.mkOption {
179 type = types.attrsOf (types.submodule ({name, options, config, ...}: {
186 users.users = lib.mapAttrs (user: {auth, ...}:
187 { extraGroups = lib.optionals (auth == "unix")
188 [ users.users.postgres.group ];
191 systemd.services.postgresql = {
192 postStart = unlines [
201 # RuntimeDirectory = [ "postgresql" ];
202 # RuntimeDirectoryMode = "0775";
209 #unix_socket_directories = '/run/postgresql'
210 #unix_socket_group = '${postgresql.superUser}-data'
211 unix_socket_permissions = 0770 # begin with 0 to use octal notation
213 identMap = lib.mkForce ''
214 # MAPNAME SYSTEM-USERNAME PG-USERNAME
215 admin ${postgresql.superUser} ${postgresql.superUser}
216 admin root ${postgresql.superUser}
217 ${unlinesAttrs (user: {...}: ''user root ${user}'') postgresql.users}
220 authentication = lib.mkForce ''
221 # CONNECTION DATABASE USER AUTH OPTIONS
222 local all postgres peer map=admin
223 local all backup peer
224 local sameuser all peer map=user
225 local samerole all peer map=role
232 #users.groups."${postgresql.superUser}-data" = {
233 # name = "${postgresql.superUser}-data";