summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/websites/default.nix58
-rw-r--r--modules/websites/httpd-service-builder.nix816
-rw-r--r--modules/websites/httpd-service-builder.patch150
-rw-r--r--modules/websites/location-options.nix54
-rw-r--r--modules/websites/vhost-options.nix275
5 files changed, 925 insertions, 428 deletions
diff --git a/modules/websites/default.nix b/modules/websites/default.nix
index 3f46e65d..d5a0f635 100644
--- a/modules/websites/default.nix
+++ b/modules/websites/default.nix
@@ -82,6 +82,14 @@ in
82 certName = mkOption { type = str; }; 82 certName = mkOption { type = str; };
83 hosts = mkOption { type = listOf str; }; 83 hosts = mkOption { type = listOf str; };
84 root = mkOption { type = nullOr path; }; 84 root = mkOption { type = nullOr path; };
85 forceSSL = mkOption {
86 type = bool;
87 default = true;
88 description = ''
89 Automatically create a corresponding non-ssl vhost
90 that will only redirect to the ssl version
91 '';
92 };
85 extraConfig = mkOption { type = listOf lines; default = []; }; 93 extraConfig = mkOption { type = listOf lines; default = []; };
86 }; 94 };
87 }; 95 };
@@ -115,6 +123,14 @@ in
115 }; 123 };
116 hosts = mkOption { type = listOf str; }; 124 hosts = mkOption { type = listOf str; };
117 root = mkOption { type = nullOr path; }; 125 root = mkOption { type = nullOr path; };
126 forceSSL = mkOption {
127 type = bool;
128 default = true;
129 description = ''
130 Automatically create a corresponding non-ssl vhost
131 that will only redirect to the ssl version
132 '';
133 };
118 extraConfig = mkOption { type = listOf lines; default = []; }; 134 extraConfig = mkOption { type = listOf lines; default = []; };
119 }; 135 };
120 }); 136 });
@@ -143,26 +159,9 @@ in
143 }; 159 };
144 160
145 config.services.httpd = let 161 config.services.httpd = let
146 redirectVhost = ips: { # Should go last, catchall http -> https redirect
147 listen = map (ip: { inherit ip; port = 80; }) ips;
148 hostName = "redirectSSL";
149 serverAliases = [ "*" ];
150 enableSSL = false;
151 logFormat = "combinedVhost";
152 documentRoot = "/var/lib/acme/acme-challenge";
153 extraConfig = ''
154 RewriteEngine on
155 RewriteCond "%{REQUEST_URI}" "!^/\.well-known"
156 RewriteRule ^(.+) https://%{HTTP_HOST}$1 [R=301]
157 # To redirect in specific "VirtualHost *:80", do
158 # RedirectMatch 301 ^/((?!\.well-known.*$).*)$ https://host/$1
159 # rather than rewrite
160 '';
161 };
162 nosslVhost = ips: cfg: { 162 nosslVhost = ips: cfg: {
163 listen = map (ip: { inherit ip; port = 80; }) ips; 163 listen = map (ip: { inherit ip; port = 80; }) ips;
164 hostName = cfg.host; 164 hostName = cfg.host;
165 enableSSL = false;
166 logFormat = "combinedVhost"; 165 logFormat = "combinedVhost";
167 documentRoot = cfg.root; 166 documentRoot = cfg.root;
168 extraConfig = '' 167 extraConfig = ''
@@ -177,19 +176,18 @@ in
177 ''; 176 '';
178 }; 177 };
179 toVhost = ips: vhostConf: { 178 toVhost = ips: vhostConf: {
180 enableSSL = true; 179 forceSSL = vhostConf.forceSSL or true;
181 sslServerCert = "${config.security.acme.certs."${vhostConf.certName}".directory}/cert.pem"; 180 useACMEHost = vhostConf.certName;
182 sslServerKey = "${config.security.acme.certs."${vhostConf.certName}".directory}/key.pem";
183 sslServerChain = "${config.security.acme.certs."${vhostConf.certName}".directory}/chain.pem";
184 logFormat = "combinedVhost"; 181 logFormat = "combinedVhost";
185 listen = map (ip: { inherit ip; port = 443; }) ips; 182 listen = if vhostConf.forceSSL
183 then lists.flatten (map (ip: [{ inherit ip; port = 443; ssl = true; } { inherit ip; port = 80; }]) ips)
184 else map (ip: { inherit ip; port = 443; ssl = true; }) ips;
186 hostName = builtins.head vhostConf.hosts; 185 hostName = builtins.head vhostConf.hosts;
187 serverAliases = builtins.tail vhostConf.hosts or []; 186 serverAliases = builtins.tail vhostConf.hosts or [];
188 documentRoot = vhostConf.root; 187 documentRoot = vhostConf.root;
189 extraConfig = builtins.concatStringsSep "\n" vhostConf.extraConfig; 188 extraConfig = builtins.concatStringsSep "\n" vhostConf.extraConfig;
190 }; 189 };
191 toVhostNoSSL = ips: vhostConf: { 190 toVhostNoSSL = ips: vhostConf: {
192 enableSSL = false;
193 logFormat = "combinedVhost"; 191 logFormat = "combinedVhost";
194 listen = map (ip: { inherit ip; port = 80; }) ips; 192 listen = map (ip: { inherit ip; port = 80; }) ips;
195 hostName = builtins.head vhostConf.hosts; 193 hostName = builtins.head vhostConf.hosts;
@@ -200,8 +198,6 @@ in
200 in attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair 198 in attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair
201 icfg.httpdName (mkIf icfg.enable { 199 icfg.httpdName (mkIf icfg.enable {
202 enable = true; 200 enable = true;
203 listen = map (ip: { inherit ip; port = 443; }) icfg.ips;
204 stateDir = "/run/httpd_${name}";
205 logPerVirtualHost = true; 201 logPerVirtualHost = true;
206 multiProcessingModule = "worker"; 202 multiProcessingModule = "worker";
207 # https://ssl-config.mozilla.org/#server=apache&version=2.4.41&config=intermediate&openssl=1.0.2t&guideline=5.4 203 # https://ssl-config.mozilla.org/#server=apache&version=2.4.41&config=intermediate&openssl=1.0.2t&guideline=5.4
@@ -216,11 +212,13 @@ in
216 logFormat = "combinedVhost"; 212 logFormat = "combinedVhost";
217 extraModules = lists.unique icfg.modules; 213 extraModules = lists.unique icfg.modules;
218 extraConfig = builtins.concatStringsSep "\n" icfg.extraConfig; 214 extraConfig = builtins.concatStringsSep "\n" icfg.extraConfig;
219 virtualHosts = [ (toVhost icfg.ips icfg.fallbackVhost) ] 215
220 ++ optionals (icfg.nosslVhost.enable) [ (nosslVhost icfg.ips icfg.nosslVhost) ] 216 virtualHosts = with attrsets; {
221 ++ (attrsets.mapAttrsToList (n: v: toVhostNoSSL icfg.ips v) icfg.vhostNoSSLConfs) 217 ___fallbackVhost = toVhost icfg.ips icfg.fallbackVhost;
222 ++ (attrsets.mapAttrsToList (n: v: toVhost icfg.ips v) icfg.vhostConfs) 218 } // (optionalAttrs icfg.nosslVhost.enable {
223 ++ [ (redirectVhost icfg.ips) ]; 219 nosslVhost = nosslVhost icfg.ips icfg.nosslVhost;
220 }) // (mapAttrs' (n: v: nameValuePair ("nossl_" + n) (toVhostNoSSL icfg.ips v)) icfg.vhostNoSSLConfs)
221 // (mapAttrs' (n: v: nameValuePair ("ssl_" + n) (toVhost icfg.ips v)) icfg.vhostConfs);
224 }) 222 })
225 ) cfg.env; 223 ) cfg.env;
226 224
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 };
diff --git a/modules/websites/httpd-service-builder.patch b/modules/websites/httpd-service-builder.patch
new file mode 100644
index 00000000..f0ad8366
--- /dev/null
+++ b/modules/websites/httpd-service-builder.patch
@@ -0,0 +1,150 @@
1--- /nix/store/xj651aslybfsma20hpbi5nznfcffq8ky-nixexprs.tar.xz/nixos/modules/services/web-servers/apache-httpd/default.nix 1970-01-01 01:00:01.000000000 +0100
2+++ modules/websites/httpd-service-builder.nix 2020-04-04 03:08:29.068490345 +0200
3@@ -1,12 +1,15 @@
4+# to help backporting this builder should stay as close as possible to
5+# nixos/modules/services/web-servers/apache-httpd/default.nix
6+{ httpdName, withUsers ? true }:
7 { config, lib, pkgs, ... }:
8
9 with lib;
10
11 let
12
13- cfg = config.services.httpd;
14+ cfg = config.services.httpd."${httpdName}";
15
16- runtimeDir = "/run/httpd";
17+ runtimeDir = "/run/httpd_${httpdName}";
18
19 pkg = cfg.package.out;
20
21@@ -318,13 +321,6 @@
22 Require all denied
23 </Directory>
24
25- # But do allow access to files in the store so that we don't have
26- # to generate <Directory> clauses for every generated file that we
27- # want to serve.
28- <Directory /nix/store>
29- Require all granted
30- </Directory>
31-
32 ${cfg.extraConfig}
33
34 ${concatMapStringsSep "\n" mkVHostConf vhosts}
35@@ -347,30 +343,30 @@
36 {
37
38 imports = [
39- (mkRemovedOptionModule [ "services" "httpd" "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.")
40- (mkRemovedOptionModule [ "services" "httpd" "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.")
41+ (mkRemovedOptionModule [ "services" "httpd" httpdName "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.")
42+ (mkRemovedOptionModule [ "services" "httpd" httpdName "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.")
43
44 # virtualHosts options
45- (mkRemovedOptionModule [ "services" "httpd" "documentRoot" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
46- (mkRemovedOptionModule [ "services" "httpd" "enableSSL" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
47- (mkRemovedOptionModule [ "services" "httpd" "enableUserDir" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
48- (mkRemovedOptionModule [ "services" "httpd" "globalRedirect" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
49- (mkRemovedOptionModule [ "services" "httpd" "hostName" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
50- (mkRemovedOptionModule [ "services" "httpd" "listen" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
51- (mkRemovedOptionModule [ "services" "httpd" "robotsEntries" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
52- (mkRemovedOptionModule [ "services" "httpd" "servedDirs" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
53- (mkRemovedOptionModule [ "services" "httpd" "servedFiles" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
54- (mkRemovedOptionModule [ "services" "httpd" "serverAliases" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
55- (mkRemovedOptionModule [ "services" "httpd" "sslServerCert" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
56- (mkRemovedOptionModule [ "services" "httpd" "sslServerChain" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
57- (mkRemovedOptionModule [ "services" "httpd" "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
58+ (mkRemovedOptionModule [ "services" "httpd" httpdName "documentRoot" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
59+ (mkRemovedOptionModule [ "services" "httpd" httpdName "enableSSL" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
60+ (mkRemovedOptionModule [ "services" "httpd" httpdName "enableUserDir" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
61+ (mkRemovedOptionModule [ "services" "httpd" httpdName "globalRedirect" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
62+ (mkRemovedOptionModule [ "services" "httpd" httpdName "hostName" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
63+ (mkRemovedOptionModule [ "services" "httpd" httpdName "listen" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
64+ (mkRemovedOptionModule [ "services" "httpd" httpdName "robotsEntries" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
65+ (mkRemovedOptionModule [ "services" "httpd" httpdName "servedDirs" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
66+ (mkRemovedOptionModule [ "services" "httpd" httpdName "servedFiles" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
67+ (mkRemovedOptionModule [ "services" "httpd" httpdName "serverAliases" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
68+ (mkRemovedOptionModule [ "services" "httpd" httpdName "sslServerCert" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
69+ (mkRemovedOptionModule [ "services" "httpd" httpdName "sslServerChain" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
70+ (mkRemovedOptionModule [ "services" "httpd" httpdName "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
71 ];
72
73 # interface
74
75 options = {
76
77- services.httpd = {
78+ services.httpd."${httpdName}" = {
79
80 enable = mkEnableOption "the Apache HTTP Server";
81
82@@ -622,7 +618,7 @@
83 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.
84 '') (filterAttrs (name: hostOpts: hostOpts.servedFiles != []) cfg.virtualHosts);
85
86- users.users = optionalAttrs (cfg.user == "wwwrun") {
87+ users.users = optionalAttrs (withUsers && cfg.user == "wwwrun") {
88 wwwrun = {
89 group = cfg.group;
90 description = "Apache httpd user";
91@@ -630,7 +626,7 @@
92 };
93 };
94
95- users.groups = optionalAttrs (cfg.group == "wwwrun") {
96+ users.groups = optionalAttrs (withUsers && cfg.group == "wwwrun") {
97 wwwrun.gid = config.ids.gids.wwwrun;
98 };
99
100@@ -646,9 +642,9 @@
101 environment.systemPackages = [ pkg ];
102
103 # required for "apachectl configtest"
104- environment.etc."httpd/httpd.conf".source = httpdConf;
105+ environment.etc."httpd/httpd_${httpdName}.conf".source = httpdConf;
106
107- services.httpd.phpOptions =
108+ services.httpd."${httpdName}" = { phpOptions =
109 ''
110 ; Needed for PHP's mail() function.
111 sendmail_path = sendmail -t -i
112@@ -661,7 +657,7 @@
113 date.timezone = "${config.time.timeZone}"
114 '';
115
116- services.httpd.extraModules = mkBefore [
117+ extraModules = mkBefore [
118 # HTTP authentication mechanisms: basic and digest.
119 "auth_basic" "auth_digest"
120
121@@ -682,17 +678,18 @@
122 # For compatibility with old configurations, the new module mod_access_compat is provided.
123 "access_compat"
124 ];
125+ };
126
127 systemd.tmpfiles.rules =
128 let
129- svc = config.systemd.services.httpd.serviceConfig;
130+ svc = config.systemd.services."httpd${httpdName}".serviceConfig;
131 in
132 [
133 "d '${cfg.logDir}' 0700 ${svc.User} ${svc.Group}"
134 "Z '${cfg.logDir}' - ${svc.User} ${svc.Group}"
135 ];
136
137- systemd.services.httpd =
138+ systemd.services."httpd${httpdName}" =
139 let
140 vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts;
141 in
142@@ -730,7 +727,7 @@
143 PIDFile = "${runtimeDir}/httpd.pid";
144 Restart = "always";
145 RestartSec = "5s";
146- RuntimeDirectory = "httpd httpd/runtime";
147+ RuntimeDirectory = "httpd_${httpdName} httpd_${httpdName}/runtime";
148 RuntimeDirectoryMode = "0750";
149 };
150 };
diff --git a/modules/websites/location-options.nix b/modules/websites/location-options.nix
new file mode 100644
index 00000000..8ea88f94
--- /dev/null
+++ b/modules/websites/location-options.nix
@@ -0,0 +1,54 @@
1{ config, lib, name, ... }:
2let
3 inherit (lib) mkOption types;
4in
5{
6 options = {
7
8 proxyPass = mkOption {
9 type = with types; nullOr str;
10 default = null;
11 example = "http://www.example.org/";
12 description = ''
13 Sets up a simple reverse proxy as described by <link xlink:href="https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html#simple" />.
14 '';
15 };
16
17 index = mkOption {
18 type = with types; nullOr str;
19 default = null;
20 example = "index.php index.html";
21 description = ''
22 Adds DirectoryIndex directive. See <link xlink:href="https://httpd.apache.org/docs/2.4/mod/mod_dir.html#directoryindex" />.
23 '';
24 };
25
26 alias = mkOption {
27 type = with types; nullOr path;
28 default = null;
29 example = "/your/alias/directory";
30 description = ''
31 Alias directory for requests. See <link xlink:href="https://httpd.apache.org/docs/2.4/mod/mod_alias.html#alias" />.
32 '';
33 };
34
35 extraConfig = mkOption {
36 type = types.lines;
37 default = "";
38 description = ''
39 These lines go to the end of the location verbatim.
40 '';
41 };
42
43 priority = mkOption {
44 type = types.int;
45 default = 1000;
46 description = ''
47 Order of this location block in relation to the others in the vhost.
48 The semantics are the same as with `lib.mkOrder`. Smaller values have
49 a greater priority.
50 '';
51 };
52
53 };
54}
diff --git a/modules/websites/vhost-options.nix b/modules/websites/vhost-options.nix
new file mode 100644
index 00000000..263980ad
--- /dev/null
+++ b/modules/websites/vhost-options.nix
@@ -0,0 +1,275 @@
1{ config, lib, name, ... }:
2let
3 inherit (lib) literalExample mkOption nameValuePair types;
4in
5{
6 options = {
7
8 hostName = mkOption {
9 type = types.str;
10 default = name;
11 description = "Canonical hostname for the server.";
12 };
13
14 serverAliases = mkOption {
15 type = types.listOf types.str;
16 default = [];
17 example = ["www.example.org" "www.example.org:8080" "example.org"];
18 description = ''
19 Additional names of virtual hosts served by this virtual host configuration.
20 '';
21 };
22
23 listen = mkOption {
24 type = with types; listOf (submodule ({
25 options = {
26 port = mkOption {
27 type = types.port;
28 description = "Port to listen on";
29 };
30 ip = mkOption {
31 type = types.str;
32 default = "*";
33 description = "IP to listen on. 0.0.0.0 for IPv4 only, * for all.";
34 };
35 ssl = mkOption {
36 type = types.bool;
37 default = false;
38 description = "Whether to enable SSL (https) support.";
39 };
40 };
41 }));
42 default = [];
43 example = [
44 { ip = "195.154.1.1"; port = 443; ssl = true;}
45 { ip = "192.154.1.1"; port = 80; }
46 { ip = "*"; port = 8080; }
47 ];
48 description = ''
49 Listen addresses and ports for this virtual host.
50 <note><para>
51 This option overrides <literal>addSSL</literal>, <literal>forceSSL</literal> and <literal>onlySSL</literal>.
52 </para></note>
53 '';
54 };
55
56 enableSSL = mkOption {
57 type = types.bool;
58 visible = false;
59 default = false;
60 };
61
62 addSSL = mkOption {
63 type = types.bool;
64 default = false;
65 description = ''
66 Whether to enable HTTPS in addition to plain HTTP. This will set defaults for
67 <literal>listen</literal> to listen on all interfaces on the respective default
68 ports (80, 443).
69 '';
70 };
71
72 onlySSL = mkOption {
73 type = types.bool;
74 default = false;
75 description = ''
76 Whether to enable HTTPS and reject plain HTTP connections. This will set
77 defaults for <literal>listen</literal> to listen on all interfaces on port 443.
78 '';
79 };
80
81 forceSSL = mkOption {
82 type = types.bool;
83 default = false;
84 description = ''
85 Whether to add a separate nginx server block that permanently redirects (301)
86 all plain HTTP traffic to HTTPS. This will set defaults for
87 <literal>listen</literal> to listen on all interfaces on the respective default
88 ports (80, 443), where the non-SSL listens are used for the redirect vhosts.
89 '';
90 };
91
92 enableACME = mkOption {
93 type = types.bool;
94 default = false;
95 description = ''
96 Whether to ask Let's Encrypt to sign a certificate for this vhost.
97 Alternately, you can use an existing certificate through <option>useACMEHost</option>.
98 '';
99 };
100
101 useACMEHost = mkOption {
102 type = types.nullOr types.str;
103 default = null;
104 description = ''
105 A host of an existing Let's Encrypt certificate to use.
106 This is useful if you have many subdomains and want to avoid hitting the
107 <link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>.
108 Alternately, you can generate a certificate through <option>enableACME</option>.
109 <emphasis>Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using <xref linkend="opt-security.acme.certs"/>.</emphasis>
110 '';
111 };
112
113 acmeRoot = mkOption {
114 type = types.str;
115 default = "/var/lib/acme/acme-challenges";
116 description = "Directory for the acme challenge which is PUBLIC, don't put certs or keys in here";
117 };
118
119 sslServerCert = mkOption {
120 type = types.path;
121 example = "/var/host.cert";
122 description = "Path to server SSL certificate.";
123 };
124
125 sslServerKey = mkOption {
126 type = types.path;
127 example = "/var/host.key";
128 description = "Path to server SSL certificate key.";
129 };
130
131 sslServerChain = mkOption {
132 type = types.nullOr types.path;
133 default = null;
134 example = "/var/ca.pem";
135 description = "Path to server SSL chain file.";
136 };
137
138 http2 = mkOption {
139 type = types.bool;
140 default = false;
141 description = ''
142 Whether to enable HTTP 2. HTTP/2 is supported in all multi-processing modules that come with httpd. <emphasis>However, if you use the prefork mpm, there will
143 be severe restrictions.</emphasis> Refer to <link xlink:href="https://httpd.apache.org/docs/2.4/howto/http2.html#mpm-config"/> for details.
144 '';
145 };
146
147 adminAddr = mkOption {
148 type = types.nullOr types.str;
149 default = null;
150 example = "admin@example.org";
151 description = "E-mail address of the server administrator.";
152 };
153
154 documentRoot = mkOption {
155 type = types.nullOr types.path;
156 default = null;
157 example = "/data/webserver/docs";
158 description = ''
159 The path of Apache's document root directory. If left undefined,
160 an empty directory in the Nix store will be used as root.
161 '';
162 };
163
164 servedDirs = mkOption {
165 type = types.listOf types.attrs;
166 default = [];
167 example = [
168 { urlPath = "/nix";
169 dir = "/home/eelco/Dev/nix-homepage";
170 }
171 ];
172 description = ''
173 This option provides a simple way to serve static directories.
174 '';
175 };
176
177 servedFiles = mkOption {
178 type = types.listOf types.attrs;
179 default = [];
180 example = [
181 { urlPath = "/foo/bar.png";
182 file = "/home/eelco/some-file.png";
183 }
184 ];
185 description = ''
186 This option provides a simple way to serve individual, static files.
187
188 <note><para>
189 This option has been deprecated and will be removed in a future
190 version of NixOS. You can achieve the same result by making use of
191 the <literal>locations.&lt;name&gt;.alias</literal> option.
192 </para></note>
193 '';
194 };
195
196 extraConfig = mkOption {
197 type = types.lines;
198 default = "";
199 example = ''
200 <Directory /home>
201 Options FollowSymlinks
202 AllowOverride All
203 </Directory>
204 '';
205 description = ''
206 These lines go to httpd.conf verbatim. They will go after
207 directories and directory aliases defined by default.
208 '';
209 };
210
211 enableUserDir = mkOption {
212 type = types.bool;
213 default = false;
214 description = ''
215 Whether to enable serving <filename>~/public_html</filename> as
216 <literal>/~<replaceable>username</replaceable></literal>.
217 '';
218 };
219
220 globalRedirect = mkOption {
221 type = types.nullOr types.str;
222 default = null;
223 example = http://newserver.example.org/;
224 description = ''
225 If set, all requests for this host are redirected permanently to
226 the given URL.
227 '';
228 };
229
230 logFormat = mkOption {
231 type = types.str;
232 default = "common";
233 example = "combined";
234 description = ''
235 Log format for Apache's log files. Possible values are: combined, common, referer, agent.
236 '';
237 };
238
239 robotsEntries = mkOption {
240 type = types.lines;
241 default = "";
242 example = "Disallow: /foo/";
243 description = ''
244 Specification of pages to be ignored by web crawlers. See <link
245 xlink:href='http://www.robotstxt.org/'/> for details.
246 '';
247 };
248
249 locations = mkOption {
250 type = with types; attrsOf (submodule (import ./location-options.nix));
251 default = {};
252 example = literalExample ''
253 {
254 "/" = {
255 proxyPass = "http://localhost:3000";
256 };
257 "/foo/bar.png" = {
258 alias = "/home/eelco/some-file.png";
259 };
260 };
261 '';
262 description = ''
263 Declarative location config. See <link
264 xlink:href="https://httpd.apache.org/docs/2.4/mod/core.html#location"/> for details.
265 '';
266 };
267
268 };
269
270 config = {
271
272 locations = builtins.listToAttrs (map (elem: nameValuePair elem.urlPath { alias = elem.file; }) config.servedFiles);
273
274 };
275}