{ pkgs, lib, config, options, ... }: with lib; let sites = config.modules.services.staticSites; staticSiteModule.options = { dataDir = mkOption { type = types.oneOf [ types.str types.path ]; default = null; }; auth = mkOption { type = types.attrsOf types.str; description = "Basic authentication options. Defines a set of user = password pairs."; example = literalExpr '' { user = "password"; anotherUser = "anotherPassword"; /* ... */ } ''; default = {}; }; disableLogsForMisc = mkOption { type = types.bool; description = "Disables access logs for /favicon.ico and /robots.txt"; default = true; }; denySensitivePaths = mkOption { type = types.bool; description = "Disables access to paths starting with a . (except well-known) to prevent leaking potentially sensitive data"; default = true; }; forceSSL = mkOption { type = types.bool; description = "Redirects HTTP requests to HTTPS."; default = true; }; }; in { options.modules.services.staticSites = mkOption { type = types.attrsOf (types.submodule staticSiteModule); example = literalExpression '' { "goop.network".dataDir = /var/www/goop.network; "reidlab.pink".dataDir = /etc/secret/private/reidlab-pink; } ''; default = {}; }; config = { assertions = mapAttrsToList (domain: _@{dataDir, ...}: { assertion = dataDir != null; description = "${domain} must specify a dataDir."; }) sites; services.nginx.virtualHosts = mkMerge (mapAttrsToList (domain: site: { ${domain} = { locations = mkMerge [ { "/".basicAuth = site.auth; } ( mkIf site.disableLogsForMisc { "= /favicon.ico".extraConfig = '' access_log off; log_not_found off; ''; "= /robots.txt".extraConfig = '' access_log off; log_not_found off; ''; }) ( mkIf site.denySensitivePaths { "${''~ /\.(?!well-known).*''}".extraConfig = ''deny all;''; }) ]; forceSSL = site.forceSSL; addSSL = !site.forceSSL; enableACME = true; root = site.dataDir; }; }) sites); }; }