]> git.immae.eu Git - perso/Immae/Config/Nix.git/blame - modules/websites/httpd-service-builder.nix
Upgrade to nixos-unstable
[perso/Immae/Config/Nix.git] / modules / websites / httpd-service-builder.nix
CommitLineData
581c499c
IB
1# to help backporting this builder should stay as close as possible to
2# nixos/modules/services/web-servers/apache-httpd/default.nix
3{ httpdName, withUsers ? true }:
273e2c61
IB
4{ config, lib, pkgs, ... }:
5
6with lib;
7
8let
9
e7b890d0 10 cfg = config.services.httpd."${httpdName}";
273e2c61 11
e7b890d0 12 runtimeDir = "/run/httpd_${httpdName}";
273e2c61 13
e7b890d0 14 pkg = cfg.package.out;
273e2c61 15
e7b890d0 16 httpdConf = cfg.configFile;
273e2c61 17
e7b890d0 18 php = cfg.phpPackage.override { apacheHttpd = pkg.dev; /* otherwise it only gets .out */ };
273e2c61 19
e7b890d0 20 phpMajorVersion = lib.versions.major (lib.getVersion php);
273e2c61 21
e7b890d0 22 mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; };
273e2c61 23
e7b890d0 24 vhosts = attrValues cfg.virtualHosts;
273e2c61 25
e7b890d0
IB
26 mkListenInfo = hostOpts:
27 if hostOpts.listen != [] then hostOpts.listen
28 else (
29 optional (hostOpts.onlySSL || hostOpts.addSSL || hostOpts.forceSSL) { ip = "*"; port = 443; ssl = true; } ++
30 optional (!hostOpts.onlySSL) { ip = "*"; port = 80; ssl = false; }
31 );
273e2c61 32
e7b890d0 33 listenInfo = unique (concatMap mkListenInfo vhosts);
273e2c61 34
e7b890d0
IB
35 enableHttp2 = any (vhost: vhost.http2) vhosts;
36 enableSSL = any (listen: listen.ssl) listenInfo;
37 enableUserDir = any (vhost: vhost.enableUserDir) vhosts;
273e2c61 38
e7b890d0
IB
39 # NOTE: generally speaking order of modules is very important
40 modules =
41 [ # required apache modules our httpd service cannot run without
42 "authn_core" "authz_core"
43 "log_config"
44 "mime" "autoindex" "negotiation" "dir"
45 "alias" "rewrite"
46 "unixd" "slotmem_shm" "socache_shmcb"
47 "mpm_${cfg.multiProcessingModule}"
273e2c61 48 ]
e7b890d0
IB
49 ++ (if cfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
50 ++ optional enableHttp2 "http2"
273e2c61 51 ++ optional enableSSL "ssl"
e7b890d0
IB
52 ++ optional enableUserDir "userdir"
53 ++ optional cfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
54 ++ optional cfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
55 ++ optional cfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
56 ++ cfg.extraModules;
273e2c61 57
e7b890d0
IB
58 loggingConf = (if cfg.logFormat != "none" then ''
59 ErrorLog ${cfg.logDir}/error.log
273e2c61
IB
60
61 LogLevel notice
62
63 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
64 LogFormat "%h %l %u %t \"%r\" %>s %b" common
65 LogFormat "%{Referer}i -> %U" referer
66 LogFormat "%{User-agent}i" agent
67
e7b890d0 68 CustomLog ${cfg.logDir}/access.log ${cfg.logFormat}
273e2c61
IB
69 '' else ''
70 ErrorLog /dev/null
71 '');
72
73
74 browserHacks = ''
e7b890d0
IB
75 <IfModule mod_setenvif.c>
76 BrowserMatch "Mozilla/2" nokeepalive
77 BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
78 BrowserMatch "RealPlayer 4\.0" force-response-1.0
79 BrowserMatch "Java/1\.0" force-response-1.0
80 BrowserMatch "JDK/1\.0" force-response-1.0
81 BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
82 BrowserMatch "^WebDrive" redirect-carefully
83 BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
84 BrowserMatch "^gnome-vfs" redirect-carefully
85 </IfModule>
273e2c61
IB
86 '';
87
88
89 sslConf = ''
e7b890d0
IB
90 <IfModule mod_ssl.c>
91 SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000)
273e2c61 92
e7b890d0 93 Mutex posixsem
273e2c61 94
e7b890d0
IB
95 SSLRandomSeed startup builtin
96 SSLRandomSeed connect builtin
273e2c61 97
e7b890d0
IB
98 SSLProtocol ${cfg.sslProtocols}
99 SSLCipherSuite ${cfg.sslCiphers}
100 SSLHonorCipherOrder on
101 </IfModule>
273e2c61
IB
102 '';
103
104
105 mimeConf = ''
e7b890d0 106 TypesConfig ${pkg}/conf/mime.types
273e2c61
IB
107
108 AddType application/x-x509-ca-cert .crt
109 AddType application/x-pkcs7-crl .crl
110 AddType application/x-httpd-php .php .phtml
111
112 <IfModule mod_mime_magic.c>
e7b890d0 113 MIMEMagicFile ${pkg}/conf/magic
273e2c61
IB
114 </IfModule>
115 '';
116
e7b890d0
IB
117 mkVHostConf = hostOpts:
118 let
119 adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr;
120 listen = filter (listen: !listen.ssl) (mkListenInfo hostOpts);
121 listenSSL = filter (listen: listen.ssl) (mkListenInfo hostOpts);
122
123 useACME = hostOpts.enableACME || hostOpts.useACMEHost != null;
124 sslCertDir =
125 if hostOpts.enableACME then config.security.acme.certs.${hostOpts.hostName}.directory
126 else if hostOpts.useACMEHost != null then config.security.acme.certs.${hostOpts.useACMEHost}.directory
127 else abort "This case should never happen.";
128
129 sslServerCert = if useACME then "${sslCertDir}/full.pem" else hostOpts.sslServerCert;
130 sslServerKey = if useACME then "${sslCertDir}/key.pem" else hostOpts.sslServerKey;
131 sslServerChain = if useACME then "${sslCertDir}/fullchain.pem" else hostOpts.sslServerChain;
132
133 acmeChallenge = optionalString useACME ''
134 Alias /.well-known/acme-challenge/ "${hostOpts.acmeRoot}/.well-known/acme-challenge/"
135 <Directory "${hostOpts.acmeRoot}">
136 AllowOverride None
137 Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
138 Require method GET POST OPTIONS
139 Require all granted
140 </Directory>
141 '';
142 in
143 optionalString (listen != []) ''
144 <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listen}>
145 ServerName ${hostOpts.hostName}
146 ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
147 ServerAdmin ${adminAddr}
148 <IfModule mod_ssl.c>
149 SSLEngine off
150 </IfModule>
151 ${acmeChallenge}
152 ${if hostOpts.forceSSL then ''
153 <IfModule mod_rewrite.c>
154 RewriteEngine on
155 RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge [NC]
156 RewriteCond %{HTTPS} off
157 RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
158 </IfModule>
159 '' else mkVHostCommonConf hostOpts}
160 </VirtualHost>
161 '' +
162 optionalString (listenSSL != []) ''
163 <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listenSSL}>
164 ServerName ${hostOpts.hostName}
165 ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
166 ServerAdmin ${adminAddr}
167 SSLEngine on
168 SSLCertificateFile ${sslServerCert}
169 SSLCertificateKeyFile ${sslServerKey}
170 ${optionalString (sslServerChain != null) "SSLCertificateChainFile ${sslServerChain}"}
171 ${optionalString hostOpts.http2 "Protocols h2 h2c http/1.1"}
172 ${acmeChallenge}
173 ${mkVHostCommonConf hostOpts}
174 </VirtualHost>
175 ''
176 ;
177
178 mkVHostCommonConf = hostOpts:
179 let
180 documentRoot = if hostOpts.documentRoot != null
181 then hostOpts.documentRoot
182 else pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out"
183 ;
184
185 mkLocations = locations: concatStringsSep "\n" (map (config: ''
186 <Location ${config.location}>
187 ${optionalString (config.proxyPass != null) ''
188 <IfModule mod_proxy.c>
189 ProxyPass ${config.proxyPass}
190 ProxyPassReverse ${config.proxyPass}
191 </IfModule>
192 ''}
193 ${optionalString (config.index != null) ''
194 <IfModule mod_dir.c>
195 DirectoryIndex ${config.index}
196 </IfModule>
197 ''}
198 ${optionalString (config.alias != null) ''
199 <IfModule mod_alias.c>
200 Alias "${config.alias}"
201 </IfModule>
202 ''}
203 ${config.extraConfig}
204 </Location>
205 '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
206 in
207 ''
208 ${optionalString cfg.logPerVirtualHost ''
209 ErrorLog ${cfg.logDir}/error-${hostOpts.hostName}.log
210 CustomLog ${cfg.logDir}/access-${hostOpts.hostName}.log ${hostOpts.logFormat}
211 ''}
212
213 ${optionalString (hostOpts.robotsEntries != "") ''
214 Alias /robots.txt ${pkgs.writeText "robots.txt" hostOpts.robotsEntries}
215 ''}
216
217 DocumentRoot "${documentRoot}"
218
219 <Directory "${documentRoot}">
220 Options Indexes FollowSymLinks
221 AllowOverride None
222 Require all granted
223 </Directory>
224
225 ${optionalString hostOpts.enableUserDir ''
226 UserDir public_html
227 UserDir disabled root
228 <Directory "/home/*/public_html">
229 AllowOverride FileInfo AuthConfig Limit Indexes
230 Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
231 <Limit GET POST OPTIONS>
232 Require all granted
233 </Limit>
234 <LimitExcept GET POST OPTIONS>
235 Require all denied
236 </LimitExcept>
237 </Directory>
238 ''}
239
240 ${optionalString (hostOpts.globalRedirect != null && hostOpts.globalRedirect != "") ''
241 RedirectPermanent / ${hostOpts.globalRedirect}
242 ''}
243
244 ${
245 let makeDirConf = elem: ''
246 Alias ${elem.urlPath} ${elem.dir}/
247 <Directory ${elem.dir}>
248 Options +Indexes
249 Require all granted
250 AllowOverride All
251 </Directory>
252 '';
253 in concatMapStrings makeDirConf hostOpts.servedDirs
254 }
255
256 ${mkLocations hostOpts.locations}
257 ${hostOpts.extraConfig}
258 ''
259 ;
273e2c61
IB
260
261
262 confFile = pkgs.writeText "httpd.conf" ''
263
e7b890d0
IB
264 ServerRoot ${pkg}
265 ServerName ${config.networking.hostName}
266 DefaultRuntimeDir ${runtimeDir}/runtime
273e2c61 267
e7b890d0 268 PidFile ${runtimeDir}/httpd.pid
273e2c61 269
e7b890d0 270 ${optionalString (cfg.multiProcessingModule != "prefork") ''
273e2c61 271 # mod_cgid requires this.
e7b890d0 272 ScriptSock ${runtimeDir}/cgisock
273e2c61
IB
273 ''}
274
275 <IfModule prefork.c>
e7b890d0
IB
276 MaxClients ${toString cfg.maxClients}
277 MaxRequestsPerChild ${toString cfg.maxRequestsPerChild}
273e2c61
IB
278 </IfModule>
279
280 ${let
e7b890d0
IB
281 toStr = listen: "Listen ${listen.ip}:${toString listen.port} ${if listen.ssl then "https" else "http"}";
282 uniqueListen = uniqList {inputList = map toStr listenInfo;};
283 in concatStringsSep "\n" uniqueListen
273e2c61
IB
284 }
285
e7b890d0
IB
286 User ${cfg.user}
287 Group ${cfg.group}
273e2c61
IB
288
289 ${let
e7b890d0
IB
290 mkModule = module:
291 if isString module then { name = module; path = "${pkg}/modules/mod_${module}.so"; }
292 else if isAttrs module then { inherit (module) name path; }
293 else throw "Expecting either a string or attribute set including a name and path.";
294 in
295 concatMapStringsSep "\n" (module: "LoadModule ${module.name}_module ${module.path}") (unique (map mkModule modules))
273e2c61
IB
296 }
297
298 AddHandler type-map var
299
300 <Files ~ "^\.ht">
e7b890d0 301 Require all denied
273e2c61
IB
302 </Files>
303
304 ${mimeConf}
305 ${loggingConf}
306 ${browserHacks}
307
e7b890d0
IB
308 Include ${pkg}/conf/extra/httpd-default.conf
309 Include ${pkg}/conf/extra/httpd-autoindex.conf
310 Include ${pkg}/conf/extra/httpd-multilang-errordoc.conf
311 Include ${pkg}/conf/extra/httpd-languages.conf
273e2c61 312
62366a39
IB
313 TraceEnable off
314
e7b890d0 315 ${sslConf}
273e2c61
IB
316
317 # Fascist default - deny access to everything.
318 <Directory />
319 Options FollowSymLinks
320 AllowOverride None
e7b890d0 321 Require all denied
273e2c61
IB
322 </Directory>
323
e7b890d0 324 ${cfg.extraConfig}
273e2c61 325
e7b890d0 326 ${concatMapStringsSep "\n" mkVHostConf vhosts}
273e2c61
IB
327 '';
328
273e2c61
IB
329 # Generate the PHP configuration file. Should probably be factored
330 # out into a separate module.
331 phpIni = pkgs.runCommand "php.ini"
e7b890d0 332 { options = cfg.phpOptions;
9129f327 333 preferLocalBuild = true;
273e2c61
IB
334 }
335 ''
336 cat ${php}/etc/php.ini > $out
337 echo "$options" >> $out
338 '';
339
340in
341
342
343{
344
e7b890d0
IB
345 imports = [
346 (mkRemovedOptionModule [ "services" "httpd" httpdName "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.")
347 (mkRemovedOptionModule [ "services" "httpd" httpdName "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.")
348
349 # virtualHosts options
350 (mkRemovedOptionModule [ "services" "httpd" httpdName "documentRoot" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
351 (mkRemovedOptionModule [ "services" "httpd" httpdName "enableSSL" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
352 (mkRemovedOptionModule [ "services" "httpd" httpdName "enableUserDir" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
353 (mkRemovedOptionModule [ "services" "httpd" httpdName "globalRedirect" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
354 (mkRemovedOptionModule [ "services" "httpd" httpdName "hostName" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
355 (mkRemovedOptionModule [ "services" "httpd" httpdName "listen" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
356 (mkRemovedOptionModule [ "services" "httpd" httpdName "robotsEntries" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
357 (mkRemovedOptionModule [ "services" "httpd" httpdName "servedDirs" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
358 (mkRemovedOptionModule [ "services" "httpd" httpdName "servedFiles" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
359 (mkRemovedOptionModule [ "services" "httpd" httpdName "serverAliases" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
360 (mkRemovedOptionModule [ "services" "httpd" httpdName "sslServerCert" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
361 (mkRemovedOptionModule [ "services" "httpd" httpdName "sslServerChain" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
362 (mkRemovedOptionModule [ "services" "httpd" httpdName "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
363 ];
364
365 # interface
273e2c61
IB
366
367 options = {
368
daf64e3f 369 services.httpd."${httpdName}" = {
273e2c61 370
e7b890d0 371 enable = mkEnableOption "the Apache HTTP Server";
273e2c61
IB
372
373 package = mkOption {
374 type = types.package;
375 default = pkgs.apacheHttpd;
376 defaultText = "pkgs.apacheHttpd";
377 description = ''
378 Overridable attribute of the Apache HTTP Server package to use.
379 '';
380 };
381
382 configFile = mkOption {
383 type = types.path;
384 default = confFile;
385 defaultText = "confFile";
386 example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
387 description = ''
388 Override the configuration file used by Apache. By default,
389 NixOS generates one automatically.
390 '';
391 };
392
393 extraConfig = mkOption {
394 type = types.lines;
395 default = "";
396 description = ''
e7b890d0
IB
397 Configuration lines appended to the generated Apache
398 configuration file. Note that this mechanism will not work
273e2c61
IB
399 when <option>configFile</option> is overridden.
400 '';
401 };
402
403 extraModules = mkOption {
404 type = types.listOf types.unspecified;
405 default = [];
e7b890d0
IB
406 example = literalExample ''
407 [
408 "proxy_connect"
409 { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
410 ]
411 '';
273e2c61 412 description = ''
e7b890d0 413 Additional Apache modules to be used. These can be
273e2c61
IB
414 specified as a string in the case of modules distributed
415 with Apache, or as an attribute set specifying the
416 <varname>name</varname> and <varname>path</varname> of the
417 module.
418 '';
419 };
420
e7b890d0
IB
421 adminAddr = mkOption {
422 type = types.str;
423 example = "admin@example.org";
424 description = "E-mail address of the server administrator.";
425 };
426
427 logFormat = mkOption {
428 type = types.str;
429 default = "common";
430 example = "combined";
431 description = ''
432 Log format for log files. Possible values are: combined, common, referer, agent.
433 See <link xlink:href="https://httpd.apache.org/docs/2.4/logs.html"/> for more details.
434 '';
435 };
436
273e2c61
IB
437 logPerVirtualHost = mkOption {
438 type = types.bool;
e7b890d0 439 default = true;
273e2c61
IB
440 description = ''
441 If enabled, each virtual host gets its own
9129f327
IB
442 <filename>access.log</filename> and
443 <filename>error.log</filename>, namely suffixed by the
273e2c61
IB
444 <option>hostName</option> of the virtual host.
445 '';
446 };
447
448 user = mkOption {
449 type = types.str;
450 default = "wwwrun";
451 description = ''
e7b890d0 452 User account under which httpd runs.
273e2c61
IB
453 '';
454 };
455
456 group = mkOption {
457 type = types.str;
458 default = "wwwrun";
459 description = ''
e7b890d0 460 Group under which httpd runs.
273e2c61
IB
461 '';
462 };
463
464 logDir = mkOption {
465 type = types.path;
466 default = "/var/log/httpd";
467 description = ''
e7b890d0 468 Directory for Apache's log files. It is created automatically.
273e2c61
IB
469 '';
470 };
471
472 virtualHosts = mkOption {
258dd18b 473 type = with types; attrsOf (submodule (import <nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix>));
e7b890d0
IB
474 default = {
475 localhost = {
476 documentRoot = "${pkg}/htdocs";
477 };
478 };
479 example = literalExample ''
480 {
481 "foo.example.com" = {
482 forceSSL = true;
483 documentRoot = "/var/www/foo.example.com"
484 };
485 "bar.example.com" = {
486 addSSL = true;
487 documentRoot = "/var/www/bar.example.com";
273e2c61 488 };
273e2c61 489 }
e7b890d0 490 '';
273e2c61 491 description = ''
e7b890d0 492 Specification of the virtual hosts served by Apache. Each
273e2c61 493 element should be an attribute set specifying the
e7b890d0 494 configuration of the virtual host.
273e2c61
IB
495 '';
496 };
497
498 enableMellon = mkOption {
499 type = types.bool;
500 default = false;
501 description = "Whether to enable the mod_auth_mellon module.";
502 };
503
504 enablePHP = mkOption {
505 type = types.bool;
506 default = false;
507 description = "Whether to enable the PHP module.";
508 };
509
510 phpPackage = mkOption {
511 type = types.package;
512 default = pkgs.php;
513 defaultText = "pkgs.php";
514 description = ''
515 Overridable attribute of the PHP package to use.
516 '';
517 };
518
519 enablePerl = mkOption {
520 type = types.bool;
521 default = false;
522 description = "Whether to enable the Perl module (mod_perl).";
523 };
524
525 phpOptions = mkOption {
526 type = types.lines;
527 default = "";
528 example =
529 ''
530 date.timezone = "CET"
531 '';
e7b890d0
IB
532 description = ''
533 Options appended to the PHP configuration file <filename>php.ini</filename>.
534 '';
273e2c61
IB
535 };
536
537 multiProcessingModule = mkOption {
e7b890d0 538 type = types.enum [ "event" "prefork" "worker" ];
273e2c61
IB
539 default = "prefork";
540 example = "worker";
541 description =
542 ''
e7b890d0 543 Multi-processing module to be used by Apache. Available
273e2c61
IB
544 modules are <literal>prefork</literal> (the default;
545 handles each request in a separate child process),
546 <literal>worker</literal> (hybrid approach that starts a
547 number of child processes each running a number of
548 threads) and <literal>event</literal> (a recent variant of
549 <literal>worker</literal> that handles persistent
550 connections more efficiently).
551 '';
552 };
553
554 maxClients = mkOption {
555 type = types.int;
556 default = 150;
557 example = 8;
558 description = "Maximum number of httpd processes (prefork)";
559 };
560
561 maxRequestsPerChild = mkOption {
562 type = types.int;
563 default = 0;
564 example = 500;
e7b890d0
IB
565 description = ''
566 Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited.
567 '';
273e2c61 568 };
62366a39
IB
569
570 sslCiphers = mkOption {
571 type = types.str;
572 default = "HIGH:!aNULL:!MD5:!EXP";
573 description = "Cipher Suite available for negotiation in SSL proxy handshake.";
574 };
575
576 sslProtocols = mkOption {
577 type = types.str;
e7b890d0 578 default = "All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1";
62366a39
IB
579 example = "All -SSLv2 -SSLv3";
580 description = "Allowed SSL/TLS protocol versions.";
581 };
e7b890d0 582 };
273e2c61
IB
583
584 };
585
e7b890d0 586 # implementation
273e2c61 587
e7b890d0 588 config = mkIf cfg.enable {
273e2c61 589
e7b890d0
IB
590 assertions = [
591 {
592 assertion = all (hostOpts: !hostOpts.enableSSL) vhosts;
593 message = ''
594 The option `services.httpd.virtualHosts.<name>.enableSSL` no longer has any effect; please remove it.
595 Select one of `services.httpd.virtualHosts.<name>.addSSL`, `services.httpd.virtualHosts.<name>.forceSSL`,
596 or `services.httpd.virtualHosts.<name>.onlySSL`.
597 '';
598 }
599 {
600 assertion = all (hostOpts: with hostOpts; !(addSSL && onlySSL) && !(forceSSL && onlySSL) && !(addSSL && forceSSL)) vhosts;
601 message = ''
602 Options `services.httpd.virtualHosts.<name>.addSSL`,
603 `services.httpd.virtualHosts.<name>.onlySSL` and `services.httpd.virtualHosts.<name>.forceSSL`
604 are mutually exclusive.
605 '';
606 }
607 {
608 assertion = all (hostOpts: !(hostOpts.enableACME && hostOpts.useACMEHost != null)) vhosts;
609 message = ''
610 Options `services.httpd.virtualHosts.<name>.enableACME` and
611 `services.httpd.virtualHosts.<name>.useACMEHost` are mutually exclusive.
612 '';
613 }
614 ];
273e2c61 615
e7b890d0
IB
616 warnings =
617 mapAttrsToList (name: hostOpts: ''
618 Using config.services.httpd.virtualHosts."${name}".servedFiles is deprecated and will become unsupported in a future release. Your configuration will continue to work as is but please migrate your configuration to config.services.httpd.virtualHosts."${name}".locations before the 20.09 release of NixOS.
619 '') (filterAttrs (name: hostOpts: hostOpts.servedFiles != []) cfg.virtualHosts);
273e2c61 620
e7b890d0
IB
621 users.users = optionalAttrs (withUsers && cfg.user == "wwwrun") {
622 wwwrun = {
623 group = cfg.group;
273e2c61
IB
624 description = "Apache httpd user";
625 uid = config.ids.uids.wwwrun;
e7b890d0
IB
626 };
627 };
628
629 users.groups = optionalAttrs (withUsers && cfg.group == "wwwrun") {
630 wwwrun.gid = config.ids.gids.wwwrun;
631 };
632
633 security.acme.certs = mapAttrs (name: hostOpts: {
634 user = cfg.user;
635 group = mkDefault cfg.group;
636 email = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr;
637 webroot = hostOpts.acmeRoot;
638 extraDomains = genAttrs hostOpts.serverAliases (alias: null);
639 postRun = "systemctl reload httpd.service";
640 }) (filterAttrs (name: hostOpts: hostOpts.enableACME) cfg.virtualHosts);
273e2c61 641
e7b890d0 642 environment.systemPackages = [ pkg ];
273e2c61 643
e7b890d0
IB
644 # required for "apachectl configtest"
645 environment.etc."httpd/httpd_${httpdName}.conf".source = httpdConf;
273e2c61 646
e7b890d0 647 services.httpd."${httpdName}" = { phpOptions =
273e2c61
IB
648 ''
649 ; Needed for PHP's mail() function.
650 sendmail_path = sendmail -t -i
9129f327
IB
651
652 ; Don't advertise PHP
653 expose_php = off
5400b9b6 654 '' + optionalString (config.time.timeZone != null) ''
273e2c61
IB
655
656 ; Apparently PHP doesn't use $TZ.
657 date.timezone = "${config.time.timeZone}"
658 '';
659
e7b890d0
IB
660 extraModules = mkBefore [
661 # HTTP authentication mechanisms: basic and digest.
662 "auth_basic" "auth_digest"
663
664 # Authentication: is the user who he claims to be?
665 "authn_file" "authn_dbm" "authn_anon"
666
667 # Authorization: is the user allowed access?
668 "authz_user" "authz_groupfile" "authz_host"
669
670 # Other modules.
671 "ext_filter" "include" "env" "mime_magic"
672 "cern_meta" "expires" "headers" "usertrack" "setenvif"
673 "dav" "status" "asis" "info" "dav_fs"
674 "vhost_alias" "imagemap" "actions" "speling"
675 "proxy" "proxy_http"
676 "cache" "cache_disk"
677
678 # For compatibility with old configurations, the new module mod_access_compat is provided.
679 "access_compat"
680 ];
681 };
682
683 systemd.tmpfiles.rules =
684 let
685 svc = config.systemd.services."httpd${httpdName}".serviceConfig;
686 in
687 [
688 "d '${cfg.logDir}' 0700 ${svc.User} ${svc.Group}"
689 "Z '${cfg.logDir}' - ${svc.User} ${svc.Group}"
690 ];
691
581c499c 692 systemd.services."httpd${httpdName}" =
e7b890d0
IB
693 let
694 vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts;
695 in
273e2c61
IB
696 { description = "Apache HTTPD";
697
698 wantedBy = [ "multi-user.target" ];
e7b890d0
IB
699 wants = concatLists (map (hostOpts: [ "acme-${hostOpts.hostName}.service" "acme-selfsigned-${hostOpts.hostName}.service" ]) vhostsACME);
700 after = [ "network.target" "fs.target" ] ++ map (hostOpts: "acme-selfsigned-${hostOpts.hostName}.service") vhostsACME;
273e2c61
IB
701
702 path =
e7b890d0
IB
703 [ pkg pkgs.coreutils pkgs.gnugrep ]
704 ++ optional cfg.enablePHP pkgs.system-sendmail; # Needed for PHP's mail() function.
273e2c61
IB
705
706 environment =
e7b890d0
IB
707 optionalAttrs cfg.enablePHP { PHPRC = phpIni; }
708 // optionalAttrs cfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; };
273e2c61
IB
709
710 preStart =
711 ''
273e2c61
IB
712 # Get rid of old semaphores. These tend to accumulate across
713 # server restarts, eventually preventing it from restarting
714 # successfully.
e7b890d0 715 for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${cfg.user} ' | cut -f2 -d ' '); do
273e2c61
IB
716 ${pkgs.utillinux}/bin/ipcrm -s $i
717 done
273e2c61
IB
718 '';
719
e7b890d0
IB
720 serviceConfig = {
721 ExecStart = "@${pkg}/bin/httpd httpd -f ${httpdConf}";
722 ExecStop = "${pkg}/bin/httpd -f ${httpdConf} -k graceful-stop";
723 ExecReload = "${pkg}/bin/httpd -f ${httpdConf} -k graceful";
724 User = "root";
725 Group = cfg.group;
726 Type = "forking";
727 PIDFile = "${runtimeDir}/httpd.pid";
728 Restart = "always";
729 RestartSec = "5s";
730 RuntimeDirectory = "httpd_${httpdName} httpd_${httpdName}/runtime";
731 RuntimeDirectoryMode = "0750";
732 };
273e2c61
IB
733 };
734
735 };
736}