+{ lib, config, ... }: with lib;
+let
+ cfg = config.services.websites;
+in
+{
+ options.services.websites = with types; mkOption {
+ default = {};
+ description = "Each type of website to enable will target a distinct httpd server";
+ type = attrsOf (submodule {
+ options = {
+ enable = mkEnableOption "Enable websites of this type";
+ adminAddr = mkOption {
+ type = str;
+ description = "Admin e-mail address of the instance";
+ };
+ httpdName = mkOption {
+ type = str;
+ description = "Name of the httpd instance to assign this type to";
+ };
+ ips = mkOption {
+ type = listOf string;
+ default = [];
+ description = "ips to listen to";
+ };
+ modules = mkOption {
+ type = listOf str;
+ default = [];
+ description = "Additional modules to load in Apache";
+ };
+ extraConfig = mkOption {
+ type = listOf lines;
+ default = [];
+ description = "Additional configuration to append to Apache";
+ };
+ nosslVhost = mkOption {
+ description = "A default nossl vhost for captive portals";
+ default = {};
+ type = submodule {
+ options = {
+ enable = mkEnableOption "Add default no-ssl vhost for this instance";
+ host = mkOption {
+ type = string;
+ description = "The hostname to use for this vhost";
+ };
+ root = mkOption {
+ type = path;
+ default = ./nosslVhost;
+ description = "The root folder to serve";
+ };
+ indexFile = mkOption {
+ type = string;
+ default = "index.html";
+ description = "The index file to show.";
+ };
+ };
+ };
+ };
+ fallbackVhost = mkOption {
+ description = "The fallback vhost that will be defined as first vhost in Apache";
+ type = submodule {
+ options = {
+ certName = mkOption { type = string; };
+ hosts = mkOption { type = listOf string; };
+ root = mkOption { type = nullOr path; };
+ extraConfig = mkOption { type = listOf lines; default = []; };
+ };
+ };
+ };
+ vhostConfs = mkOption {
+ default = {};
+ description = "List of vhosts to define for Apache";
+ type = attrsOf (submodule {
+ options = {
+ certName = mkOption { type = string; };
+ hosts = mkOption { type = listOf string; };
+ root = mkOption { type = nullOr path; };
+ extraConfig = mkOption { type = listOf lines; default = []; };
+ };
+ });
+ };
+ };
+ });
+ };
+
+ config.services.httpd = let
+ redirectVhost = ips: { # Should go last, catchall http -> https redirect
+ listen = map (ip: { inherit ip; port = 80; }) ips;
+ hostName = "redirectSSL";
+ serverAliases = [ "*" ];
+ enableSSL = false;
+ logFormat = "combinedVhost";
+ documentRoot = "/var/lib/acme/acme-challenge";
+ extraConfig = ''
+ RewriteEngine on
+ RewriteCond "%{REQUEST_URI}" "!^/\.well-known"
+ RewriteRule ^(.+) https://%{HTTP_HOST}$1 [R=301]
+ # To redirect in specific "VirtualHost *:80", do
+ # RedirectMatch 301 ^/((?!\.well-known.*$).*)$ https://host/$1
+ # rather than rewrite
+ '';
+ };
+ nosslVhost = ips: cfg: {
+ listen = map (ip: { inherit ip; port = 80; }) ips;
+ hostName = cfg.host;
+ enableSSL = false;
+ logFormat = "combinedVhost";
+ documentRoot = cfg.root;
+ extraConfig = ''
+ <Directory ${cfg.root}>
+ DirectoryIndex ${cfg.indexFile}
+ AllowOverride None
+ Require all granted
+
+ RewriteEngine on
+ RewriteRule ^/(.+) / [L]
+ </Directory>
+ '';
+ };
+ toVhost = ips: vhostConf: {
+ enableSSL = true;
+ sslServerCert = "/var/lib/acme/${vhostConf.certName}/cert.pem";
+ sslServerKey = "/var/lib/acme/${vhostConf.certName}/key.pem";
+ sslServerChain = "/var/lib/acme/${vhostConf.certName}/chain.pem";
+ logFormat = "combinedVhost";
+ listen = map (ip: { inherit ip; port = 443; }) ips;
+ hostName = builtins.head vhostConf.hosts;
+ serverAliases = builtins.tail vhostConf.hosts or [];
+ documentRoot = vhostConf.root;
+ extraConfig = builtins.concatStringsSep "\n" vhostConf.extraConfig;
+ };
+ in attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair
+ icfg.httpdName (mkIf icfg.enable {
+ enable = true;
+ listen = map (ip: { inherit ip; port = 443; }) icfg.ips;
+ stateDir = "/run/httpd_${name}";
+ logPerVirtualHost = true;
+ multiProcessingModule = "worker";
+ inherit (icfg) adminAddr;
+ logFormat = "combinedVhost";
+ extraModules = lists.unique icfg.modules;
+ extraConfig = builtins.concatStringsSep "\n" icfg.extraConfig;
+ virtualHosts = [ (toVhost icfg.ips icfg.fallbackVhost) ]
+ ++ optionals (icfg.nosslVhost.enable) [ (nosslVhost icfg.ips icfg.nosslVhost) ]
+ ++ (attrsets.mapAttrsToList (n: v: toVhost icfg.ips v) icfg.vhostConfs)
+ ++ [ (redirectVhost icfg.ips) ];
+ })
+ ) cfg;
+}