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