# Based on # See also: # - # - { config, lib, pkgs, ... }: with lib; let cfg = config.services.php-fpm-isolated; configFile = { pool, poolOpts, runtimeDir, sockFile, pidFile }: let config = { global = { daemonize = false; error_log = "syslog"; pid = pidFile; }; "${pool}" = let enforced = { inherit (poolOpts) user group; listen = sockFile; }; defaults = { "pm" = "dynamic"; "pm.max_children" = 16; "pm.min_spare_servers" = 1; "pm.max_spare_servers" = 4; "pm.start_servers" = 1; "catch_workers_output" = true; "php_admin_flag[log_errors]" = true; "env[PATH]" = makeBinPath [ pkgs.php ]; }; env = mapAttrs' (name: value: { name = "env[${name}]"; value = "\"${escape [ "\"" ] value}\""; }) poolOpts.env; in defaults // poolOpts.config // env // enforced; }; in (pkgs.formats.ini { }).generate "php-fpm-pool-${pool}.conf" config; in { options.services.php-fpm-isolated.pools = mkOption { default = { }; type = with types; attrsOf (submodule { options = { enable = mkEnableOption "PHP-FPM pool"; user = mkOption { type = str; }; group = mkOption { type = str; }; unveil = mkOption { type = listOf (either package str); }; env = mkOption { type = attrsOf str; default = { }; }; config = mkOption { type = attrsOf (oneOf [ int str bool ]); default = { }; }; }; }); }; config.systemd = let php-fpm = "${pkgs.php}/bin/php-fpm"; unitsFor = pool: poolOpts: let runtimeBase = "php-fpm-isolated/${pool}"; runtimeDir = "/run/${runtimeBase}"; pidFile = "${runtimeDir}/${pool}.pid"; sockFile = "${runtimeDir}/${pool}.sock"; in { name = "php-fpm-pool-${pool}"; value.service = { description = "PHP-FPM process manager for pool '${pool}'"; after = [ "network.target" ]; confinement.enable = true; serviceConfig = { Type = "notify"; ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID"; PIDFile = pidFile; Environment = "FPM_SOCKETS=${sockFile}=3"; ExecStart = let fpmConfig = configFile { inherit pool poolOpts runtimeDir sockFile pidFile; }; in "${php-fpm} --nodaemonize --fpm-config ${fpmConfig} --pid ${pidFile}"; PrivateTmp = true; PrivateNetwork = true; PrivateDevices = true; # XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK"; User = poolOpts.user; Group = poolOpts.group; RuntimeDirectory = runtimeBase; BindReadOnlyPaths = let unveiled = map builtins.toString poolOpts.unveil; in [ "/run/systemd/journal/socket" ] ++ unveiled; }; }; value.socket = { description = "PHP-FPM socket for pool '${pool}'"; listenStreams = [ sockFile ]; socketConfig = { User = poolOpts.user; Group = poolOpts.group; }; }; }; units = mapAttrs' unitsFor (filterAttrs (_: pool: pool.enable) cfg.pools); in { sockets = mapAttrs (_: unit: unit.socket) units; services = mapAttrs (_: unit: unit.service) units; }; }