-
- perServerConf = isMainServer: cfg: let
-
- serverInfo = makeServerInfo cfg;
-
- subservices = callSubservices serverInfo cfg.extraSubservices;
-
- maybeDocumentRoot = fold (svc: acc:
- if acc == null then svc.documentRoot else assert svc.documentRoot == null; acc
- ) null ([ cfg ] ++ subservices);
-
- documentRoot = if maybeDocumentRoot != null then maybeDocumentRoot else
- pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out";
-
- documentRootConf = ''
- DocumentRoot "${documentRoot}"
-
- <Directory "${documentRoot}">
- Options Indexes FollowSymLinks
- AllowOverride None
- ${allGranted}
- </Directory>
- '';
-
- robotsTxt =
- concatStringsSep "\n" (filter (x: x != "") (
- # If this is a vhost, the include the entries for the main server as well.
- (if isMainServer then [] else [mainCfg.robotsEntries] ++ map (svc: svc.robotsEntries) mainSubservices)
- ++ [cfg.robotsEntries]
- ++ (map (svc: svc.robotsEntries) subservices)));
-
- in ''
- ${concatStringsSep "\n" (map (n: "ServerName ${n}") serverInfo.canonicalNames)}
-
- ${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases}
-
- ${if cfg.sslServerCert != null then ''
- SSLCertificateFile ${cfg.sslServerCert}
- SSLCertificateKeyFile ${cfg.sslServerKey}
- ${if cfg.sslServerChain != null then ''
- SSLCertificateChainFile ${cfg.sslServerChain}
- '' else ""}
- '' else ""}
-
- ${if cfg.enableSSL then ''
- SSLEngine on
- '' else if enableSSL then /* i.e., SSL is enabled for some host, but not this one */
- ''
- SSLEngine off
- '' else ""}
-
- ${if isMainServer || cfg.adminAddr != null then ''
- ServerAdmin ${cfg.adminAddr}
- '' else ""}
-
- ${if !isMainServer && mainCfg.logPerVirtualHost then ''
- ErrorLog ${mainCfg.logDir}/error-${cfg.hostName}.log
- CustomLog ${mainCfg.logDir}/access-${cfg.hostName}.log ${cfg.logFormat}
- '' else ""}
-
- ${optionalString (robotsTxt != "") ''
- Alias /robots.txt ${pkgs.writeText "robots.txt" robotsTxt}
- ''}
-
- ${if isMainServer || maybeDocumentRoot != null then documentRootConf else ""}
-
- ${if cfg.enableUserDir then ''
-
- UserDir public_html
- UserDir disabled root
-
- <Directory "/home/*/public_html">
- AllowOverride FileInfo AuthConfig Limit Indexes
- Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
- <Limit GET POST OPTIONS>
- ${allGranted}
- </Limit>
- <LimitExcept GET POST OPTIONS>
- ${allDenied}
- </LimitExcept>
- </Directory>
-
- '' else ""}
-
- ${if cfg.globalRedirect != null && cfg.globalRedirect != "" then ''
- RedirectPermanent / ${cfg.globalRedirect}
- '' else ""}
-
- ${
- let makeFileConf = elem: ''
- Alias ${elem.urlPath} ${elem.file}
- '';
- in concatMapStrings makeFileConf cfg.servedFiles
- }
-
- ${
- let makeDirConf = elem: ''
- Alias ${elem.urlPath} ${elem.dir}/
- <Directory ${elem.dir}>
- Options +Indexes
- ${allGranted}
- AllowOverride All
- </Directory>
- '';
- in concatMapStrings makeDirConf cfg.servedDirs
- }
-
- ${concatMapStrings (svc: svc.extraConfig) subservices}
-
- ${cfg.extraConfig}
- '';
+ mkVHostConf = hostOpts:
+ let
+ adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr;
+ listen = filter (listen: !listen.ssl) (mkListenInfo hostOpts);
+ listenSSL = filter (listen: listen.ssl) (mkListenInfo hostOpts);
+
+ useACME = hostOpts.enableACME || hostOpts.useACMEHost != null;
+ sslCertDir =
+ if hostOpts.enableACME then config.security.acme.certs.${hostOpts.hostName}.directory
+ else if hostOpts.useACMEHost != null then config.security.acme.certs.${hostOpts.useACMEHost}.directory
+ else abort "This case should never happen.";
+
+ sslServerCert = if useACME then "${sslCertDir}/full.pem" else hostOpts.sslServerCert;
+ sslServerKey = if useACME then "${sslCertDir}/key.pem" else hostOpts.sslServerKey;
+ sslServerChain = if useACME then "${sslCertDir}/fullchain.pem" else hostOpts.sslServerChain;
+
+ acmeChallenge = optionalString useACME ''
+ Alias /.well-known/acme-challenge/ "${hostOpts.acmeRoot}/.well-known/acme-challenge/"
+ <Directory "${hostOpts.acmeRoot}">
+ AllowOverride None
+ Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
+ Require method GET POST OPTIONS
+ Require all granted
+ </Directory>
+ '';
+ in
+ optionalString (listen != []) ''
+ <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listen}>
+ ServerName ${hostOpts.hostName}
+ ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
+ ServerAdmin ${adminAddr}
+ <IfModule mod_ssl.c>
+ SSLEngine off
+ </IfModule>
+ ${acmeChallenge}
+ ${if hostOpts.forceSSL then ''
+ <IfModule mod_rewrite.c>
+ RewriteEngine on
+ RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge [NC]
+ RewriteCond %{HTTPS} off
+ RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
+ </IfModule>
+ '' else mkVHostCommonConf hostOpts}
+ </VirtualHost>
+ '' +
+ optionalString (listenSSL != []) ''
+ <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listenSSL}>
+ ServerName ${hostOpts.hostName}
+ ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
+ ServerAdmin ${adminAddr}
+ SSLEngine on
+ SSLCertificateFile ${sslServerCert}
+ SSLCertificateKeyFile ${sslServerKey}
+ ${optionalString (sslServerChain != null) "SSLCertificateChainFile ${sslServerChain}"}
+ ${optionalString hostOpts.http2 "Protocols h2 h2c http/1.1"}
+ ${acmeChallenge}
+ ${mkVHostCommonConf hostOpts}
+ </VirtualHost>
+ ''
+ ;
+
+ mkVHostCommonConf = hostOpts:
+ let
+ documentRoot = if hostOpts.documentRoot != null
+ then hostOpts.documentRoot
+ else pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out"
+ ;
+
+ mkLocations = locations: concatStringsSep "\n" (map (config: ''
+ <Location ${config.location}>
+ ${optionalString (config.proxyPass != null) ''
+ <IfModule mod_proxy.c>
+ ProxyPass ${config.proxyPass}
+ ProxyPassReverse ${config.proxyPass}
+ </IfModule>
+ ''}
+ ${optionalString (config.index != null) ''
+ <IfModule mod_dir.c>
+ DirectoryIndex ${config.index}
+ </IfModule>
+ ''}
+ ${optionalString (config.alias != null) ''
+ <IfModule mod_alias.c>
+ Alias "${config.alias}"
+ </IfModule>
+ ''}
+ ${config.extraConfig}
+ </Location>
+ '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
+ in
+ ''
+ ${optionalString cfg.logPerVirtualHost ''
+ ErrorLog ${cfg.logDir}/error-${hostOpts.hostName}.log
+ CustomLog ${cfg.logDir}/access-${hostOpts.hostName}.log ${hostOpts.logFormat}
+ ''}
+
+ ${optionalString (hostOpts.robotsEntries != "") ''
+ Alias /robots.txt ${pkgs.writeText "robots.txt" hostOpts.robotsEntries}
+ ''}
+
+ DocumentRoot "${documentRoot}"
+
+ <Directory "${documentRoot}">
+ Options Indexes FollowSymLinks
+ AllowOverride None
+ Require all granted
+ </Directory>
+
+ ${optionalString hostOpts.enableUserDir ''
+ UserDir public_html
+ UserDir disabled root
+ <Directory "/home/*/public_html">
+ AllowOverride FileInfo AuthConfig Limit Indexes
+ Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
+ <Limit GET POST OPTIONS>
+ Require all granted
+ </Limit>
+ <LimitExcept GET POST OPTIONS>
+ Require all denied
+ </LimitExcept>
+ </Directory>
+ ''}
+
+ ${optionalString (hostOpts.globalRedirect != null && hostOpts.globalRedirect != "") ''
+ RedirectPermanent / ${hostOpts.globalRedirect}
+ ''}
+
+ ${
+ let makeDirConf = elem: ''
+ Alias ${elem.urlPath} ${elem.dir}/
+ <Directory ${elem.dir}>
+ Options +Indexes
+ Require all granted
+ AllowOverride All
+ </Directory>
+ '';
+ in concatMapStrings makeDirConf hostOpts.servedDirs
+ }
+
+ ${mkLocations hostOpts.locations}
+ ${hostOpts.extraConfig}
+ ''
+ ;