1{ lib, config, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.postfixadmin;
7 fpm = config.services.phpfpm.pools.postfixadmin;
8 localDB = cfg.database.host == "localhost";
9 user = if localDB then cfg.database.username else "nginx";
10in
11{
12 options.services.postfixadmin = {
13 enable = mkOption {
14 type = types.bool;
15 default = false;
16 description = ''
17 Whether to enable postfixadmin.
18
19 Also enables nginx virtual host management.
20 Further nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`.
21 See [](#opt-services.nginx.virtualHosts) for further information.
22 '';
23 };
24
25 hostName = mkOption {
26 type = types.str;
27 example = "postfixadmin.example.com";
28 description = "Hostname to use for the nginx vhost";
29 };
30
31 adminEmail = mkOption {
32 type = types.str;
33 example = "postmaster@example.com";
34 description = ''
35 Defines the Site Admin's email address.
36 This will be used to send emails from to create mailboxes and
37 from Send Email / Broadcast message pages.
38 '';
39 };
40
41 setupPasswordFile = mkOption {
42 type = types.path;
43 description = ''
44 Password file for the admin.
45 Generate with `php -r "echo password_hash('some password here', PASSWORD_DEFAULT);"`
46 '';
47 };
48
49 database = {
50 username = mkOption {
51 type = types.str;
52 default = "postfixadmin";
53 description = ''
54 Username for the postgresql connection.
55 If `database.host` is set to `localhost`, a unix user and group of the same name will be created as well.
56 '';
57 };
58 host = mkOption {
59 type = types.str;
60 default = "localhost";
61 description = ''
62 Host of the postgresql server. If this is not set to
63 `localhost`, you have to create the
64 postgresql user and database yourself, with appropriate
65 permissions.
66 '';
67 };
68 passwordFile = mkOption {
69 type = types.path;
70 description = "Password file for the postgresql connection. Must be readable by user `nginx`.";
71 };
72 dbname = mkOption {
73 type = types.str;
74 default = "postfixadmin";
75 description = "Name of the postgresql database";
76 };
77 };
78
79 extraConfig = mkOption {
80 type = types.lines;
81 default = "";
82 description = "Extra configuration for the postfixadmin instance, see postfixadmin's config.inc.php for available options.";
83 };
84 };
85
86 config = mkIf cfg.enable {
87 environment.etc."postfixadmin/config.local.php".text = ''
88 <?php
89
90 $CONF['setup_password'] = file_get_contents('${cfg.setupPasswordFile}');
91
92 $CONF['database_type'] = 'pgsql';
93 $CONF['database_host'] = ${if localDB then "null" else "'${cfg.database.host}'"};
94 ${optionalString localDB "$CONF['database_user'] = '${cfg.database.username}';"}
95 $CONF['database_password'] = ${if localDB then "'dummy'" else "file_get_contents('${cfg.database.passwordFile}')"};
96 $CONF['database_name'] = '${cfg.database.dbname}';
97 $CONF['configured'] = true;
98
99 ${cfg.extraConfig}
100 '';
101
102 systemd.tmpfiles.settings."10-postfixadmin"."/var/cache/postfixadmin/templates_c".d = {
103 inherit user;
104 group = user;
105 mode = "700";
106 };
107
108 services.nginx = {
109 enable = true;
110 virtualHosts = {
111 ${cfg.hostName} = {
112 forceSSL = mkDefault true;
113 enableACME = mkDefault true;
114 locations."/" = {
115 root = "${pkgs.postfixadmin}/public";
116 index = "index.php";
117 extraConfig = ''
118 location ~* \.php$ {
119 fastcgi_split_path_info ^(.+\.php)(/.+)$;
120 fastcgi_pass unix:${fpm.socket};
121 include ${config.services.nginx.package}/conf/fastcgi_params;
122 include ${pkgs.nginx}/conf/fastcgi.conf;
123 }
124 '';
125 };
126 };
127 };
128 };
129
130 services.postgresql = mkIf localDB {
131 enable = true;
132 ensureUsers = [ {
133 name = cfg.database.username;
134 } ];
135 };
136 # The postgresql module doesn't currently support concepts like
137 # objects owners and extensions; for now we tack on what's needed
138 # here.
139 systemd.services.postfixadmin-postgres = let pgsql = config.services.postgresql; in mkIf localDB {
140 after = [ "postgresql.service" ];
141 bindsTo = [ "postgresql.service" ];
142 wantedBy = [ "multi-user.target" ];
143 path = [
144 pgsql.package
145 pkgs.util-linux
146 ];
147 script = ''
148 set -eu
149
150 PSQL() {
151 psql --port=${toString pgsql.port} "$@"
152 }
153
154 PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.database.dbname}'" | grep -q 1 || PSQL -tAc 'CREATE DATABASE "${cfg.database.dbname}" OWNER "${cfg.database.username}"'
155 current_owner=$(PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.database.dbname}'")
156 if [[ "$current_owner" != "${cfg.database.username}" ]]; then
157 PSQL -tAc 'ALTER DATABASE "${cfg.database.dbname}" OWNER TO "${cfg.database.username}"'
158 if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}" ]]; then
159 echo "Reassigning ownership of database ${cfg.database.dbname} to user ${cfg.database.username} failed on last boot. Failing..."
160 exit 1
161 fi
162 touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}"
163 PSQL "${cfg.database.dbname}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.database.username}\""
164 rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}"
165 fi
166 '';
167
168 serviceConfig = {
169 User = pgsql.superUser;
170 Type = "oneshot";
171 RemainAfterExit = true;
172 };
173 };
174
175 users.users.${user} = mkIf localDB {
176 group = user;
177 isSystemUser = true;
178 createHome = false;
179 };
180 users.groups.${user} = mkIf localDB {};
181
182 services.phpfpm.pools.postfixadmin = {
183 user = user;
184 phpPackage = pkgs.php81;
185 phpOptions = ''
186 error_log = 'stderr'
187 log_errors = on
188 '';
189 settings = mapAttrs (name: mkDefault) {
190 "listen.owner" = "nginx";
191 "listen.group" = "nginx";
192 "listen.mode" = "0660";
193 "pm" = "dynamic";
194 "pm.max_children" = 75;
195 "pm.start_servers" = 2;
196 "pm.min_spare_servers" = 1;
197 "pm.max_spare_servers" = 20;
198 "pm.max_requests" = 500;
199 "catch_workers_output" = true;
200 };
201 };
202 };
203}