]>
Commit | Line | Data |
---|---|---|
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 | ||
6 | with lib; | |
7 | ||
8 | let | |
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 | ||
340 | in | |
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 | } |