summaryrefslogtreecommitdiff
path: root/modules/websites
diff options
context:
space:
mode:
Diffstat (limited to 'modules/websites')
-rw-r--r--modules/websites/default.nix199
-rw-r--r--modules/websites/httpd-service-builder.nix746
-rw-r--r--modules/websites/nosslVhost/index.html11
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;
2let
3 cfg = config.services.websites;
4in
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
6with lib;
7
8let
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
432in
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>