diff options
Diffstat (limited to 'modules/websites')
-rw-r--r-- | modules/websites/default.nix | 281 | ||||
-rw-r--r-- | modules/websites/httpd-service-builder.nix | 735 | ||||
-rw-r--r-- | modules/websites/httpd-service-builder.patch | 150 | ||||
-rw-r--r-- | modules/websites/nosslVhost/index.html | 11 | ||||
-rw-r--r-- | modules/websites/php-application.nix | 224 |
5 files changed, 0 insertions, 1401 deletions
diff --git a/modules/websites/default.nix b/modules/websites/default.nix deleted file mode 100644 index 6658c66..0000000 --- a/modules/websites/default.nix +++ /dev/null | |||
@@ -1,281 +0,0 @@ | |||
1 | { lib, config, pkgs, ... }: with lib; | ||
2 | let | ||
3 | cfg = config.services.websites; | ||
4 | in | ||
5 | { | ||
6 | options.services.websites = with types; { | ||
7 | certs = mkOption { | ||
8 | description = "Default websites configuration for certificates as accepted by acme"; | ||
9 | }; | ||
10 | env = mkOption { | ||
11 | default = {}; | ||
12 | description = "Each type of website to enable will target a distinct httpd server"; | ||
13 | type = attrsOf (submodule { | ||
14 | options = { | ||
15 | enable = mkEnableOption "Enable websites of this type"; | ||
16 | adminAddr = mkOption { | ||
17 | type = str; | ||
18 | description = "Admin e-mail address of the instance"; | ||
19 | }; | ||
20 | httpdName = mkOption { | ||
21 | type = str; | ||
22 | description = "Name of the httpd instance to assign this type to"; | ||
23 | }; | ||
24 | ips = mkOption { | ||
25 | type = listOf str; | ||
26 | default = []; | ||
27 | description = "ips to listen to"; | ||
28 | }; | ||
29 | modules = mkOption { | ||
30 | type = listOf str; | ||
31 | default = []; | ||
32 | description = "Additional modules to load in Apache"; | ||
33 | }; | ||
34 | extraConfig = mkOption { | ||
35 | type = listOf lines; | ||
36 | default = []; | ||
37 | description = "Additional configuration to append to Apache"; | ||
38 | }; | ||
39 | nosslVhost = mkOption { | ||
40 | description = "A default nossl vhost for captive portals"; | ||
41 | default = {}; | ||
42 | type = submodule { | ||
43 | options = { | ||
44 | enable = mkEnableOption "Add default no-ssl vhost for this instance"; | ||
45 | host = mkOption { | ||
46 | type = str; | ||
47 | description = "The hostname to use for this vhost"; | ||
48 | }; | ||
49 | root = mkOption { | ||
50 | type = path; | ||
51 | default = ./nosslVhost; | ||
52 | description = "The root folder to serve"; | ||
53 | }; | ||
54 | indexFile = mkOption { | ||
55 | type = str; | ||
56 | default = "index.html"; | ||
57 | description = "The index file to show."; | ||
58 | }; | ||
59 | }; | ||
60 | }; | ||
61 | }; | ||
62 | fallbackVhost = mkOption { | ||
63 | description = "The fallback vhost that will be defined as first vhost in Apache"; | ||
64 | type = submodule { | ||
65 | options = { | ||
66 | certName = mkOption { type = str; }; | ||
67 | hosts = mkOption { type = listOf str; }; | ||
68 | root = mkOption { type = nullOr path; }; | ||
69 | forceSSL = mkOption { | ||
70 | type = bool; | ||
71 | default = true; | ||
72 | description = '' | ||
73 | Automatically create a corresponding non-ssl vhost | ||
74 | that will only redirect to the ssl version | ||
75 | ''; | ||
76 | }; | ||
77 | extraConfig = mkOption { type = listOf lines; default = []; }; | ||
78 | }; | ||
79 | }; | ||
80 | }; | ||
81 | vhostNoSSLConfs = mkOption { | ||
82 | default = {}; | ||
83 | description = "List of no ssl vhosts to define for Apache"; | ||
84 | type = attrsOf (submodule { | ||
85 | options = { | ||
86 | hosts = mkOption { type = listOf str; }; | ||
87 | root = mkOption { type = nullOr path; }; | ||
88 | extraConfig = mkOption { type = listOf lines; default = []; }; | ||
89 | }; | ||
90 | }); | ||
91 | }; | ||
92 | vhostConfs = mkOption { | ||
93 | default = {}; | ||
94 | description = "List of vhosts to define for Apache"; | ||
95 | type = attrsOf (submodule { | ||
96 | options = { | ||
97 | certName = mkOption { type = str; }; | ||
98 | addToCerts = mkOption { | ||
99 | type = bool; | ||
100 | default = false; | ||
101 | description = "Use these to certificates. Is ignored (considered true) if certMainHost is not null"; | ||
102 | }; | ||
103 | certMainHost = mkOption { | ||
104 | type = nullOr str; | ||
105 | description = "Use that host as 'main host' for acme certs"; | ||
106 | default = null; | ||
107 | }; | ||
108 | hosts = mkOption { type = listOf str; }; | ||
109 | root = mkOption { type = nullOr path; }; | ||
110 | forceSSL = mkOption { | ||
111 | type = bool; | ||
112 | default = true; | ||
113 | description = '' | ||
114 | Automatically create a corresponding non-ssl vhost | ||
115 | that will only redirect to the ssl version | ||
116 | ''; | ||
117 | }; | ||
118 | extraConfig = mkOption { type = listOf lines; default = []; }; | ||
119 | }; | ||
120 | }); | ||
121 | }; | ||
122 | watchPaths = mkOption { | ||
123 | type = listOf str; | ||
124 | default = []; | ||
125 | description = '' | ||
126 | Paths to watch that should trigger a reload of httpd | ||
127 | ''; | ||
128 | }; | ||
129 | }; | ||
130 | }); | ||
131 | }; | ||
132 | }; | ||
133 | |||
134 | config.services.httpd = let | ||
135 | nosslVhost = ips: cfg: { | ||
136 | listen = map (ip: { inherit ip; port = 80; }) ips; | ||
137 | hostName = cfg.host; | ||
138 | logFormat = "combinedVhost"; | ||
139 | documentRoot = cfg.root; | ||
140 | extraConfig = '' | ||
141 | <Directory ${cfg.root}> | ||
142 | DirectoryIndex ${cfg.indexFile} | ||
143 | AllowOverride None | ||
144 | Require all granted | ||
145 | |||
146 | RewriteEngine on | ||
147 | RewriteRule ^/(.+) / [L] | ||
148 | </Directory> | ||
149 | ''; | ||
150 | }; | ||
151 | toVhost = ips: vhostConf: { | ||
152 | forceSSL = vhostConf.forceSSL or true; | ||
153 | useACMEHost = vhostConf.certName; | ||
154 | logFormat = "combinedVhost"; | ||
155 | listen = if vhostConf.forceSSL | ||
156 | then lists.flatten (map (ip: [{ inherit ip; port = 443; ssl = true; } { inherit ip; port = 80; }]) ips) | ||
157 | else map (ip: { inherit ip; port = 443; ssl = true; }) ips; | ||
158 | hostName = builtins.head vhostConf.hosts; | ||
159 | serverAliases = builtins.tail vhostConf.hosts or []; | ||
160 | documentRoot = vhostConf.root; | ||
161 | extraConfig = builtins.concatStringsSep "\n" vhostConf.extraConfig; | ||
162 | }; | ||
163 | toVhostNoSSL = ips: vhostConf: { | ||
164 | logFormat = "combinedVhost"; | ||
165 | listen = map (ip: { inherit ip; port = 80; }) ips; | ||
166 | hostName = builtins.head vhostConf.hosts; | ||
167 | serverAliases = builtins.tail vhostConf.hosts or []; | ||
168 | documentRoot = vhostConf.root; | ||
169 | extraConfig = builtins.concatStringsSep "\n" vhostConf.extraConfig; | ||
170 | }; | ||
171 | in attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | ||
172 | icfg.httpdName (mkIf icfg.enable { | ||
173 | enable = true; | ||
174 | logPerVirtualHost = true; | ||
175 | multiProcessingModule = "worker"; | ||
176 | # https://ssl-config.mozilla.org/#server=apache&version=2.4.41&config=intermediate&openssl=1.0.2t&guideline=5.4 | ||
177 | # test with https://www.ssllabs.com/ssltest/analyze.html?d=www.immae.eu&s=176.9.151.154&latest | ||
178 | sslProtocols = "all -SSLv3 -TLSv1 -TLSv1.1"; | ||
179 | sslCiphers = builtins.concatStringsSep ":" [ | ||
180 | "ECDHE-ECDSA-AES128-GCM-SHA256" "ECDHE-RSA-AES128-GCM-SHA256" | ||
181 | "ECDHE-ECDSA-AES256-GCM-SHA384" "ECDHE-RSA-AES256-GCM-SHA384" | ||
182 | "ECDHE-ECDSA-CHACHA20-POLY1305" "ECDHE-RSA-CHACHA20-POLY1305" | ||
183 | "DHE-RSA-AES128-GCM-SHA256" "DHE-RSA-AES256-GCM-SHA384" | ||
184 | ]; | ||
185 | inherit (icfg) adminAddr; | ||
186 | logFormat = "combinedVhost"; | ||
187 | extraModules = lists.unique icfg.modules; | ||
188 | extraConfig = builtins.concatStringsSep "\n" icfg.extraConfig; | ||
189 | |||
190 | virtualHosts = with attrsets; { | ||
191 | ___fallbackVhost = toVhost icfg.ips icfg.fallbackVhost; | ||
192 | } // (optionalAttrs icfg.nosslVhost.enable { | ||
193 | nosslVhost = nosslVhost icfg.ips icfg.nosslVhost; | ||
194 | }) // (mapAttrs' (n: v: nameValuePair ("nossl_" + n) (toVhostNoSSL icfg.ips v)) icfg.vhostNoSSLConfs) | ||
195 | // (mapAttrs' (n: v: nameValuePair ("ssl_" + n) (toVhost icfg.ips v)) icfg.vhostConfs); | ||
196 | }) | ||
197 | ) cfg.env; | ||
198 | |||
199 | config.services.filesWatcher = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | ||
200 | "httpd${icfg.httpdName}" { | ||
201 | paths = icfg.watchPaths; | ||
202 | waitTime = 5; | ||
203 | } | ||
204 | ) cfg.env; | ||
205 | |||
206 | config.security.acme.certs = let | ||
207 | typesToManage = attrsets.filterAttrs (k: v: v.enable) cfg.env; | ||
208 | flatVhosts = lists.flatten (attrsets.mapAttrsToList (k: v: | ||
209 | attrValues v.vhostConfs | ||
210 | ) typesToManage); | ||
211 | groupedCerts = attrsets.filterAttrs | ||
212 | (_: group: builtins.any (v: v.addToCerts || !isNull v.certMainHost) group) | ||
213 | (lists.groupBy (v: v.certName) flatVhosts); | ||
214 | groupToDomain = group: | ||
215 | let | ||
216 | nonNull = builtins.filter (v: !isNull v.certMainHost) group; | ||
217 | domains = lists.unique (map (v: v.certMainHost) nonNull); | ||
218 | in | ||
219 | if builtins.length domains == 0 | ||
220 | then null | ||
221 | else assert (builtins.length domains == 1); (elemAt domains 0); | ||
222 | extraDomains = group: | ||
223 | let | ||
224 | mainDomain = groupToDomain group; | ||
225 | in | ||
226 | lists.remove mainDomain ( | ||
227 | lists.unique ( | ||
228 | lists.flatten (map (c: optionals (c.addToCerts || !isNull c.certMainHost) c.hosts) group) | ||
229 | ) | ||
230 | ); | ||
231 | in attrsets.mapAttrs (k: g: | ||
232 | if (!isNull (groupToDomain g)) | ||
233 | then cfg.certs // { | ||
234 | domain = groupToDomain g; | ||
235 | extraDomains = builtins.listToAttrs ( | ||
236 | map (d: attrsets.nameValuePair d null) (extraDomains g)); | ||
237 | } | ||
238 | else { | ||
239 | extraDomains = builtins.listToAttrs ( | ||
240 | map (d: attrsets.nameValuePair d null) (extraDomains g)); | ||
241 | } | ||
242 | ) groupedCerts; | ||
243 | |||
244 | config.systemd.services = let | ||
245 | package = httpdName: config.services.httpd.${httpdName}.package.out; | ||
246 | cfgFile = httpdName: config.services.httpd.${httpdName}.configFile; | ||
247 | serviceChange = attrsets.mapAttrs' (name: icfg: | ||
248 | attrsets.nameValuePair | ||
249 | "httpd${icfg.httpdName}" { | ||
250 | stopIfChanged = false; | ||
251 | serviceConfig.ExecStart = | ||
252 | lib.mkForce "@${package icfg.httpdName}/bin/httpd httpd -f /etc/httpd/httpd_${icfg.httpdName}.conf"; | ||
253 | serviceConfig.ExecStop = | ||
254 | lib.mkForce "${package icfg.httpdName}/bin/httpd -f /etc/httpd/httpd_${icfg.httpdName}.conf -k graceful-stop"; | ||
255 | serviceConfig.ExecReload = | ||
256 | lib.mkForce "${package icfg.httpdName}/bin/httpd -f /etc/httpd/httpd_${icfg.httpdName}.conf -k graceful"; | ||
257 | } | ||
258 | ) cfg.env; | ||
259 | serviceReload = attrsets.mapAttrs' (name: icfg: | ||
260 | attrsets.nameValuePair | ||
261 | "httpd${icfg.httpdName}-config-reload" { | ||
262 | wants = [ "httpd${icfg.httpdName}.service" ]; | ||
263 | wantedBy = [ "multi-user.target" ]; | ||
264 | restartTriggers = [ (cfgFile icfg.httpdName) ]; | ||
265 | # commented, because can cause extra delays during activate for this config: | ||
266 | # services.nginx.virtualHosts."_".locations."/".proxyPass = "http://blabla:3000"; | ||
267 | # stopIfChanged = false; | ||
268 | serviceConfig.Type = "oneshot"; | ||
269 | serviceConfig.TimeoutSec = 60; | ||
270 | script = '' | ||
271 | if ${pkgs.systemd}/bin/systemctl -q is-active httpd${icfg.httpdName}.service ; then | ||
272 | ${package icfg.httpdName}/bin/httpd -f /etc/httpd/httpd_${icfg.httpdName}.conf -t && \ | ||
273 | ${pkgs.systemd}/bin/systemctl reload httpd${icfg.httpdName}.service | ||
274 | fi | ||
275 | ''; | ||
276 | serviceConfig.RemainAfterExit = true; | ||
277 | } | ||
278 | ) cfg.env; | ||
279 | in | ||
280 | serviceChange // serviceReload; | ||
281 | } | ||
diff --git a/modules/websites/httpd-service-builder.nix b/modules/websites/httpd-service-builder.nix deleted file mode 100644 index 1f7488d..0000000 --- a/modules/websites/httpd-service-builder.nix +++ /dev/null | |||
@@ -1,735 +0,0 @@ | |||
1 | # to help backporting this builder should stay as close as possible to | ||
2 | # nixos/modules/services/web-servers/apache-httpd/default.nix | ||
3 | { httpdName, withUsers ? true }: | ||
4 | { config, lib, pkgs, ... }: | ||
5 | |||
6 | with lib; | ||
7 | |||
8 | let | ||
9 | |||
10 | cfg = config.services.httpd."${httpdName}"; | ||
11 | |||
12 | runtimeDir = "/run/httpd_${httpdName}"; | ||
13 | |||
14 | pkg = cfg.package.out; | ||
15 | |||
16 | httpdConf = cfg.configFile; | ||
17 | |||
18 | php = cfg.phpPackage.override { apacheHttpd = pkg.dev; /* otherwise it only gets .out */ }; | ||
19 | |||
20 | phpMajorVersion = lib.versions.major (lib.getVersion php); | ||
21 | |||
22 | mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; }; | ||
23 | |||
24 | vhosts = attrValues cfg.virtualHosts; | ||
25 | |||
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 | |||
33 | listenInfo = unique (concatMap mkListenInfo vhosts); | ||
34 | |||
35 | enableHttp2 = any (vhost: vhost.http2) vhosts; | ||
36 | enableSSL = any (listen: listen.ssl) listenInfo; | ||
37 | enableUserDir = any (vhost: vhost.enableUserDir) vhosts; | ||
38 | |||
39 | # NOTE: generally speaking order of modules is very important | ||
40 | modules = | ||
41 | [ # required apache modules our httpd service cannot run without | ||
42 | "authn_core" "authz_core" | ||
43 | "log_config" | ||
44 | "mime" "autoindex" "negotiation" "dir" | ||
45 | "alias" "rewrite" | ||
46 | "unixd" "slotmem_shm" "socache_shmcb" | ||
47 | "mpm_${cfg.multiProcessingModule}" | ||
48 | ] | ||
49 | ++ (if cfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ]) | ||
50 | ++ optional enableHttp2 "http2" | ||
51 | ++ optional enableSSL "ssl" | ||
52 | ++ optional enableUserDir "userdir" | ||
53 | ++ optional cfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; } | ||
54 | ++ optional cfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; } | ||
55 | ++ optional cfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; } | ||
56 | ++ cfg.extraModules; | ||
57 | |||
58 | loggingConf = (if cfg.logFormat != "none" then '' | ||
59 | ErrorLog ${cfg.logDir}/error.log | ||
60 | |||
61 | LogLevel notice | ||
62 | |||
63 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined | ||
64 | LogFormat "%h %l %u %t \"%r\" %>s %b" common | ||
65 | LogFormat "%{Referer}i -> %U" referer | ||
66 | LogFormat "%{User-agent}i" agent | ||
67 | |||
68 | CustomLog ${cfg.logDir}/access.log ${cfg.logFormat} | ||
69 | '' else '' | ||
70 | ErrorLog /dev/null | ||
71 | ''); | ||
72 | |||
73 | |||
74 | browserHacks = '' | ||
75 | <IfModule mod_setenvif.c> | ||
76 | BrowserMatch "Mozilla/2" nokeepalive | ||
77 | BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0 | ||
78 | BrowserMatch "RealPlayer 4\.0" force-response-1.0 | ||
79 | BrowserMatch "Java/1\.0" force-response-1.0 | ||
80 | BrowserMatch "JDK/1\.0" force-response-1.0 | ||
81 | BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully | ||
82 | BrowserMatch "^WebDrive" redirect-carefully | ||
83 | BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully | ||
84 | BrowserMatch "^gnome-vfs" redirect-carefully | ||
85 | </IfModule> | ||
86 | ''; | ||
87 | |||
88 | |||
89 | sslConf = '' | ||
90 | <IfModule mod_ssl.c> | ||
91 | SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000) | ||
92 | |||
93 | Mutex posixsem | ||
94 | |||
95 | SSLRandomSeed startup builtin | ||
96 | SSLRandomSeed connect builtin | ||
97 | |||
98 | SSLProtocol ${cfg.sslProtocols} | ||
99 | SSLCipherSuite ${cfg.sslCiphers} | ||
100 | SSLHonorCipherOrder on | ||
101 | </IfModule> | ||
102 | ''; | ||
103 | |||
104 | |||
105 | mimeConf = '' | ||
106 | TypesConfig ${pkg}/conf/mime.types | ||
107 | |||
108 | AddType application/x-x509-ca-cert .crt | ||
109 | AddType application/x-pkcs7-crl .crl | ||
110 | AddType application/x-httpd-php .php .phtml | ||
111 | |||
112 | <IfModule mod_mime_magic.c> | ||
113 | MIMEMagicFile ${pkg}/conf/magic | ||
114 | </IfModule> | ||
115 | ''; | ||
116 | |||
117 | mkVHostConf = hostOpts: | ||
118 | let | ||
119 | adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr; | ||
120 | listen = filter (listen: !listen.ssl) (mkListenInfo hostOpts); | ||
121 | listenSSL = filter (listen: listen.ssl) (mkListenInfo hostOpts); | ||
122 | |||
123 | useACME = hostOpts.enableACME || hostOpts.useACMEHost != null; | ||
124 | sslCertDir = | ||
125 | if hostOpts.enableACME then config.security.acme.certs.${hostOpts.hostName}.directory | ||
126 | else if hostOpts.useACMEHost != null then config.security.acme.certs.${hostOpts.useACMEHost}.directory | ||
127 | else abort "This case should never happen."; | ||
128 | |||
129 | sslServerCert = if useACME then "${sslCertDir}/full.pem" else hostOpts.sslServerCert; | ||
130 | sslServerKey = if useACME then "${sslCertDir}/key.pem" else hostOpts.sslServerKey; | ||
131 | sslServerChain = if useACME then "${sslCertDir}/fullchain.pem" else hostOpts.sslServerChain; | ||
132 | |||
133 | acmeChallenge = optionalString useACME '' | ||
134 | Alias /.well-known/acme-challenge/ "${hostOpts.acmeRoot}/.well-known/acme-challenge/" | ||
135 | <Directory "${hostOpts.acmeRoot}"> | ||
136 | AllowOverride None | ||
137 | Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec | ||
138 | Require method GET POST OPTIONS | ||
139 | Require all granted | ||
140 | </Directory> | ||
141 | ''; | ||
142 | in | ||
143 | optionalString (listen != []) '' | ||
144 | <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listen}> | ||
145 | ServerName ${hostOpts.hostName} | ||
146 | ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases} | ||
147 | ServerAdmin ${adminAddr} | ||
148 | <IfModule mod_ssl.c> | ||
149 | SSLEngine off | ||
150 | </IfModule> | ||
151 | ${acmeChallenge} | ||
152 | ${if hostOpts.forceSSL then '' | ||
153 | <IfModule mod_rewrite.c> | ||
154 | RewriteEngine on | ||
155 | RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge [NC] | ||
156 | RewriteCond %{HTTPS} off | ||
157 | RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} | ||
158 | </IfModule> | ||
159 | '' else mkVHostCommonConf hostOpts} | ||
160 | </VirtualHost> | ||
161 | '' + | ||
162 | optionalString (listenSSL != []) '' | ||
163 | <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listenSSL}> | ||
164 | ServerName ${hostOpts.hostName} | ||
165 | ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases} | ||
166 | ServerAdmin ${adminAddr} | ||
167 | SSLEngine on | ||
168 | SSLCertificateFile ${sslServerCert} | ||
169 | SSLCertificateKeyFile ${sslServerKey} | ||
170 | ${optionalString hostOpts.http2 "Protocols h2 h2c http/1.1"} | ||
171 | ${acmeChallenge} | ||
172 | ${mkVHostCommonConf hostOpts} | ||
173 | </VirtualHost> | ||
174 | '' | ||
175 | ; | ||
176 | |||
177 | mkVHostCommonConf = hostOpts: | ||
178 | let | ||
179 | documentRoot = if hostOpts.documentRoot != null | ||
180 | then hostOpts.documentRoot | ||
181 | else pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out" | ||
182 | ; | ||
183 | |||
184 | mkLocations = locations: concatStringsSep "\n" (map (config: '' | ||
185 | <Location ${config.location}> | ||
186 | ${optionalString (config.proxyPass != null) '' | ||
187 | <IfModule mod_proxy.c> | ||
188 | ProxyPass ${config.proxyPass} | ||
189 | ProxyPassReverse ${config.proxyPass} | ||
190 | </IfModule> | ||
191 | ''} | ||
192 | ${optionalString (config.index != null) '' | ||
193 | <IfModule mod_dir.c> | ||
194 | DirectoryIndex ${config.index} | ||
195 | </IfModule> | ||
196 | ''} | ||
197 | ${optionalString (config.alias != null) '' | ||
198 | <IfModule mod_alias.c> | ||
199 | Alias "${config.alias}" | ||
200 | </IfModule> | ||
201 | ''} | ||
202 | ${config.extraConfig} | ||
203 | </Location> | ||
204 | '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations))); | ||
205 | in | ||
206 | '' | ||
207 | ${optionalString cfg.logPerVirtualHost '' | ||
208 | ErrorLog ${cfg.logDir}/error-${hostOpts.hostName}.log | ||
209 | CustomLog ${cfg.logDir}/access-${hostOpts.hostName}.log ${hostOpts.logFormat} | ||
210 | ''} | ||
211 | |||
212 | ${optionalString (hostOpts.robotsEntries != "") '' | ||
213 | Alias /robots.txt ${pkgs.writeText "robots.txt" hostOpts.robotsEntries} | ||
214 | ''} | ||
215 | |||
216 | DocumentRoot "${documentRoot}" | ||
217 | |||
218 | <Directory "${documentRoot}"> | ||
219 | Options Indexes FollowSymLinks | ||
220 | AllowOverride None | ||
221 | Require all granted | ||
222 | </Directory> | ||
223 | |||
224 | ${optionalString hostOpts.enableUserDir '' | ||
225 | UserDir public_html | ||
226 | UserDir disabled root | ||
227 | <Directory "/home/*/public_html"> | ||
228 | AllowOverride FileInfo AuthConfig Limit Indexes | ||
229 | Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec | ||
230 | <Limit GET POST OPTIONS> | ||
231 | Require all granted | ||
232 | </Limit> | ||
233 | <LimitExcept GET POST OPTIONS> | ||
234 | Require all denied | ||
235 | </LimitExcept> | ||
236 | </Directory> | ||
237 | ''} | ||
238 | |||
239 | ${optionalString (hostOpts.globalRedirect != null && hostOpts.globalRedirect != "") '' | ||
240 | RedirectPermanent / ${hostOpts.globalRedirect} | ||
241 | ''} | ||
242 | |||
243 | ${ | ||
244 | let makeDirConf = elem: '' | ||
245 | Alias ${elem.urlPath} ${elem.dir}/ | ||
246 | <Directory ${elem.dir}> | ||
247 | Options +Indexes | ||
248 | Require all granted | ||
249 | AllowOverride All | ||
250 | </Directory> | ||
251 | ''; | ||
252 | in concatMapStrings makeDirConf hostOpts.servedDirs | ||
253 | } | ||
254 | |||
255 | ${mkLocations hostOpts.locations} | ||
256 | ${hostOpts.extraConfig} | ||
257 | '' | ||
258 | ; | ||
259 | |||
260 | |||
261 | confFile = pkgs.writeText "httpd.conf" '' | ||
262 | |||
263 | ServerRoot ${pkg} | ||
264 | ServerName ${config.networking.hostName} | ||
265 | DefaultRuntimeDir ${runtimeDir}/runtime | ||
266 | |||
267 | PidFile ${runtimeDir}/httpd.pid | ||
268 | |||
269 | ${optionalString (cfg.multiProcessingModule != "prefork") '' | ||
270 | # mod_cgid requires this. | ||
271 | ScriptSock ${runtimeDir}/cgisock | ||
272 | ''} | ||
273 | |||
274 | <IfModule prefork.c> | ||
275 | MaxClients ${toString cfg.maxClients} | ||
276 | MaxRequestsPerChild ${toString cfg.maxRequestsPerChild} | ||
277 | </IfModule> | ||
278 | |||
279 | ${let | ||
280 | toStr = listen: "Listen ${listen.ip}:${toString listen.port} ${if listen.ssl then "https" else "http"}"; | ||
281 | uniqueListen = uniqList {inputList = map toStr listenInfo;}; | ||
282 | in concatStringsSep "\n" uniqueListen | ||
283 | } | ||
284 | |||
285 | User ${cfg.user} | ||
286 | Group ${cfg.group} | ||
287 | |||
288 | ${let | ||
289 | mkModule = module: | ||
290 | if isString module then { name = module; path = "${pkg}/modules/mod_${module}.so"; } | ||
291 | else if isAttrs module then { inherit (module) name path; } | ||
292 | else throw "Expecting either a string or attribute set including a name and path."; | ||
293 | in | ||
294 | concatMapStringsSep "\n" (module: "LoadModule ${module.name}_module ${module.path}") (unique (map mkModule modules)) | ||
295 | } | ||
296 | |||
297 | AddHandler type-map var | ||
298 | |||
299 | <Files ~ "^\.ht"> | ||
300 | Require all denied | ||
301 | </Files> | ||
302 | |||
303 | ${mimeConf} | ||
304 | ${loggingConf} | ||
305 | ${browserHacks} | ||
306 | |||
307 | Include ${pkg}/conf/extra/httpd-default.conf | ||
308 | Include ${pkg}/conf/extra/httpd-autoindex.conf | ||
309 | Include ${pkg}/conf/extra/httpd-multilang-errordoc.conf | ||
310 | Include ${pkg}/conf/extra/httpd-languages.conf | ||
311 | |||
312 | TraceEnable off | ||
313 | |||
314 | ${sslConf} | ||
315 | |||
316 | # Fascist default - deny access to everything. | ||
317 | <Directory /> | ||
318 | Options FollowSymLinks | ||
319 | AllowOverride None | ||
320 | Require all denied | ||
321 | </Directory> | ||
322 | |||
323 | ${cfg.extraConfig} | ||
324 | |||
325 | ${concatMapStringsSep "\n" mkVHostConf vhosts} | ||
326 | ''; | ||
327 | |||
328 | # Generate the PHP configuration file. Should probably be factored | ||
329 | # out into a separate module. | ||
330 | phpIni = pkgs.runCommand "php.ini" | ||
331 | { options = cfg.phpOptions; | ||
332 | preferLocalBuild = true; | ||
333 | } | ||
334 | '' | ||
335 | cat ${php}/etc/php.ini > $out | ||
336 | echo "$options" >> $out | ||
337 | ''; | ||
338 | |||
339 | in | ||
340 | |||
341 | |||
342 | { | ||
343 | |||
344 | imports = [ | ||
345 | (mkRemovedOptionModule [ "services" "httpd" httpdName "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.") | ||
346 | (mkRemovedOptionModule [ "services" "httpd" httpdName "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.") | ||
347 | |||
348 | # virtualHosts options | ||
349 | (mkRemovedOptionModule [ "services" "httpd" httpdName "documentRoot" ] "Please define a virtual host using `services.httpd.virtualHosts`.") | ||
350 | (mkRemovedOptionModule [ "services" "httpd" httpdName "enableSSL" ] "Please define a virtual host using `services.httpd.virtualHosts`.") | ||
351 | (mkRemovedOptionModule [ "services" "httpd" httpdName "enableUserDir" ] "Please define a virtual host using `services.httpd.virtualHosts`.") | ||
352 | (mkRemovedOptionModule [ "services" "httpd" httpdName "globalRedirect" ] "Please define a virtual host using `services.httpd.virtualHosts`.") | ||
353 | (mkRemovedOptionModule [ "services" "httpd" httpdName "hostName" ] "Please define a virtual host using `services.httpd.virtualHosts`.") | ||
354 | (mkRemovedOptionModule [ "services" "httpd" httpdName "listen" ] "Please define a virtual host using `services.httpd.virtualHosts`.") | ||
355 | (mkRemovedOptionModule [ "services" "httpd" httpdName "robotsEntries" ] "Please define a virtual host using `services.httpd.virtualHosts`.") | ||
356 | (mkRemovedOptionModule [ "services" "httpd" httpdName "servedDirs" ] "Please define a virtual host using `services.httpd.virtualHosts`.") | ||
357 | (mkRemovedOptionModule [ "services" "httpd" httpdName "servedFiles" ] "Please define a virtual host using `services.httpd.virtualHosts`.") | ||
358 | (mkRemovedOptionModule [ "services" "httpd" httpdName "serverAliases" ] "Please define a virtual host using `services.httpd.virtualHosts`.") | ||
359 | (mkRemovedOptionModule [ "services" "httpd" httpdName "sslServerCert" ] "Please define a virtual host using `services.httpd.virtualHosts`.") | ||
360 | (mkRemovedOptionModule [ "services" "httpd" httpdName "sslServerChain" ] "Please define a virtual host using `services.httpd.virtualHosts`.") | ||
361 | (mkRemovedOptionModule [ "services" "httpd" httpdName "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.") | ||
362 | ]; | ||
363 | |||
364 | # interface | ||
365 | |||
366 | options = { | ||
367 | |||
368 | services.httpd."${httpdName}" = { | ||
369 | |||
370 | enable = mkEnableOption "the Apache HTTP Server"; | ||
371 | |||
372 | package = mkOption { | ||
373 | type = types.package; | ||
374 | default = pkgs.apacheHttpd; | ||
375 | defaultText = "pkgs.apacheHttpd"; | ||
376 | description = '' | ||
377 | Overridable attribute of the Apache HTTP Server package to use. | ||
378 | ''; | ||
379 | }; | ||
380 | |||
381 | configFile = mkOption { | ||
382 | type = types.path; | ||
383 | default = confFile; | ||
384 | defaultText = "confFile"; | ||
385 | example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ..."''; | ||
386 | description = '' | ||
387 | Override the configuration file used by Apache. By default, | ||
388 | NixOS generates one automatically. | ||
389 | ''; | ||
390 | }; | ||
391 | |||
392 | extraConfig = mkOption { | ||
393 | type = types.lines; | ||
394 | default = ""; | ||
395 | description = '' | ||
396 | Configuration lines appended to the generated Apache | ||
397 | configuration file. Note that this mechanism will not work | ||
398 | when <option>configFile</option> is overridden. | ||
399 | ''; | ||
400 | }; | ||
401 | |||
402 | extraModules = mkOption { | ||
403 | type = types.listOf types.unspecified; | ||
404 | default = []; | ||
405 | example = literalExample '' | ||
406 | [ | ||
407 | "proxy_connect" | ||
408 | { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; } | ||
409 | ] | ||
410 | ''; | ||
411 | description = '' | ||
412 | Additional Apache modules to be used. These can be | ||
413 | specified as a string in the case of modules distributed | ||
414 | with Apache, or as an attribute set specifying the | ||
415 | <varname>name</varname> and <varname>path</varname> of the | ||
416 | module. | ||
417 | ''; | ||
418 | }; | ||
419 | |||
420 | adminAddr = mkOption { | ||
421 | type = types.str; | ||
422 | example = "admin@example.org"; | ||
423 | description = "E-mail address of the server administrator."; | ||
424 | }; | ||
425 | |||
426 | logFormat = mkOption { | ||
427 | type = types.str; | ||
428 | default = "common"; | ||
429 | example = "combined"; | ||
430 | description = '' | ||
431 | Log format for log files. Possible values are: combined, common, referer, agent. | ||
432 | See <link xlink:href="https://httpd.apache.org/docs/2.4/logs.html"/> for more details. | ||
433 | ''; | ||
434 | }; | ||
435 | |||
436 | logPerVirtualHost = mkOption { | ||
437 | type = types.bool; | ||
438 | default = true; | ||
439 | description = '' | ||
440 | If enabled, each virtual host gets its own | ||
441 | <filename>access.log</filename> and | ||
442 | <filename>error.log</filename>, namely suffixed by the | ||
443 | <option>hostName</option> of the virtual host. | ||
444 | ''; | ||
445 | }; | ||
446 | |||
447 | user = mkOption { | ||
448 | type = types.str; | ||
449 | default = "wwwrun"; | ||
450 | description = '' | ||
451 | User account under which httpd runs. | ||
452 | ''; | ||
453 | }; | ||
454 | |||
455 | group = mkOption { | ||
456 | type = types.str; | ||
457 | default = "wwwrun"; | ||
458 | description = '' | ||
459 | Group under which httpd runs. | ||
460 | ''; | ||
461 | }; | ||
462 | |||
463 | logDir = mkOption { | ||
464 | type = types.path; | ||
465 | default = "/var/log/httpd"; | ||
466 | description = '' | ||
467 | Directory for Apache's log files. It is created automatically. | ||
468 | ''; | ||
469 | }; | ||
470 | |||
471 | virtualHosts = mkOption { | ||
472 | type = with types; attrsOf (submodule (import <nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix>)); | ||
473 | default = { | ||
474 | localhost = { | ||
475 | documentRoot = "${pkg}/htdocs"; | ||
476 | }; | ||
477 | }; | ||
478 | example = literalExample '' | ||
479 | { | ||
480 | "foo.example.com" = { | ||
481 | forceSSL = true; | ||
482 | documentRoot = "/var/www/foo.example.com" | ||
483 | }; | ||
484 | "bar.example.com" = { | ||
485 | addSSL = true; | ||
486 | documentRoot = "/var/www/bar.example.com"; | ||
487 | }; | ||
488 | } | ||
489 | ''; | ||
490 | description = '' | ||
491 | Specification of the virtual hosts served by Apache. Each | ||
492 | element should be an attribute set specifying the | ||
493 | configuration of the virtual host. | ||
494 | ''; | ||
495 | }; | ||
496 | |||
497 | enableMellon = mkOption { | ||
498 | type = types.bool; | ||
499 | default = false; | ||
500 | description = "Whether to enable the mod_auth_mellon module."; | ||
501 | }; | ||
502 | |||
503 | enablePHP = mkOption { | ||
504 | type = types.bool; | ||
505 | default = false; | ||
506 | description = "Whether to enable the PHP module."; | ||
507 | }; | ||
508 | |||
509 | phpPackage = mkOption { | ||
510 | type = types.package; | ||
511 | default = pkgs.php; | ||
512 | defaultText = "pkgs.php"; | ||
513 | description = '' | ||
514 | Overridable attribute of the PHP package to use. | ||
515 | ''; | ||
516 | }; | ||
517 | |||
518 | enablePerl = mkOption { | ||
519 | type = types.bool; | ||
520 | default = false; | ||
521 | description = "Whether to enable the Perl module (mod_perl)."; | ||
522 | }; | ||
523 | |||
524 | phpOptions = mkOption { | ||
525 | type = types.lines; | ||
526 | default = ""; | ||
527 | example = | ||
528 | '' | ||
529 | date.timezone = "CET" | ||
530 | ''; | ||
531 | description = '' | ||
532 | Options appended to the PHP configuration file <filename>php.ini</filename>. | ||
533 | ''; | ||
534 | }; | ||
535 | |||
536 | multiProcessingModule = mkOption { | ||
537 | type = types.enum [ "event" "prefork" "worker" ]; | ||
538 | default = "prefork"; | ||
539 | example = "worker"; | ||
540 | description = | ||
541 | '' | ||
542 | Multi-processing module to be used by Apache. Available | ||
543 | modules are <literal>prefork</literal> (the default; | ||
544 | handles each request in a separate child process), | ||
545 | <literal>worker</literal> (hybrid approach that starts a | ||
546 | number of child processes each running a number of | ||
547 | threads) and <literal>event</literal> (a recent variant of | ||
548 | <literal>worker</literal> that handles persistent | ||
549 | connections more efficiently). | ||
550 | ''; | ||
551 | }; | ||
552 | |||
553 | maxClients = mkOption { | ||
554 | type = types.int; | ||
555 | default = 150; | ||
556 | example = 8; | ||
557 | description = "Maximum number of httpd processes (prefork)"; | ||
558 | }; | ||
559 | |||
560 | maxRequestsPerChild = mkOption { | ||
561 | type = types.int; | ||
562 | default = 0; | ||
563 | example = 500; | ||
564 | description = '' | ||
565 | Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited. | ||
566 | ''; | ||
567 | }; | ||
568 | |||
569 | sslCiphers = mkOption { | ||
570 | type = types.str; | ||
571 | default = "HIGH:!aNULL:!MD5:!EXP"; | ||
572 | description = "Cipher Suite available for negotiation in SSL proxy handshake."; | ||
573 | }; | ||
574 | |||
575 | sslProtocols = mkOption { | ||
576 | type = types.str; | ||
577 | default = "All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1"; | ||
578 | example = "All -SSLv2 -SSLv3"; | ||
579 | description = "Allowed SSL/TLS protocol versions."; | ||
580 | }; | ||
581 | }; | ||
582 | |||
583 | }; | ||
584 | |||
585 | # implementation | ||
586 | |||
587 | config = mkIf cfg.enable { | ||
588 | |||
589 | assertions = [ | ||
590 | { | ||
591 | assertion = all (hostOpts: !hostOpts.enableSSL) vhosts; | ||
592 | message = '' | ||
593 | The option `services.httpd.virtualHosts.<name>.enableSSL` no longer has any effect; please remove it. | ||
594 | Select one of `services.httpd.virtualHosts.<name>.addSSL`, `services.httpd.virtualHosts.<name>.forceSSL`, | ||
595 | or `services.httpd.virtualHosts.<name>.onlySSL`. | ||
596 | ''; | ||
597 | } | ||
598 | { | ||
599 | assertion = all (hostOpts: with hostOpts; !(addSSL && onlySSL) && !(forceSSL && onlySSL) && !(addSSL && forceSSL)) vhosts; | ||
600 | message = '' | ||
601 | Options `services.httpd.virtualHosts.<name>.addSSL`, | ||
602 | `services.httpd.virtualHosts.<name>.onlySSL` and `services.httpd.virtualHosts.<name>.forceSSL` | ||
603 | are mutually exclusive. | ||
604 | ''; | ||
605 | } | ||
606 | { | ||
607 | assertion = all (hostOpts: !(hostOpts.enableACME && hostOpts.useACMEHost != null)) vhosts; | ||
608 | message = '' | ||
609 | Options `services.httpd.virtualHosts.<name>.enableACME` and | ||
610 | `services.httpd.virtualHosts.<name>.useACMEHost` are mutually exclusive. | ||
611 | ''; | ||
612 | } | ||
613 | ]; | ||
614 | |||
615 | warnings = | ||
616 | mapAttrsToList (name: hostOpts: '' | ||
617 | 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. | ||
618 | '') (filterAttrs (name: hostOpts: hostOpts.servedFiles != []) cfg.virtualHosts); | ||
619 | |||
620 | users.users = optionalAttrs (withUsers && cfg.user == "wwwrun") { | ||
621 | wwwrun = { | ||
622 | group = cfg.group; | ||
623 | description = "Apache httpd user"; | ||
624 | uid = config.ids.uids.wwwrun; | ||
625 | }; | ||
626 | }; | ||
627 | |||
628 | users.groups = optionalAttrs (withUsers && cfg.group == "wwwrun") { | ||
629 | wwwrun.gid = config.ids.gids.wwwrun; | ||
630 | }; | ||
631 | |||
632 | security.acme.certs = mapAttrs (name: hostOpts: { | ||
633 | user = cfg.user; | ||
634 | group = mkDefault cfg.group; | ||
635 | email = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr; | ||
636 | webroot = hostOpts.acmeRoot; | ||
637 | extraDomains = genAttrs hostOpts.serverAliases (alias: null); | ||
638 | postRun = "systemctl reload httpd.service"; | ||
639 | }) (filterAttrs (name: hostOpts: hostOpts.enableACME) cfg.virtualHosts); | ||
640 | |||
641 | environment.systemPackages = [ pkg ]; | ||
642 | |||
643 | # required for "apachectl configtest" | ||
644 | environment.etc."httpd/httpd_${httpdName}.conf".source = httpdConf; | ||
645 | |||
646 | services.httpd."${httpdName}" = { phpOptions = | ||
647 | '' | ||
648 | ; Needed for PHP's mail() function. | ||
649 | sendmail_path = sendmail -t -i | ||
650 | |||
651 | ; Don't advertise PHP | ||
652 | expose_php = off | ||
653 | '' + optionalString (config.time.timeZone != null) '' | ||
654 | |||
655 | ; Apparently PHP doesn't use $TZ. | ||
656 | date.timezone = "${config.time.timeZone}" | ||
657 | ''; | ||
658 | |||
659 | extraModules = mkBefore [ | ||
660 | # HTTP authentication mechanisms: basic and digest. | ||
661 | "auth_basic" "auth_digest" | ||
662 | |||
663 | # Authentication: is the user who he claims to be? | ||
664 | "authn_file" "authn_dbm" "authn_anon" | ||
665 | |||
666 | # Authorization: is the user allowed access? | ||
667 | "authz_user" "authz_groupfile" "authz_host" | ||
668 | |||
669 | # Other modules. | ||
670 | "ext_filter" "include" "env" "mime_magic" | ||
671 | "cern_meta" "expires" "headers" "usertrack" "setenvif" | ||
672 | "dav" "status" "asis" "info" "dav_fs" | ||
673 | "vhost_alias" "imagemap" "actions" "speling" | ||
674 | "proxy" "proxy_http" | ||
675 | "cache" "cache_disk" | ||
676 | |||
677 | # For compatibility with old configurations, the new module mod_access_compat is provided. | ||
678 | "access_compat" | ||
679 | ]; | ||
680 | }; | ||
681 | |||
682 | systemd.tmpfiles.rules = | ||
683 | let | ||
684 | svc = config.systemd.services."httpd${httpdName}".serviceConfig; | ||
685 | in | ||
686 | [ | ||
687 | "d '${cfg.logDir}' 0700 ${svc.User} ${svc.Group}" | ||
688 | "Z '${cfg.logDir}' - ${svc.User} ${svc.Group}" | ||
689 | ]; | ||
690 | |||
691 | systemd.services."httpd${httpdName}" = | ||
692 | let | ||
693 | vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts; | ||
694 | in | ||
695 | { description = "Apache HTTPD"; | ||
696 | |||
697 | wantedBy = [ "multi-user.target" ]; | ||
698 | wants = concatLists (map (hostOpts: [ "acme-${hostOpts.hostName}.service" "acme-selfsigned-${hostOpts.hostName}.service" ]) vhostsACME); | ||
699 | after = [ "network.target" "fs.target" ] ++ map (hostOpts: "acme-selfsigned-${hostOpts.hostName}.service") vhostsACME; | ||
700 | |||
701 | path = | ||
702 | [ pkg pkgs.coreutils pkgs.gnugrep ] | ||
703 | ++ optional cfg.enablePHP pkgs.system-sendmail; # Needed for PHP's mail() function. | ||
704 | |||
705 | environment = | ||
706 | optionalAttrs cfg.enablePHP { PHPRC = phpIni; } | ||
707 | // optionalAttrs cfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; }; | ||
708 | |||
709 | preStart = | ||
710 | '' | ||
711 | # Get rid of old semaphores. These tend to accumulate across | ||
712 | # server restarts, eventually preventing it from restarting | ||
713 | # successfully. | ||
714 | for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${cfg.user} ' | cut -f2 -d ' '); do | ||
715 | ${pkgs.utillinux}/bin/ipcrm -s $i | ||
716 | done | ||
717 | ''; | ||
718 | |||
719 | serviceConfig = { | ||
720 | ExecStart = "@${pkg}/bin/httpd httpd -f ${httpdConf}"; | ||
721 | ExecStop = "${pkg}/bin/httpd -f ${httpdConf} -k graceful-stop"; | ||
722 | ExecReload = "${pkg}/bin/httpd -f ${httpdConf} -k graceful"; | ||
723 | User = "root"; | ||
724 | Group = cfg.group; | ||
725 | Type = "forking"; | ||
726 | PIDFile = "${runtimeDir}/httpd.pid"; | ||
727 | Restart = "always"; | ||
728 | RestartSec = "5s"; | ||
729 | RuntimeDirectory = "httpd_${httpdName} httpd_${httpdName}/runtime"; | ||
730 | RuntimeDirectoryMode = "0750"; | ||
731 | }; | ||
732 | }; | ||
733 | |||
734 | }; | ||
735 | } | ||
diff --git a/modules/websites/httpd-service-builder.patch b/modules/websites/httpd-service-builder.patch deleted file mode 100644 index f0ad836..0000000 --- a/modules/websites/httpd-service-builder.patch +++ /dev/null | |||
@@ -1,150 +0,0 @@ | |||
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/nosslVhost/index.html b/modules/websites/nosslVhost/index.html deleted file mode 100644 index 4401a80..0000000 --- a/modules/websites/nosslVhost/index.html +++ /dev/null | |||
@@ -1,11 +0,0 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html> | ||
3 | <head> | ||
4 | <title>No SSL site</title> | ||
5 | </head> | ||
6 | <body> | ||
7 | <h1>No SSL on this site</h1> | ||
8 | <p>Use for wifi networks with login page that doesn't work well with | ||
9 | https.</p> | ||
10 | </body> | ||
11 | </html> | ||
diff --git a/modules/websites/php-application.nix b/modules/websites/php-application.nix deleted file mode 100644 index 3a43a45..0000000 --- a/modules/websites/php-application.nix +++ /dev/null | |||
@@ -1,224 +0,0 @@ | |||
1 | { lib, config, pkgs, ... }: | ||
2 | with lib; | ||
3 | let | ||
4 | cfg = config.services.phpApplication; | ||
5 | cfgByEnv = lists.groupBy (x: x.websiteEnv) (builtins.attrValues cfg.apps); | ||
6 | in | ||
7 | { | ||
8 | options = with types; { | ||
9 | services.phpApplication.apps = mkOption { | ||
10 | default = {}; | ||
11 | description = '' | ||
12 | php applications to define | ||
13 | ''; | ||
14 | type = attrsOf (submodule { | ||
15 | options = { | ||
16 | varDir = mkOption { | ||
17 | type = nullOr path; | ||
18 | description = '' | ||
19 | Path to application’s vardir. | ||
20 | ''; | ||
21 | }; | ||
22 | varDirPaths = mkOption { | ||
23 | type = attrsOf str; | ||
24 | default = {}; | ||
25 | description = '' | ||
26 | Map of additional folders => mode to create under varDir | ||
27 | ''; | ||
28 | }; | ||
29 | mode = mkOption { | ||
30 | type = str; | ||
31 | default = "0700"; | ||
32 | description = '' | ||
33 | Mode to apply to the vardir | ||
34 | ''; | ||
35 | }; | ||
36 | phpSession = mkOption { | ||
37 | type = bool; | ||
38 | default = true; | ||
39 | description = "Handle phpsession files separately in vardir"; | ||
40 | }; | ||
41 | phpListen = mkOption { | ||
42 | type = nullOr str; | ||
43 | default = null; | ||
44 | description = "Name of the socket to listen to. Defaults to app name if null"; | ||
45 | }; | ||
46 | phpPool = mkOption { | ||
47 | type = attrsOf str; | ||
48 | default = {}; | ||
49 | description = "Pool configuration to append"; | ||
50 | }; | ||
51 | phpEnv = mkOption { | ||
52 | type = attrsOf str; | ||
53 | default = {}; | ||
54 | description = "Pool environment to append"; | ||
55 | }; | ||
56 | phpPackage = mkOption { | ||
57 | type = attrsOf str; | ||
58 | default = pkgs.php; | ||
59 | description = "Php package to use"; | ||
60 | }; | ||
61 | phpOptions = mkOption { | ||
62 | type = lines; | ||
63 | default = ""; | ||
64 | description = "php configuration to append"; | ||
65 | }; | ||
66 | phpOpenbasedir = mkOption { | ||
67 | type = listOf path; | ||
68 | default = []; | ||
69 | description = '' | ||
70 | paths to add to php open_basedir configuration in addition to app and vardir | ||
71 | ''; | ||
72 | }; | ||
73 | phpWatchFiles = mkOption { | ||
74 | type = listOf path; | ||
75 | default = []; | ||
76 | description = '' | ||
77 | Path to other files to watch to trigger preStart scripts | ||
78 | ''; | ||
79 | }; | ||
80 | websiteEnv = mkOption { | ||
81 | type = str; | ||
82 | description = '' | ||
83 | website instance name to use | ||
84 | ''; | ||
85 | }; | ||
86 | httpdUser = mkOption { | ||
87 | type = str; | ||
88 | default = config.services.httpd.user; | ||
89 | description = '' | ||
90 | httpd user to run the prestart scripts as. | ||
91 | ''; | ||
92 | }; | ||
93 | httpdGroup = mkOption { | ||
94 | type = str; | ||
95 | default = config.services.httpd.group; | ||
96 | description = '' | ||
97 | httpd group to run the prestart scripts as. | ||
98 | ''; | ||
99 | }; | ||
100 | httpdWatchFiles = mkOption { | ||
101 | type = listOf path; | ||
102 | default = []; | ||
103 | description = '' | ||
104 | Path to other files to watch to trigger httpd reload | ||
105 | ''; | ||
106 | }; | ||
107 | app = mkOption { | ||
108 | type = path; | ||
109 | description = '' | ||
110 | Path to application root | ||
111 | ''; | ||
112 | }; | ||
113 | webRoot = mkOption { | ||
114 | type = nullOr path; | ||
115 | description = '' | ||
116 | Path to the web root path of the application. May differ from the application itself (usually a subdirectory) | ||
117 | ''; | ||
118 | }; | ||
119 | preStartActions = mkOption { | ||
120 | type = listOf str; | ||
121 | default = []; | ||
122 | description = '' | ||
123 | List of actions to run as apache user at preStart when | ||
124 | whatchFiles or app dir changed. | ||
125 | ''; | ||
126 | }; | ||
127 | serviceDeps = mkOption { | ||
128 | type = listOf str; | ||
129 | default = []; | ||
130 | description = '' | ||
131 | List of systemd services this application depends on | ||
132 | ''; | ||
133 | }; | ||
134 | }; | ||
135 | }); | ||
136 | }; | ||
137 | # Read-only variables | ||
138 | services.phpApplication.phpListenPaths = mkOption { | ||
139 | type = attrsOf path; | ||
140 | default = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | ||
141 | name config.services.phpfpm.pools."${name}".socket | ||
142 | ) cfg.apps; | ||
143 | readOnly = true; | ||
144 | description = '' | ||
145 | Full paths to listen for php | ||
146 | ''; | ||
147 | }; | ||
148 | }; | ||
149 | |||
150 | config = { | ||
151 | services.websites.env = attrsets.mapAttrs' (name: cfgs: attrsets.nameValuePair | ||
152 | name { | ||
153 | modules = [ "proxy_fcgi" ]; | ||
154 | watchPaths = builtins.concatLists (map (c: c.httpdWatchFiles) cfgs); | ||
155 | } | ||
156 | ) cfgByEnv; | ||
157 | |||
158 | services.phpfpm.pools = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | ||
159 | name { | ||
160 | user = icfg.httpdUser; | ||
161 | group = icfg.httpdUser; | ||
162 | settings = { | ||
163 | "listen.owner" = icfg.httpdUser; | ||
164 | "listen.group" = icfg.httpdGroup; | ||
165 | "php_admin_value[open_basedir]" = builtins.concatStringsSep ":" ([icfg.app icfg.varDir] ++ icfg.phpWatchFiles ++ icfg.phpOpenbasedir); | ||
166 | } | ||
167 | // optionalAttrs (icfg.phpSession) { "php_admin_value[session.save_path]" = "${icfg.varDir}/phpSessions"; } | ||
168 | // icfg.phpPool; | ||
169 | phpOptions = config.services.phpfpm.phpOptions + icfg.phpOptions; | ||
170 | inherit (icfg) phpEnv phpPackage; | ||
171 | } | ||
172 | ) cfg.apps; | ||
173 | |||
174 | services.filesWatcher = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | ||
175 | "phpfpm-${name}" { | ||
176 | restart = true; | ||
177 | paths = icfg.phpWatchFiles; | ||
178 | } | ||
179 | ) (attrsets.filterAttrs (n: v: builtins.length v.phpWatchFiles > 0) cfg.apps); | ||
180 | |||
181 | systemd.services = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | ||
182 | "phpfpm-${name}" { | ||
183 | after = lib.mkAfter icfg.serviceDeps; | ||
184 | wants = icfg.serviceDeps; | ||
185 | preStart = lib.mkAfter (optionalString (!isNull icfg.varDir) '' | ||
186 | watchFilesChanged() { | ||
187 | ${optionalString (builtins.length icfg.phpWatchFiles == 0) "return 1"} | ||
188 | [ ! -f "${icfg.varDir}"/watchedFiles ] \ | ||
189 | || ! sha512sum -c --status ${icfg.varDir}/watchedFiles | ||
190 | } | ||
191 | appDirChanged() { | ||
192 | [ ! -f "${icfg.varDir}/currentWebappDir" -o \ | ||
193 | "${icfg.app}" != "$(cat ${icfg.varDir}/currentWebappDir 2>/dev/null)" ] | ||
194 | } | ||
195 | updateWatchFiles() { | ||
196 | ${optionalString (builtins.length icfg.phpWatchFiles == 0) "return 0"} | ||
197 | sha512sum ${builtins.concatStringsSep " " icfg.phpWatchFiles} > ${icfg.varDir}/watchedFiles | ||
198 | } | ||
199 | |||
200 | if watchFilesChanged || appDirChanged; then | ||
201 | pushd ${icfg.app} > /dev/null | ||
202 | ${builtins.concatStringsSep "\n " (map (c: "/run/wrappers/bin/sudo -u ${icfg.httpdUser} ${c}") icfg.preStartActions) } | ||
203 | popd > /dev/null | ||
204 | echo -n "${icfg.app}" > ${icfg.varDir}/currentWebappDir | ||
205 | updateWatchFiles | ||
206 | fi | ||
207 | ''); | ||
208 | } | ||
209 | ) cfg.apps; | ||
210 | |||
211 | system.activationScripts = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | ||
212 | name { | ||
213 | deps = []; | ||
214 | text = optionalString (!isNull icfg.varDir) '' | ||
215 | install -m ${icfg.mode} -o ${icfg.httpdUser} -g ${icfg.httpdGroup} -d ${icfg.varDir} | ||
216 | '' + optionalString (icfg.phpSession) '' | ||
217 | install -m 0700 -o ${icfg.httpdUser} -g ${icfg.httpdGroup} -d ${icfg.varDir}/phpSessions | ||
218 | '' + builtins.concatStringsSep "\n" (attrsets.mapAttrsToList (n: v: '' | ||
219 | install -m ${v} -o ${icfg.httpdUser} -g ${icfg.httpdGroup} -d ${icfg.varDir}/${n} | ||
220 | '') icfg.varDirPaths); | ||
221 | } | ||
222 | ) cfg.apps; | ||
223 | }; | ||
224 | } | ||