{ lib, config, pkgs, ... }: with lib; let cfg = config.services.postfixadmin; fpm = config.services.phpfpm.pools.postfixadmin; localDB = cfg.database.host == "localhost"; user = if localDB then cfg.database.username else "nginx"; in { options.services.postfixadmin = { enable = mkOption { type = types.bool; default = false; description = '' Whether to enable postfixadmin. Also enables nginx virtual host management. Further nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`. See [](#opt-services.nginx.virtualHosts) for further information. ''; }; hostName = mkOption { type = types.str; example = "postfixadmin.example.com"; description = "Hostname to use for the nginx vhost"; }; adminEmail = mkOption { type = types.str; example = "postmaster@example.com"; description = '' Defines the Site Admin's email address. This will be used to send emails from to create mailboxes and from Send Email / Broadcast message pages. ''; }; setupPasswordFile = mkOption { type = types.path; description = '' Password file for the admin. Generate with `php -r "echo password_hash('some password here', PASSWORD_DEFAULT);"` ''; }; database = { username = mkOption { type = types.str; default = "postfixadmin"; description = '' Username for the postgresql connection. If `database.host` is set to `localhost`, a unix user and group of the same name will be created as well. ''; }; host = mkOption { type = types.str; default = "localhost"; description = '' Host of the postgresql server. If this is not set to `localhost`, you have to create the postgresql user and database yourself, with appropriate permissions. ''; }; passwordFile = mkOption { type = types.path; description = "Password file for the postgresql connection. Must be readable by user `nginx`."; }; dbname = mkOption { type = types.str; default = "postfixadmin"; description = "Name of the postgresql database"; }; }; extraConfig = mkOption { type = types.lines; default = ""; description = "Extra configuration for the postfixadmin instance, see postfixadmin's config.inc.php for available options."; }; }; config = mkIf cfg.enable { environment.etc."postfixadmin/config.local.php".text = '' <?php $CONF['setup_password'] = file_get_contents('${cfg.setupPasswordFile}'); $CONF['database_type'] = 'pgsql'; $CONF['database_host'] = ${if localDB then "null" else "'${cfg.database.host}'"}; ${optionalString localDB "$CONF['database_user'] = '${cfg.database.username}';"} $CONF['database_password'] = ${if localDB then "'dummy'" else "file_get_contents('${cfg.database.passwordFile}')"}; $CONF['database_name'] = '${cfg.database.dbname}'; $CONF['configured'] = true; ${cfg.extraConfig} ''; systemd.tmpfiles.settings."10-postfixadmin"."/var/cache/postfixadmin/templates_c".d = { inherit user; group = user; mode = "700"; }; services.nginx = { enable = true; virtualHosts = { ${cfg.hostName} = { forceSSL = mkDefault true; enableACME = mkDefault true; locations."/" = { root = "${pkgs.postfixadmin}/public"; index = "index.php"; extraConfig = '' location ~* \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:${fpm.socket}; include ${config.services.nginx.package}/conf/fastcgi_params; include ${pkgs.nginx}/conf/fastcgi.conf; } ''; }; }; }; }; services.postgresql = mkIf localDB { enable = true; ensureUsers = [ { name = cfg.database.username; } ]; }; # The postgresql module doesn't currently support concepts like # objects owners and extensions; for now we tack on what's needed # here. systemd.services.postfixadmin-postgres = let pgsql = config.services.postgresql; in mkIf localDB { after = [ "postgresql.service" ]; bindsTo = [ "postgresql.service" ]; wantedBy = [ "multi-user.target" ]; path = [ pgsql.package pkgs.util-linux ]; script = '' set -eu PSQL() { psql --port=${toString pgsql.port} "$@" } 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}"' current_owner=$(PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.database.dbname}'") if [[ "$current_owner" != "${cfg.database.username}" ]]; then PSQL -tAc 'ALTER DATABASE "${cfg.database.dbname}" OWNER TO "${cfg.database.username}"' if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}" ]]; then echo "Reassigning ownership of database ${cfg.database.dbname} to user ${cfg.database.username} failed on last boot. Failing..." exit 1 fi touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}" PSQL "${cfg.database.dbname}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.database.username}\"" rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}" fi ''; serviceConfig = { User = pgsql.superUser; Type = "oneshot"; RemainAfterExit = true; }; }; users.users.${user} = mkIf localDB { group = user; isSystemUser = true; createHome = false; }; users.groups.${user} = mkIf localDB {}; services.phpfpm.pools.postfixadmin = { user = user; phpPackage = pkgs.php81; phpOptions = '' error_log = 'stderr' log_errors = on ''; settings = mapAttrs (name: mkDefault) { "listen.owner" = "nginx"; "listen.group" = "nginx"; "listen.mode" = "0660"; "pm" = "dynamic"; "pm.max_children" = 75; "pm.start_servers" = 2; "pm.min_spare_servers" = 1; "pm.max_spare_servers" = 20; "pm.max_requests" = 500; "catch_workers_output" = true; }; }; }; }