1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.phpfpm;
7
8 runtimeDir = "/run/phpfpm";
9
10 toStr = value:
11 if true == value then "yes"
12 else if false == value then "no"
13 else toString value;
14
15 fpmCfgFile = pool: poolOpts: pkgs.writeText "phpfpm-${pool}.conf" ''
16 [global]
17 ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings)}
18 ${optionalString (cfg.extraConfig != null) cfg.extraConfig}
19
20 [${pool}]
21 ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") poolOpts.settings)}
22 ${concatStringsSep "\n" (mapAttrsToList (n: v: "env[${n}] = ${toStr v}") poolOpts.phpEnv)}
23 ${optionalString (poolOpts.extraConfig != null) poolOpts.extraConfig}
24 '';
25
26 phpIni = poolOpts: pkgs.runCommand "php.ini" {
27 inherit (poolOpts) phpPackage phpOptions;
28 preferLocalBuild = true;
29 passAsFile = [ "phpOptions" ];
30 } ''
31 cat ${poolOpts.phpPackage}/etc/php.ini $phpOptionsPath > $out
32 '';
33
34 poolOpts = { name, ... }:
35 let
36 poolOpts = cfg.pools.${name};
37 in
38 {
39 options = {
40 socket = mkOption {
41 type = types.str;
42 readOnly = true;
43 description = ''
44 Path to the unix socket file on which to accept FastCGI requests.
45 <note><para>This option is read-only and managed by NixOS.</para></note>
46 '';
47 example = "${runtimeDir}/<name>.sock";
48 };
49
50 listen = mkOption {
51 type = types.str;
52 default = "";
53 example = "/path/to/unix/socket";
54 description = ''
55 The address on which to accept FastCGI requests.
56 '';
57 };
58
59 phpPackage = mkOption {
60 type = types.package;
61 default = cfg.phpPackage;
62 defaultText = literalExpression "config.services.phpfpm.phpPackage";
63 description = ''
64 The PHP package to use for running this PHP-FPM pool.
65 '';
66 };
67
68 phpOptions = mkOption {
69 type = types.lines;
70 description = ''
71 "Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool."
72 '';
73 };
74
75 phpEnv = lib.mkOption {
76 type = with types; attrsOf str;
77 default = {};
78 description = ''
79 Environment variables used for this PHP-FPM pool.
80 '';
81 example = literalExpression ''
82 {
83 HOSTNAME = "$HOSTNAME";
84 TMP = "/tmp";
85 TMPDIR = "/tmp";
86 TEMP = "/tmp";
87 }
88 '';
89 };
90
91 user = mkOption {
92 type = types.str;
93 description = "User account under which this pool runs.";
94 };
95
96 group = mkOption {
97 type = types.str;
98 description = "Group account under which this pool runs.";
99 };
100
101 settings = mkOption {
102 type = with types; attrsOf (oneOf [ str int bool ]);
103 default = {};
104 description = ''
105 PHP-FPM pool directives. Refer to the "List of pool directives" section of
106 <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
107 for details. Note that settings names must be enclosed in quotes (e.g.
108 <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
109 '';
110 example = literalExpression ''
111 {
112 "pm" = "dynamic";
113 "pm.max_children" = 75;
114 "pm.start_servers" = 10;
115 "pm.min_spare_servers" = 5;
116 "pm.max_spare_servers" = 20;
117 "pm.max_requests" = 500;
118 }
119 '';
120 };
121
122 extraConfig = mkOption {
123 type = with types; nullOr lines;
124 default = null;
125 description = ''
126 Extra lines that go into the pool configuration.
127 See the documentation on <literal>php-fpm.conf</literal> for
128 details on configuration directives.
129 '';
130 };
131 };
132
133 config = {
134 socket = if poolOpts.listen == "" then "${runtimeDir}/${name}.sock" else poolOpts.listen;
135 group = mkDefault poolOpts.user;
136 phpOptions = mkBefore cfg.phpOptions;
137
138 settings = mapAttrs (name: mkDefault){
139 listen = poolOpts.socket;
140 user = poolOpts.user;
141 group = poolOpts.group;
142 };
143 };
144 };
145
146in {
147 imports = [
148 (mkRemovedOptionModule [ "services" "phpfpm" "poolConfigs" ] "Use services.phpfpm.pools instead.")
149 (mkRemovedOptionModule [ "services" "phpfpm" "phpIni" ] "")
150 ];
151
152 options = {
153 services.phpfpm = {
154 settings = mkOption {
155 type = with types; attrsOf (oneOf [ str int bool ]);
156 default = {};
157 description = ''
158 PHP-FPM global directives. Refer to the "List of global php-fpm.conf directives" section of
159 <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
160 for details. Note that settings names must be enclosed in quotes (e.g.
161 <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
162 You need not specify the options <literal>error_log</literal> or
163 <literal>daemonize</literal> here, since they are generated by NixOS.
164 '';
165 };
166
167 extraConfig = mkOption {
168 type = with types; nullOr lines;
169 default = null;
170 description = ''
171 Extra configuration that should be put in the global section of
172 the PHP-FPM configuration file. Do not specify the options
173 <literal>error_log</literal> or
174 <literal>daemonize</literal> here, since they are generated by
175 NixOS.
176 '';
177 };
178
179 phpPackage = mkOption {
180 type = types.package;
181 default = pkgs.php;
182 defaultText = literalExpression "pkgs.php";
183 description = ''
184 The PHP package to use for running the PHP-FPM service.
185 '';
186 };
187
188 phpOptions = mkOption {
189 type = types.lines;
190 default = "";
191 example =
192 ''
193 date.timezone = "CET"
194 '';
195 description = ''
196 Options appended to the PHP configuration file <filename>php.ini</filename>.
197 '';
198 };
199
200 pools = mkOption {
201 type = types.attrsOf (types.submodule poolOpts);
202 default = {};
203 example = literalExpression ''
204 {
205 mypool = {
206 user = "php";
207 group = "php";
208 phpPackage = pkgs.php;
209 settings = {
210 "pm" = "dynamic";
211 "pm.max_children" = 75;
212 "pm.start_servers" = 10;
213 "pm.min_spare_servers" = 5;
214 "pm.max_spare_servers" = 20;
215 "pm.max_requests" = 500;
216 };
217 }
218 }'';
219 description = ''
220 PHP-FPM pools. If no pools are defined, the PHP-FPM
221 service is disabled.
222 '';
223 };
224 };
225 };
226
227 config = mkIf (cfg.pools != {}) {
228
229 warnings =
230 mapAttrsToList (pool: poolOpts: ''
231 Using config.services.phpfpm.pools.${pool}.listen is deprecated and will become unsupported in a future release. Please reference the read-only option config.services.phpfpm.pools.${pool}.socket to access the path of your socket.
232 '') (filterAttrs (pool: poolOpts: poolOpts.listen != "") cfg.pools) ++
233 mapAttrsToList (pool: poolOpts: ''
234 Using config.services.phpfpm.pools.${pool}.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.pools.${pool}.settings.
235 '') (filterAttrs (pool: poolOpts: poolOpts.extraConfig != null) cfg.pools) ++
236 optional (cfg.extraConfig != null) ''
237 Using config.services.phpfpm.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.settings.
238 ''
239 ;
240
241 services.phpfpm.settings = {
242 error_log = "syslog";
243 daemonize = false;
244 };
245
246 systemd.slices.phpfpm = {
247 description = "PHP FastCGI Process manager pools slice";
248 };
249
250 systemd.targets.phpfpm = {
251 description = "PHP FastCGI Process manager pools target";
252 wantedBy = [ "multi-user.target" ];
253 };
254
255 systemd.services = mapAttrs' (pool: poolOpts:
256 nameValuePair "phpfpm-${pool}" {
257 description = "PHP FastCGI Process Manager service for pool ${pool}";
258 after = [ "network.target" ];
259 wantedBy = [ "phpfpm.target" ];
260 partOf = [ "phpfpm.target" ];
261 serviceConfig = let
262 cfgFile = fpmCfgFile pool poolOpts;
263 iniFile = phpIni poolOpts;
264 in {
265 Slice = "phpfpm.slice";
266 PrivateDevices = true;
267 PrivateTmp = true;
268 ProtectSystem = "full";
269 ProtectHome = true;
270 # XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work
271 RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
272 Type = "notify";
273 ExecStart = "${poolOpts.phpPackage}/bin/php-fpm -y ${cfgFile} -c ${iniFile}";
274 ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
275 RuntimeDirectory = "phpfpm";
276 RuntimeDirectoryPreserve = true; # Relevant when multiple processes are running
277 Restart = "always";
278 };
279 }
280 ) cfg.pools;
281 };
282}