diff options
Diffstat (limited to 'modules/websites/default.nix')
-rw-r--r-- | modules/websites/default.nix | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/modules/websites/default.nix b/modules/websites/default.nix new file mode 100644 index 0000000..6a18c8a --- /dev/null +++ b/modules/websites/default.nix | |||
@@ -0,0 +1,148 @@ | |||
1 | { lib, config, ... }: with lib; | ||
2 | let | ||
3 | cfg = config.services.websites; | ||
4 | in | ||
5 | { | ||
6 | options.services.websites = with types; mkOption { | ||
7 | default = {}; | ||
8 | description = "Each type of website to enable will target a distinct httpd server"; | ||
9 | type = attrsOf (submodule { | ||
10 | options = { | ||
11 | enable = mkEnableOption "Enable websites of this type"; | ||
12 | adminAddr = mkOption { | ||
13 | type = str; | ||
14 | description = "Admin e-mail address of the instance"; | ||
15 | }; | ||
16 | httpdName = mkOption { | ||
17 | type = str; | ||
18 | description = "Name of the httpd instance to assign this type to"; | ||
19 | }; | ||
20 | ips = mkOption { | ||
21 | type = listOf string; | ||
22 | default = []; | ||
23 | description = "ips to listen to"; | ||
24 | }; | ||
25 | modules = mkOption { | ||
26 | type = listOf str; | ||
27 | default = []; | ||
28 | description = "Additional modules to load in Apache"; | ||
29 | }; | ||
30 | extraConfig = mkOption { | ||
31 | type = listOf lines; | ||
32 | default = []; | ||
33 | description = "Additional configuration to append to Apache"; | ||
34 | }; | ||
35 | nosslVhost = mkOption { | ||
36 | description = "A default nossl vhost for captive portals"; | ||
37 | default = {}; | ||
38 | type = submodule { | ||
39 | options = { | ||
40 | enable = mkEnableOption "Add default no-ssl vhost for this instance"; | ||
41 | host = mkOption { | ||
42 | type = string; | ||
43 | description = "The hostname to use for this vhost"; | ||
44 | }; | ||
45 | root = mkOption { | ||
46 | type = path; | ||
47 | default = ./nosslVhost; | ||
48 | description = "The root folder to serve"; | ||
49 | }; | ||
50 | indexFile = mkOption { | ||
51 | type = string; | ||
52 | default = "index.html"; | ||
53 | description = "The index file to show."; | ||
54 | }; | ||
55 | }; | ||
56 | }; | ||
57 | }; | ||
58 | fallbackVhost = mkOption { | ||
59 | description = "The fallback vhost that will be defined as first vhost in Apache"; | ||
60 | type = submodule { | ||
61 | options = { | ||
62 | certName = mkOption { type = string; }; | ||
63 | hosts = mkOption { type = listOf string; }; | ||
64 | root = mkOption { type = nullOr path; }; | ||
65 | extraConfig = mkOption { type = listOf lines; default = []; }; | ||
66 | }; | ||
67 | }; | ||
68 | }; | ||
69 | vhostConfs = mkOption { | ||
70 | default = {}; | ||
71 | description = "List of vhosts to define for Apache"; | ||
72 | type = attrsOf (submodule { | ||
73 | options = { | ||
74 | certName = mkOption { type = string; }; | ||
75 | hosts = mkOption { type = listOf string; }; | ||
76 | root = mkOption { type = nullOr path; }; | ||
77 | extraConfig = mkOption { type = listOf lines; default = []; }; | ||
78 | }; | ||
79 | }); | ||
80 | }; | ||
81 | }; | ||
82 | }); | ||
83 | }; | ||
84 | |||
85 | config.services.httpd = let | ||
86 | redirectVhost = ips: { # Should go last, catchall http -> https redirect | ||
87 | listen = map (ip: { inherit ip; port = 80; }) ips; | ||
88 | hostName = "redirectSSL"; | ||
89 | serverAliases = [ "*" ]; | ||
90 | enableSSL = false; | ||
91 | logFormat = "combinedVhost"; | ||
92 | documentRoot = "/var/lib/acme/acme-challenge"; | ||
93 | extraConfig = '' | ||
94 | RewriteEngine on | ||
95 | RewriteCond "%{REQUEST_URI}" "!^/\.well-known" | ||
96 | RewriteRule ^(.+) https://%{HTTP_HOST}$1 [R=301] | ||
97 | # To redirect in specific "VirtualHost *:80", do | ||
98 | # RedirectMatch 301 ^/((?!\.well-known.*$).*)$ https://host/$1 | ||
99 | # rather than rewrite | ||
100 | ''; | ||
101 | }; | ||
102 | nosslVhost = ips: cfg: { | ||
103 | listen = map (ip: { inherit ip; port = 80; }) ips; | ||
104 | hostName = cfg.host; | ||
105 | enableSSL = false; | ||
106 | logFormat = "combinedVhost"; | ||
107 | documentRoot = cfg.root; | ||
108 | extraConfig = '' | ||
109 | <Directory ${cfg.root}> | ||
110 | DirectoryIndex ${cfg.indexFile} | ||
111 | AllowOverride None | ||
112 | Require all granted | ||
113 | |||
114 | RewriteEngine on | ||
115 | RewriteRule ^/(.+) / [L] | ||
116 | </Directory> | ||
117 | ''; | ||
118 | }; | ||
119 | toVhost = ips: vhostConf: { | ||
120 | enableSSL = true; | ||
121 | sslServerCert = "/var/lib/acme/${vhostConf.certName}/cert.pem"; | ||
122 | sslServerKey = "/var/lib/acme/${vhostConf.certName}/key.pem"; | ||
123 | sslServerChain = "/var/lib/acme/${vhostConf.certName}/chain.pem"; | ||
124 | logFormat = "combinedVhost"; | ||
125 | listen = map (ip: { inherit ip; port = 443; }) ips; | ||
126 | hostName = builtins.head vhostConf.hosts; | ||
127 | serverAliases = builtins.tail vhostConf.hosts or []; | ||
128 | documentRoot = vhostConf.root; | ||
129 | extraConfig = builtins.concatStringsSep "\n" vhostConf.extraConfig; | ||
130 | }; | ||
131 | in attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | ||
132 | icfg.httpdName (mkIf icfg.enable { | ||
133 | enable = true; | ||
134 | listen = map (ip: { inherit ip; port = 443; }) icfg.ips; | ||
135 | stateDir = "/run/httpd_${name}"; | ||
136 | logPerVirtualHost = true; | ||
137 | multiProcessingModule = "worker"; | ||
138 | inherit (icfg) adminAddr; | ||
139 | logFormat = "combinedVhost"; | ||
140 | extraModules = lists.unique icfg.modules; | ||
141 | extraConfig = builtins.concatStringsSep "\n" icfg.extraConfig; | ||
142 | virtualHosts = [ (toVhost icfg.ips icfg.fallbackVhost) ] | ||
143 | ++ optionals (icfg.nosslVhost.enable) [ (nosslVhost icfg.ips icfg.nosslVhost) ] | ||
144 | ++ (attrsets.mapAttrsToList (n: v: toVhost icfg.ips v) icfg.vhostConfs) | ||
145 | ++ [ (redirectVhost icfg.ips) ]; | ||
146 | }) | ||
147 | ) cfg; | ||
148 | } | ||