diff options
Diffstat (limited to 'modules/websites/httpd-service-builder.nix')
-rw-r--r-- | modules/websites/httpd-service-builder.nix | 816 |
1 files changed, 418 insertions, 398 deletions
diff --git a/modules/websites/httpd-service-builder.nix b/modules/websites/httpd-service-builder.nix index f0208ab5..ec79a90c 100644 --- a/modules/websites/httpd-service-builder.nix +++ b/modules/websites/httpd-service-builder.nix | |||
@@ -7,134 +7,56 @@ with lib; | |||
7 | 7 | ||
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 | }; |