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