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