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