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