at 23.05-pre 6.7 kB view raw
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 = lib.mdDoc '' 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 = lib.mdDoc "Hostname to use for the nginx vhost"; 29 }; 30 31 adminEmail = mkOption { 32 type = types.str; 33 example = "postmaster@example.com"; 34 description = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc "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 = lib.mdDoc "Name of the postgresql database"; 76 }; 77 }; 78 79 extraConfig = mkOption { 80 type = types.lines; 81 default = ""; 82 description = lib.mdDoc "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.rules = [ "d /var/cache/postfixadmin/templates_c 700 ${user} ${user}" ]; 103 104 services.nginx = { 105 enable = true; 106 virtualHosts = { 107 ${cfg.hostName} = { 108 forceSSL = mkDefault true; 109 enableACME = mkDefault true; 110 locations."/" = { 111 root = "${pkgs.postfixadmin}/public"; 112 index = "index.php"; 113 extraConfig = '' 114 location ~* \.php$ { 115 fastcgi_split_path_info ^(.+\.php)(/.+)$; 116 fastcgi_pass unix:${fpm.socket}; 117 include ${config.services.nginx.package}/conf/fastcgi_params; 118 include ${pkgs.nginx}/conf/fastcgi.conf; 119 } 120 ''; 121 }; 122 }; 123 }; 124 }; 125 126 services.postgresql = mkIf localDB { 127 enable = true; 128 ensureUsers = [ { 129 name = cfg.database.username; 130 } ]; 131 }; 132 # The postgresql module doesn't currently support concepts like 133 # objects owners and extensions; for now we tack on what's needed 134 # here. 135 systemd.services.postfixadmin-postgres = let pgsql = config.services.postgresql; in mkIf localDB { 136 after = [ "postgresql.service" ]; 137 bindsTo = [ "postgresql.service" ]; 138 wantedBy = [ "multi-user.target" ]; 139 path = [ 140 pgsql.package 141 pkgs.util-linux 142 ]; 143 script = '' 144 set -eu 145 146 PSQL() { 147 psql --port=${toString pgsql.port} "$@" 148 } 149 150 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}"' 151 current_owner=$(PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.database.dbname}'") 152 if [[ "$current_owner" != "${cfg.database.username}" ]]; then 153 PSQL -tAc 'ALTER DATABASE "${cfg.database.dbname}" OWNER TO "${cfg.database.username}"' 154 if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}" ]]; then 155 echo "Reassigning ownership of database ${cfg.database.dbname} to user ${cfg.database.username} failed on last boot. Failing..." 156 exit 1 157 fi 158 touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}" 159 PSQL "${cfg.database.dbname}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.database.username}\"" 160 rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}" 161 fi 162 ''; 163 164 serviceConfig = { 165 User = pgsql.superUser; 166 Type = "oneshot"; 167 RemainAfterExit = true; 168 }; 169 }; 170 171 users.users.${user} = mkIf localDB { 172 group = user; 173 isSystemUser = true; 174 createHome = false; 175 }; 176 users.groups.${user} = mkIf localDB {}; 177 178 services.phpfpm.pools.postfixadmin = { 179 user = user; 180 phpPackage = pkgs.php81; 181 phpOptions = '' 182 error_log = 'stderr' 183 log_errors = on 184 ''; 185 settings = mapAttrs (name: mkDefault) { 186 "listen.owner" = "nginx"; 187 "listen.group" = "nginx"; 188 "listen.mode" = "0660"; 189 "pm" = "dynamic"; 190 "pm.max_children" = 75; 191 "pm.start_servers" = 2; 192 "pm.min_spare_servers" = 1; 193 "pm.max_spare_servers" = 20; 194 "pm.max_requests" = 500; 195 "catch_workers_output" = true; 196 }; 197 }; 198 }; 199}