]>
Commit | Line | Data |
---|---|---|
daf64e3f IB |
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 | } |