diff options
Diffstat (limited to 'modules/websites/default.nix')
-rw-r--r-- | modules/websites/default.nix | 281 |
1 files changed, 0 insertions, 281 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 | } | ||