]> git.immae.eu Git - perso/Immae/Config/Nix.git/blame - modules/websites/default.nix
Remove webappdirs
[perso/Immae/Config/Nix.git] / modules / websites / default.nix
CommitLineData
32021034 1{ lib, config, pkgs, ... }: with lib;
daf64e3f 2let
29f8cb85 3 cfg = config.services.websites;
daf64e3f
IB
4in
5{
29f8cb85
IB
6 options.services.websites = with types; {
7 certs = mkOption {
8 description = "Default websites configuration for certificates as accepted by acme";
9 };
29f8cb85
IB
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 {
5400b9b6 25 type = listOf str;
29f8cb85
IB
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 {
5400b9b6 46 type = str;
29f8cb85
IB
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 {
5400b9b6 55 type = str;
29f8cb85
IB
56 default = "index.html";
57 description = "The index file to show.";
58 };
daf64e3f
IB
59 };
60 };
61 };
29f8cb85
IB
62 fallbackVhost = mkOption {
63 description = "The fallback vhost that will be defined as first vhost in Apache";
64 type = submodule {
65 options = {
5400b9b6
IB
66 certName = mkOption { type = str; };
67 hosts = mkOption { type = listOf str; };
29f8cb85 68 root = mkOption { type = nullOr path; };
e7b890d0
IB
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 };
29f8cb85
IB
77 extraConfig = mkOption { type = listOf lines; default = []; };
78 };
daf64e3f
IB
79 };
80 };
9a414bd6
IB
81 vhostNoSSLConfs = mkOption {
82 default = {};
83 description = "List of no ssl vhosts to define for Apache";
84 type = attrsOf (submodule {
85 options = {
5400b9b6 86 hosts = mkOption { type = listOf str; };
9a414bd6
IB
87 root = mkOption { type = nullOr path; };
88 extraConfig = mkOption { type = listOf lines; default = []; };
89 };
90 });
91 };
29f8cb85
IB
92 vhostConfs = mkOption {
93 default = {};
94 description = "List of vhosts to define for Apache";
95 type = attrsOf (submodule {
96 options = {
5400b9b6 97 certName = mkOption { type = str; };
29f8cb85
IB
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 {
5400b9b6 104 type = nullOr str;
29f8cb85
IB
105 description = "Use that host as 'main host' for acme certs";
106 default = null;
107 };
5400b9b6 108 hosts = mkOption { type = listOf str; };
29f8cb85 109 root = mkOption { type = nullOr path; };
e7b890d0
IB
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 };
29f8cb85 118 extraConfig = mkOption { type = listOf lines; default = []; };
7df420c2 119 };
29f8cb85
IB
120 });
121 };
122 watchPaths = mkOption {
5400b9b6 123 type = listOf str;
29f8cb85
IB
124 default = [];
125 description = ''
126 Paths to watch that should trigger a reload of httpd
127 '';
128 };
17f6eae9 129 };
29f8cb85
IB
130 });
131 };
daf64e3f
IB
132 };
133
134 config.services.httpd = let
daf64e3f
IB
135 nosslVhost = ips: cfg: {
136 listen = map (ip: { inherit ip; port = 80; }) ips;
137 hostName = cfg.host;
daf64e3f
IB
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: {
e7b890d0
IB
152 forceSSL = vhostConf.forceSSL or true;
153 useACMEHost = vhostConf.certName;
daf64e3f 154 logFormat = "combinedVhost";
e7b890d0
IB
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;
daf64e3f
IB
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 };
9a414bd6 163 toVhostNoSSL = ips: vhostConf: {
9a414bd6
IB
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 };
daf64e3f
IB
171 in attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair
172 icfg.httpdName (mkIf icfg.enable {
173 enable = true;
daf64e3f
IB
174 logPerVirtualHost = true;
175 multiProcessingModule = "worker";
29252c23 176 # https://ssl-config.mozilla.org/#server=apache&version=2.4.41&config=intermediate&openssl=1.0.2t&guideline=5.4
41521c75 177 # test with https://www.ssllabs.com/ssltest/analyze.html?d=www.immae.eu&s=176.9.151.154&latest
29252c23
IB
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 ];
daf64e3f
IB
185 inherit (icfg) adminAddr;
186 logFormat = "combinedVhost";
187 extraModules = lists.unique icfg.modules;
188 extraConfig = builtins.concatStringsSep "\n" icfg.extraConfig;
e7b890d0
IB
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);
daf64e3f 196 })
f4da0504 197 ) cfg.env;
7df420c2 198
17f6eae9
IB
199 config.services.filesWatcher = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair
200 "httpd${icfg.httpdName}" {
201 paths = icfg.watchPaths;
202 waitTime = 5;
203 }
f4da0504 204 ) cfg.env;
17f6eae9 205
5400b9b6 206 config.security.acme.certs = let
f4da0504 207 typesToManage = attrsets.filterAttrs (k: v: v.enable) cfg.env;
7df420c2
IB
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))
f4da0504 233 then cfg.certs // {
7df420c2
IB
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;
f4da0504 243
32021034
IB
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;
daf64e3f 281}