]> git.immae.eu Git - perso/Immae/Config/Nix/NUR.git/blob - modules/websites/httpd-service-builder.nix
Prepare upgrade to nixos 20.03
[perso/Immae/Config/Nix/NUR.git] / modules / websites / httpd-service-builder.nix
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 }:
4 { config, lib, pkgs, ... }:
5
6 with lib;
7
8 let
9
10 cfg = config.services.httpd."${httpdName}";
11
12 runtimeDir = "/run/httpd_${httpdName}";
13
14 pkg = cfg.package.out;
15
16 httpdConf = cfg.configFile;
17
18 php = cfg.phpPackage.override { apacheHttpd = pkg.dev; /* otherwise it only gets .out */ };
19
20 phpMajorVersion = lib.versions.major (lib.getVersion php);
21
22 mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; };
23
24 vhosts = attrValues cfg.virtualHosts;
25
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 );
32
33 listenInfo = unique (concatMap mkListenInfo vhosts);
34
35 enableHttp2 = any (vhost: vhost.http2) vhosts;
36 enableSSL = any (listen: listen.ssl) listenInfo;
37 enableUserDir = any (vhost: vhost.enableUserDir) vhosts;
38
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}"
48 ]
49 ++ (if cfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
50 ++ optional enableHttp2 "http2"
51 ++ optional enableSSL "ssl"
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;
57
58 loggingConf = (if cfg.logFormat != "none" then ''
59 ErrorLog ${cfg.logDir}/error.log
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
68 CustomLog ${cfg.logDir}/access.log ${cfg.logFormat}
69 '' else ''
70 ErrorLog /dev/null
71 '');
72
73
74 browserHacks = ''
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>
86 '';
87
88
89 sslConf = ''
90 <IfModule mod_ssl.c>
91 SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000)
92
93 Mutex posixsem
94
95 SSLRandomSeed startup builtin
96 SSLRandomSeed connect builtin
97
98 SSLProtocol ${cfg.sslProtocols}
99 SSLCipherSuite ${cfg.sslCiphers}
100 SSLHonorCipherOrder on
101 </IfModule>
102 '';
103
104
105 mimeConf = ''
106 TypesConfig ${pkg}/conf/mime.types
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>
113 MIMEMagicFile ${pkg}/conf/magic
114 </IfModule>
115 '';
116
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 ;
260
261
262 confFile = pkgs.writeText "httpd.conf" ''
263
264 ServerRoot ${pkg}
265 ServerName ${config.networking.hostName}
266 DefaultRuntimeDir ${runtimeDir}/runtime
267
268 PidFile ${runtimeDir}/httpd.pid
269
270 ${optionalString (cfg.multiProcessingModule != "prefork") ''
271 # mod_cgid requires this.
272 ScriptSock ${runtimeDir}/cgisock
273 ''}
274
275 <IfModule prefork.c>
276 MaxClients ${toString cfg.maxClients}
277 MaxRequestsPerChild ${toString cfg.maxRequestsPerChild}
278 </IfModule>
279
280 ${let
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
284 }
285
286 User ${cfg.user}
287 Group ${cfg.group}
288
289 ${let
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))
296 }
297
298 AddHandler type-map var
299
300 <Files ~ "^\.ht">
301 Require all denied
302 </Files>
303
304 ${mimeConf}
305 ${loggingConf}
306 ${browserHacks}
307
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
312
313 TraceEnable off
314
315 ${sslConf}
316
317 # Fascist default - deny access to everything.
318 <Directory />
319 Options FollowSymLinks
320 AllowOverride None
321 Require all denied
322 </Directory>
323
324 ${cfg.extraConfig}
325
326 ${concatMapStringsSep "\n" mkVHostConf vhosts}
327 '';
328
329 # Generate the PHP configuration file. Should probably be factored
330 # out into a separate module.
331 phpIni = pkgs.runCommand "php.ini"
332 { options = cfg.phpOptions;
333 preferLocalBuild = true;
334 }
335 ''
336 cat ${php}/etc/php.ini > $out
337 echo "$options" >> $out
338 '';
339
340 in
341
342
343 {
344
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
366
367 options = {
368
369 services.httpd."${httpdName}" = {
370
371 enable = mkEnableOption "the Apache HTTP Server";
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 = ''
397 Configuration lines appended to the generated Apache
398 configuration file. Note that this mechanism will not work
399 when <option>configFile</option> is overridden.
400 '';
401 };
402
403 extraModules = mkOption {
404 type = types.listOf types.unspecified;
405 default = [];
406 example = literalExample ''
407 [
408 "proxy_connect"
409 { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
410 ]
411 '';
412 description = ''
413 Additional Apache modules to be used. These can be
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
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
437 logPerVirtualHost = mkOption {
438 type = types.bool;
439 default = true;
440 description = ''
441 If enabled, each virtual host gets its own
442 <filename>access.log</filename> and
443 <filename>error.log</filename>, namely suffixed by the
444 <option>hostName</option> of the virtual host.
445 '';
446 };
447
448 user = mkOption {
449 type = types.str;
450 default = "wwwrun";
451 description = ''
452 User account under which httpd runs.
453 '';
454 };
455
456 group = mkOption {
457 type = types.str;
458 default = "wwwrun";
459 description = ''
460 Group under which httpd runs.
461 '';
462 };
463
464 logDir = mkOption {
465 type = types.path;
466 default = "/var/log/httpd";
467 description = ''
468 Directory for Apache's log files. It is created automatically.
469 '';
470 };
471
472 virtualHosts = mkOption {
473 type = with types; attrsOf (submodule (import ./vhost-options.nix));
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";
488 };
489 }
490 '';
491 description = ''
492 Specification of the virtual hosts served by Apache. Each
493 element should be an attribute set specifying the
494 configuration of the virtual host.
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 '';
532 description = ''
533 Options appended to the PHP configuration file <filename>php.ini</filename>.
534 '';
535 };
536
537 multiProcessingModule = mkOption {
538 type = types.enum [ "event" "prefork" "worker" ];
539 default = "prefork";
540 example = "worker";
541 description =
542 ''
543 Multi-processing module to be used by Apache. Available
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;
565 description = ''
566 Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited.
567 '';
568 };
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;
578 default = "All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1";
579 example = "All -SSLv2 -SSLv3";
580 description = "Allowed SSL/TLS protocol versions.";
581 };
582 };
583
584 };
585
586 # implementation
587
588 config = mkIf cfg.enable {
589
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 ];
615
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);
620
621 users.users = optionalAttrs (withUsers && cfg.user == "wwwrun") {
622 wwwrun = {
623 group = cfg.group;
624 description = "Apache httpd user";
625 uid = config.ids.uids.wwwrun;
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);
641
642 environment.systemPackages = [ pkg ];
643
644 # required for "apachectl configtest"
645 environment.etc."httpd/httpd_${httpdName}.conf".source = httpdConf;
646
647 services.httpd."${httpdName}" = { phpOptions =
648 ''
649 ; Needed for PHP's mail() function.
650 sendmail_path = sendmail -t -i
651
652 ; Don't advertise PHP
653 expose_php = off
654 '' + optionalString (config.time.timeZone != null) ''
655
656 ; Apparently PHP doesn't use $TZ.
657 date.timezone = "${config.time.timeZone}"
658 '';
659
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
692 systemd.services."httpd${httpdName}" =
693 let
694 vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts;
695 in
696 { description = "Apache HTTPD";
697
698 wantedBy = [ "multi-user.target" ];
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;
701
702 path =
703 [ pkg pkgs.coreutils pkgs.gnugrep ]
704 ++ optional cfg.enablePHP pkgs.system-sendmail; # Needed for PHP's mail() function.
705
706 environment =
707 optionalAttrs cfg.enablePHP { PHPRC = phpIni; }
708 // optionalAttrs cfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; };
709
710 preStart =
711 ''
712 # Get rid of old semaphores. These tend to accumulate across
713 # server restarts, eventually preventing it from restarting
714 # successfully.
715 for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${cfg.user} ' | cut -f2 -d ' '); do
716 ${pkgs.utillinux}/bin/ipcrm -s $i
717 done
718 '';
719
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 };
733 };
734
735 };
736 }