]> git.immae.eu Git - perso/Immae/Config/Nix.git/blobdiff - modules/websites/default.nix
Start moving websites configuration to modules
[perso/Immae/Config/Nix.git] / modules / websites / default.nix
diff --git a/modules/websites/default.nix b/modules/websites/default.nix
new file mode 100644 (file)
index 0000000..6a18c8a
--- /dev/null
@@ -0,0 +1,148 @@
+{ 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;
+}