1 { lib, config, ... }: with lib;
3 cfg = config.services.websites;
6 options.services.websites = with types; mkOption {
8 description = "Each type of website to enable will target a distinct httpd server";
9 type = attrsOf (submodule {
11 enable = mkEnableOption "Enable websites of this type";
12 adminAddr = mkOption {
14 description = "Admin e-mail address of the instance";
16 httpdName = mkOption {
18 description = "Name of the httpd instance to assign this type to";
23 description = "ips to listen to";
28 description = "Additional modules to load in Apache";
30 extraConfig = mkOption {
33 description = "Additional configuration to append to Apache";
35 nosslVhost = mkOption {
36 description = "A default nossl vhost for captive portals";
40 enable = mkEnableOption "Add default no-ssl vhost for this instance";
43 description = "The hostname to use for this vhost";
47 default = ./nosslVhost;
48 description = "The root folder to serve";
50 indexFile = mkOption {
52 default = "index.html";
53 description = "The index file to show.";
58 fallbackVhost = mkOption {
59 description = "The fallback vhost that will be defined as first vhost in Apache";
62 certName = mkOption { type = string; };
63 hosts = mkOption { type = listOf string; };
64 root = mkOption { type = nullOr path; };
65 extraConfig = mkOption { type = listOf lines; default = []; };
69 vhostConfs = mkOption {
71 description = "List of vhosts to define for Apache";
72 type = attrsOf (submodule {
74 certName = mkOption { type = string; };
75 hosts = mkOption { type = listOf string; };
76 root = mkOption { type = nullOr path; };
77 extraConfig = mkOption { type = listOf lines; default = []; };
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 = [ "*" ];
91 logFormat = "combinedVhost";
92 documentRoot = "/var/lib/acme/acme-challenge";
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
102 nosslVhost = ips: cfg: {
103 listen = map (ip: { inherit ip; port = 80; }) ips;
106 logFormat = "combinedVhost";
107 documentRoot = cfg.root;
109 <Directory ${cfg.root}>
110 DirectoryIndex ${cfg.indexFile}
115 RewriteRule ^/(.+) / [L]
119 toVhost = ips: vhostConf: {
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;
131 in attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair
132 icfg.httpdName (mkIf icfg.enable {
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) ];