aboutsummaryrefslogtreecommitdiff
path: root/modules/websites/default.nix
diff options
context:
space:
mode:
Diffstat (limited to 'modules/websites/default.nix')
-rw-r--r--modules/websites/default.nix148
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;
2let
3 cfg = config.services.websites;
4in
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}