diff options
-rw-r--r-- | modules/private/websites/papa/maison_bbc.nix | 1 | ||||
-rw-r--r-- | modules/websites/default.nix | 58 | ||||
-rw-r--r-- | modules/websites/httpd-service-builder.nix | 816 | ||||
-rw-r--r-- | modules/websites/httpd-service-builder.patch | 150 | ||||
-rw-r--r-- | modules/websites/location-options.nix | 54 | ||||
-rw-r--r-- | modules/websites/vhost-options.nix | 275 |
6 files changed, 926 insertions, 428 deletions
diff --git a/modules/private/websites/papa/maison_bbc.nix b/modules/private/websites/papa/maison_bbc.nix index 11e7937..9576a9e 100644 --- a/modules/private/websites/papa/maison_bbc.nix +++ b/modules/private/websites/papa/maison_bbc.nix | |||
@@ -56,6 +56,7 @@ in { | |||
56 | addToCerts = true; | 56 | addToCerts = true; |
57 | hosts = [ "maison.bbc.bouya.org" ]; | 57 | hosts = [ "maison.bbc.bouya.org" ]; |
58 | root = varDir; | 58 | root = varDir; |
59 | forceSSL = false; | ||
59 | extraConfig = [ | 60 | extraConfig = [ |
60 | '' | 61 | '' |
61 | <Directory ${varDir}> | 62 | <Directory ${varDir}> |
diff --git a/modules/websites/default.nix b/modules/websites/default.nix index 3f46e65..d5a0f63 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 f0208ab..ec79a90 100644 --- a/modules/websites/httpd-service-builder.nix +++ b/modules/websites/httpd-service-builder.nix | |||
@@ -7,134 +7,56 @@ with lib; | |||
7 | 7 | ||
8 | let | 8 | let |
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 0000000..f0ad836 --- /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 0000000..8ea88f9 --- /dev/null +++ b/modules/websites/location-options.nix | |||
@@ -0,0 +1,54 @@ | |||
1 | { config, lib, name, ... }: | ||
2 | let | ||
3 | inherit (lib) mkOption types; | ||
4 | in | ||
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 0000000..263980a --- /dev/null +++ b/modules/websites/vhost-options.nix | |||
@@ -0,0 +1,275 @@ | |||
1 | { config, lib, name, ... }: | ||
2 | let | ||
3 | inherit (lib) literalExample mkOption nameValuePair types; | ||
4 | in | ||
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.<name>.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 | } | ||