let
- mainCfg = config.services.httpd."${httpdName}";
+ cfg = config.services.httpd."${httpdName}";
- httpd = mainCfg.package.out;
+ runtimeDir = "/run/httpd_${httpdName}";
- httpdConf = mainCfg.configFile;
+ pkg = cfg.package.out;
- php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ };
+ httpdConf = cfg.configFile;
- phpMajorVersion = head (splitString "." php.version);
+ php = cfg.phpPackage.override { apacheHttpd = pkg.dev; /* otherwise it only gets .out */ };
- mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; };
+ phpMajorVersion = lib.versions.major (lib.getVersion php);
- defaultListen = cfg: if cfg.enableSSL
- then [{ip = "*"; port = 443;}]
- else [{ip = "*"; port = 80;}];
+ mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; };
- getListen = cfg:
- if cfg.listen == []
- then defaultListen cfg
- else cfg.listen;
+ vhosts = attrValues cfg.virtualHosts;
- listenToString = l: "${l.ip}:${toString l.port}";
+ mkListenInfo = hostOpts:
+ if hostOpts.listen != [] then hostOpts.listen
+ else (
+ optional (hostOpts.onlySSL || hostOpts.addSSL || hostOpts.forceSSL) { ip = "*"; port = 443; ssl = true; } ++
+ optional (!hostOpts.onlySSL) { ip = "*"; port = 80; ssl = false; }
+ );
- extraModules = attrByPath ["extraModules"] [] mainCfg;
- extraForeignModules = filter isAttrs extraModules;
- extraApacheModules = filter isString extraModules;
+ listenInfo = unique (concatMap mkListenInfo vhosts);
+ enableHttp2 = any (vhost: vhost.http2) vhosts;
+ enableSSL = any (listen: listen.ssl) listenInfo;
+ enableUserDir = any (vhost: vhost.enableUserDir) vhosts;
- makeServerInfo = cfg: {
- # Canonical name must not include a trailing slash.
- canonicalNames =
- let defaultPort = (head (defaultListen cfg)).port; in
- map (port:
- (if cfg.enableSSL then "https" else "http") + "://" +
- cfg.hostName +
- (if port != defaultPort then ":${toString port}" else "")
- ) (map (x: x.port) (getListen cfg));
-
- # Admin address: inherit from the main server if not specified for
- # a virtual host.
- adminAddr = if cfg.adminAddr != null then cfg.adminAddr else mainCfg.adminAddr;
-
- vhostConfig = cfg;
- serverConfig = mainCfg;
- fullConfig = config; # machine config
- };
-
-
- allHosts = [mainCfg] ++ mainCfg.virtualHosts;
-
-
- callSubservices = serverInfo: defs:
- let f = svc:
- let
- svcFunction =
- if svc ? function then svc.function
- # instead of using serviceType="mediawiki"; you can copy mediawiki.nix to any location outside nixpkgs, modify it at will, and use serviceExpression=./mediawiki.nix;
- else if svc ? serviceExpression then import (toString svc.serviceExpression)
- else import (toString "${toString ./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix");
- config = (evalModules
- { modules = [ { options = res.options; config = svc.config or svc; } ];
- check = false;
- }).config;
- defaults = {
- extraConfig = "";
- extraModules = [];
- extraModulesPre = [];
- extraPath = [];
- extraServerPath = [];
- globalEnvVars = [];
- robotsEntries = "";
- startupScript = "";
- enablePHP = false;
- enablePerl = false;
- phpOptions = "";
- options = {};
- documentRoot = null;
- };
- res = defaults // svcFunction { inherit config lib pkgs serverInfo php; };
- in res;
- in map f defs;
-
-
- # !!! callSubservices is expensive
- subservicesFor = cfg: callSubservices (makeServerInfo cfg) cfg.extraSubservices;
-
- mainSubservices = subservicesFor mainCfg;
-
- allSubservices = mainSubservices ++ concatMap subservicesFor mainCfg.virtualHosts;
-
-
- enableSSL = any (vhost: vhost.enableSSL) allHosts;
-
-
- # Names of modules from ${httpd}/modules that we want to load.
- apacheModules =
- [ # HTTP authentication mechanisms: basic and digest.
- "auth_basic" "auth_digest"
-
- # Authentication: is the user who he claims to be?
- "authn_file" "authn_dbm" "authn_anon" "authn_core"
-
- # Authorization: is the user allowed access?
- "authz_user" "authz_groupfile" "authz_host" "authz_core"
-
- # Other modules.
- "ext_filter" "include" "log_config" "env" "mime_magic"
- "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif"
- "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
- "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
- "userdir" "alias" "rewrite" "proxy" "proxy_http"
- "unixd" "cache" "cache_disk" "slotmem_shm" "socache_shmcb"
- "mpm_${mainCfg.multiProcessingModule}"
-
- # For compatibility with old configurations, the new module mod_access_compat is provided.
- "access_compat"
+ # NOTE: generally speaking order of modules is very important
+ modules =
+ [ # required apache modules our httpd service cannot run without
+ "authn_core" "authz_core"
+ "log_config"
+ "mime" "autoindex" "negotiation" "dir"
+ "alias" "rewrite"
+ "unixd" "slotmem_shm" "socache_shmcb"
+ "mpm_${cfg.multiProcessingModule}"
]
- ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
+ ++ (if cfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
+ ++ optional enableHttp2 "http2"
++ optional enableSSL "ssl"
- ++ extraApacheModules;
-
-
- allDenied = "Require all denied";
- allGranted = "Require all granted";
-
+ ++ optional enableUserDir "userdir"
+ ++ optional cfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
+ ++ optional cfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
+ ++ optional cfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
+ ++ cfg.extraModules;
- loggingConf = (if mainCfg.logFormat != "none" then ''
- ErrorLog ${mainCfg.logDir}/error.log
+ loggingConf = (if cfg.logFormat != "none" then ''
+ ErrorLog ${cfg.logDir}/error.log
LogLevel notice
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
- CustomLog ${mainCfg.logDir}/access.log ${mainCfg.logFormat}
+ CustomLog ${cfg.logDir}/access.log ${cfg.logFormat}
'' else ''
ErrorLog /dev/null
'');
browserHacks = ''
- BrowserMatch "Mozilla/2" nokeepalive
- BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
- BrowserMatch "RealPlayer 4\.0" force-response-1.0
- BrowserMatch "Java/1\.0" force-response-1.0
- BrowserMatch "JDK/1\.0" force-response-1.0
- BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
- BrowserMatch "^WebDrive" redirect-carefully
- BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
- BrowserMatch "^gnome-vfs" redirect-carefully
+ <IfModule mod_setenvif.c>
+ BrowserMatch "Mozilla/2" nokeepalive
+ BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
+ BrowserMatch "RealPlayer 4\.0" force-response-1.0
+ BrowserMatch "Java/1\.0" force-response-1.0
+ BrowserMatch "JDK/1\.0" force-response-1.0
+ BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
+ BrowserMatch "^WebDrive" redirect-carefully
+ BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
+ BrowserMatch "^gnome-vfs" redirect-carefully
+ </IfModule>
'';
sslConf = ''
- SSLSessionCache shmcb:${mainCfg.stateDir}/ssl_scache(512000)
+ <IfModule mod_ssl.c>
+ SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000)
- Mutex posixsem
+ Mutex posixsem
- SSLRandomSeed startup builtin
- SSLRandomSeed connect builtin
+ SSLRandomSeed startup builtin
+ SSLRandomSeed connect builtin
- SSLProtocol ${mainCfg.sslProtocols}
- SSLCipherSuite ${mainCfg.sslCiphers}
- SSLHonorCipherOrder on
+ SSLProtocol ${cfg.sslProtocols}
+ SSLCipherSuite ${cfg.sslCiphers}
+ SSLHonorCipherOrder on
+ </IfModule>
'';
mimeConf = ''
- TypesConfig ${httpd}/conf/mime.types
+ TypesConfig ${pkg}/conf/mime.types
AddType application/x-x509-ca-cert .crt
AddType application/x-pkcs7-crl .crl
AddType application/x-httpd-php .php .phtml
<IfModule mod_mime_magic.c>
- MIMEMagicFile ${httpd}/conf/magic
+ MIMEMagicFile ${pkg}/conf/magic
</IfModule>
'';
-
- 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}
+ ''
+ ;
confFile = pkgs.writeText "httpd.conf" ''
- ServerRoot ${httpd}
+ ServerRoot ${pkg}
+ ServerName ${config.networking.hostName}
+ DefaultRuntimeDir ${runtimeDir}/runtime
- DefaultRuntimeDir ${mainCfg.stateDir}/runtime
+ PidFile ${runtimeDir}/httpd.pid
- PidFile ${mainCfg.stateDir}/httpd.pid
-
- ${optionalString (mainCfg.multiProcessingModule != "prefork") ''
+ ${optionalString (cfg.multiProcessingModule != "prefork") ''
# mod_cgid requires this.
- ScriptSock ${mainCfg.stateDir}/cgisock
+ ScriptSock ${runtimeDir}/cgisock
''}
<IfModule prefork.c>
- MaxClients ${toString mainCfg.maxClients}
- MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild}
+ MaxClients ${toString cfg.maxClients}
+ MaxRequestsPerChild ${toString cfg.maxRequestsPerChild}
</IfModule>
${let
- listen = concatMap getListen allHosts;
- toStr = listen: "Listen ${listenToString listen}\n";
- uniqueListen = uniqList {inputList = map toStr listen;};
- in concatStrings uniqueListen
+ toStr = listen: "Listen ${listen.ip}:${toString listen.port} ${if listen.ssl then "https" else "http"}";
+ uniqueListen = uniqList {inputList = map toStr listenInfo;};
+ in concatStringsSep "\n" uniqueListen
}
- User ${mainCfg.user}
- Group ${mainCfg.group}
+ User ${cfg.user}
+ Group ${cfg.group}
${let
- load = {name, path}: "LoadModule ${name}_module ${path}\n";
- allModules =
- concatMap (svc: svc.extraModulesPre) allSubservices
- ++ map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules
- ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
- ++ optional enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
- ++ optional enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
- ++ concatMap (svc: svc.extraModules) allSubservices
- ++ extraForeignModules;
- in concatMapStrings load (unique allModules)
+ mkModule = module:
+ if isString module then { name = module; path = "${pkg}/modules/mod_${module}.so"; }
+ else if isAttrs module then { inherit (module) name path; }
+ else throw "Expecting either a string or attribute set including a name and path.";
+ in
+ concatMapStringsSep "\n" (module: "LoadModule ${module.name}_module ${module.path}") (unique (map mkModule modules))
}
AddHandler type-map var
<Files ~ "^\.ht">
- ${allDenied}
+ Require all denied
</Files>
${mimeConf}
${loggingConf}
${browserHacks}
- Include ${httpd}/conf/extra/httpd-default.conf
- Include ${httpd}/conf/extra/httpd-autoindex.conf
- Include ${httpd}/conf/extra/httpd-multilang-errordoc.conf
- Include ${httpd}/conf/extra/httpd-languages.conf
+ Include ${pkg}/conf/extra/httpd-default.conf
+ Include ${pkg}/conf/extra/httpd-autoindex.conf
+ Include ${pkg}/conf/extra/httpd-multilang-errordoc.conf
+ Include ${pkg}/conf/extra/httpd-languages.conf
TraceEnable off
- ${if enableSSL then sslConf else ""}
+ ${sslConf}
# Fascist default - deny access to everything.
<Directory />
Options FollowSymLinks
AllowOverride None
- ${allDenied}
+ Require all denied
</Directory>
- # Generate directives for the main server.
- ${perServerConf true mainCfg}
+ ${cfg.extraConfig}
- ${let
- makeVirtualHost = vhost: ''
- <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}>
- ${perServerConf false vhost}
- </VirtualHost>
- '';
- in concatMapStrings makeVirtualHost mainCfg.virtualHosts
- }
+ ${concatMapStringsSep "\n" mkVHostConf vhosts}
'';
-
- enablePHP = mainCfg.enablePHP || any (svc: svc.enablePHP) allSubservices;
-
- enablePerl = mainCfg.enablePerl || any (svc: svc.enablePerl) allSubservices;
-
-
# Generate the PHP configuration file. Should probably be factored
# out into a separate module.
phpIni = pkgs.runCommand "php.ini"
- { options = concatStringsSep "\n"
- ([ mainCfg.phpOptions ] ++ (map (svc: svc.phpOptions) allSubservices));
+ { options = cfg.phpOptions;
preferLocalBuild = true;
}
''
{
- ###### interface
+ imports = [
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.")
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.")
+
+ # virtualHosts options
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "documentRoot" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "enableSSL" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "enableUserDir" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "globalRedirect" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "hostName" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "listen" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "robotsEntries" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "servedDirs" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "servedFiles" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "serverAliases" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "sslServerCert" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "sslServerChain" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+ (mkRemovedOptionModule [ "services" "httpd" httpdName "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+ ];
+
+ # interface
options = {
services.httpd."${httpdName}" = {
- enable = mkOption {
- type = types.bool;
- default = false;
- description = "Whether to enable the Apache HTTP Server.";
- };
+ enable = mkEnableOption "the Apache HTTP Server";
package = mkOption {
type = types.package;
type = types.lines;
default = "";
description = ''
- Cnfiguration lines appended to the generated Apache
- configuration file. Note that this mechanism may not work
+ Configuration lines appended to the generated Apache
+ configuration file. Note that this mechanism will not work
when <option>configFile</option> is overridden.
'';
};
extraModules = mkOption {
type = types.listOf types.unspecified;
default = [];
- example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]'';
+ example = literalExample ''
+ [
+ "proxy_connect"
+ { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
+ ]
+ '';
description = ''
- Additional Apache modules to be used. These can be
+ Additional Apache modules to be used. These can be
specified as a string in the case of modules distributed
with Apache, or as an attribute set specifying the
<varname>name</varname> and <varname>path</varname> of the
'';
};
+ adminAddr = mkOption {
+ type = types.str;
+ example = "admin@example.org";
+ description = "E-mail address of the server administrator.";
+ };
+
+ logFormat = mkOption {
+ type = types.str;
+ default = "common";
+ example = "combined";
+ description = ''
+ Log format for log files. Possible values are: combined, common, referer, agent.
+ See <link xlink:href="https://httpd.apache.org/docs/2.4/logs.html"/> for more details.
+ '';
+ };
+
logPerVirtualHost = mkOption {
type = types.bool;
- default = false;
+ default = true;
description = ''
If enabled, each virtual host gets its own
<filename>access.log</filename> and
type = types.str;
default = "wwwrun";
description = ''
- User account under which httpd runs. The account is created
- automatically if it doesn't exist.
+ User account under which httpd runs.
'';
};
type = types.str;
default = "wwwrun";
description = ''
- Group under which httpd runs. The account is created
- automatically if it doesn't exist.
+ Group under which httpd runs.
'';
};
type = types.path;
default = "/var/log/httpd";
description = ''
- Directory for Apache's log files. It is created automatically.
- '';
- };
-
- stateDir = mkOption {
- type = types.path;
- default = "/run/httpd";
- description = ''
- Directory for Apache's transient runtime state (such as PID
- files). It is created automatically. Note that the default,
- <filename>/run/httpd</filename>, is deleted at boot time.
+ Directory for Apache's log files. It is created automatically.
'';
};
virtualHosts = mkOption {
- type = types.listOf (types.submodule (
- { options = import <nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix> {
- inherit lib;
- forMainServer = false;
+ type = with types; attrsOf (submodule (import ./vhost-options.nix));
+ default = {
+ localhost = {
+ documentRoot = "${pkg}/htdocs";
+ };
+ };
+ example = literalExample ''
+ {
+ "foo.example.com" = {
+ forceSSL = true;
+ documentRoot = "/var/www/foo.example.com"
+ };
+ "bar.example.com" = {
+ addSSL = true;
+ documentRoot = "/var/www/bar.example.com";
};
- }));
- default = [];
- example = [
- { hostName = "foo";
- documentRoot = "/data/webroot-foo";
- }
- { hostName = "bar";
- documentRoot = "/data/webroot-bar";
}
- ];
+ '';
description = ''
- Specification of the virtual hosts served by Apache. Each
+ Specification of the virtual hosts served by Apache. Each
element should be an attribute set specifying the
- configuration of the virtual host. The available options
- are the non-global options permissible for the main host.
+ configuration of the virtual host.
'';
};
''
date.timezone = "CET"
'';
- description =
- "Options appended to the PHP configuration file <filename>php.ini</filename>.";
+ description = ''
+ Options appended to the PHP configuration file <filename>php.ini</filename>.
+ '';
};
multiProcessingModule = mkOption {
- type = types.str;
+ type = types.enum [ "event" "prefork" "worker" ];
default = "prefork";
example = "worker";
description =
''
- Multi-processing module to be used by Apache. Available
+ Multi-processing module to be used by Apache. Available
modules are <literal>prefork</literal> (the default;
handles each request in a separate child process),
<literal>worker</literal> (hybrid approach that starts a
type = types.int;
default = 0;
example = 500;
- description =
- "Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited";
+ description = ''
+ Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited.
+ '';
};
sslCiphers = mkOption {
sslProtocols = mkOption {
type = types.str;
- default = "All -SSLv2 -SSLv3 -TLSv1";
+ default = "All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1";
example = "All -SSLv2 -SSLv3";
description = "Allowed SSL/TLS protocol versions.";
};
- }
-
- # Include the options shared between the main server and virtual hosts.
- // (import <nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix> {
- inherit lib;
- forMainServer = true;
- });
+ };
};
+ # implementation
- ###### implementation
-
- config = mkIf config.services.httpd."${httpdName}".enable {
+ config = mkIf cfg.enable {
- assertions = [ { assertion = mainCfg.enableSSL == true
- -> mainCfg.sslServerCert != null
- && mainCfg.sslServerKey != null;
- message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; }
- ];
+ assertions = [
+ {
+ assertion = all (hostOpts: !hostOpts.enableSSL) vhosts;
+ message = ''
+ The option `services.httpd.virtualHosts.<name>.enableSSL` no longer has any effect; please remove it.
+ Select one of `services.httpd.virtualHosts.<name>.addSSL`, `services.httpd.virtualHosts.<name>.forceSSL`,
+ or `services.httpd.virtualHosts.<name>.onlySSL`.
+ '';
+ }
+ {
+ assertion = all (hostOpts: with hostOpts; !(addSSL && onlySSL) && !(forceSSL && onlySSL) && !(addSSL && forceSSL)) vhosts;
+ message = ''
+ Options `services.httpd.virtualHosts.<name>.addSSL`,
+ `services.httpd.virtualHosts.<name>.onlySSL` and `services.httpd.virtualHosts.<name>.forceSSL`
+ are mutually exclusive.
+ '';
+ }
+ {
+ assertion = all (hostOpts: !(hostOpts.enableACME && hostOpts.useACMEHost != null)) vhosts;
+ message = ''
+ Options `services.httpd.virtualHosts.<name>.enableACME` and
+ `services.httpd.virtualHosts.<name>.useACMEHost` are mutually exclusive.
+ '';
+ }
+ ];
- warnings = map (cfg: "apache-httpd's extraSubservices option is deprecated. Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.") (lib.filter (cfg: cfg.extraSubservices != []) allHosts);
+ warnings =
+ mapAttrsToList (name: hostOpts: ''
+ Using config.services.httpd.virtualHosts."${name}".servedFiles is deprecated and will become unsupported in a future release. Your configuration will continue to work as is but please migrate your configuration to config.services.httpd.virtualHosts."${name}".locations before the 20.09 release of NixOS.
+ '') (filterAttrs (name: hostOpts: hostOpts.servedFiles != []) cfg.virtualHosts);
- users.users = optionalAttrs (withUsers && mainCfg.user == "wwwrun") (singleton
- { name = "wwwrun";
- group = mainCfg.group;
+ users.users = optionalAttrs (withUsers && cfg.user == "wwwrun") {
+ wwwrun = {
+ group = cfg.group;
description = "Apache httpd user";
uid = config.ids.uids.wwwrun;
- });
+ };
+ };
+
+ users.groups = optionalAttrs (withUsers && cfg.group == "wwwrun") {
+ wwwrun.gid = config.ids.gids.wwwrun;
+ };
+
+ security.acme.certs = mapAttrs (name: hostOpts: {
+ user = cfg.user;
+ group = mkDefault cfg.group;
+ email = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr;
+ webroot = hostOpts.acmeRoot;
+ extraDomains = genAttrs hostOpts.serverAliases (alias: null);
+ postRun = "systemctl reload httpd.service";
+ }) (filterAttrs (name: hostOpts: hostOpts.enableACME) cfg.virtualHosts);
- users.groups = optionalAttrs (withUsers && mainCfg.group == "wwwrun") (singleton
- { name = "wwwrun";
- gid = config.ids.gids.wwwrun;
- });
+ environment.systemPackages = [ pkg ];
- environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices;
+ # required for "apachectl configtest"
+ environment.etc."httpd/httpd_${httpdName}.conf".source = httpdConf;
- services.httpd."${httpdName}".phpOptions =
+ services.httpd."${httpdName}" = { phpOptions =
''
; Needed for PHP's mail() function.
sendmail_path = sendmail -t -i
date.timezone = "${config.time.timeZone}"
'';
+ extraModules = mkBefore [
+ # HTTP authentication mechanisms: basic and digest.
+ "auth_basic" "auth_digest"
+
+ # Authentication: is the user who he claims to be?
+ "authn_file" "authn_dbm" "authn_anon"
+
+ # Authorization: is the user allowed access?
+ "authz_user" "authz_groupfile" "authz_host"
+
+ # Other modules.
+ "ext_filter" "include" "env" "mime_magic"
+ "cern_meta" "expires" "headers" "usertrack" "setenvif"
+ "dav" "status" "asis" "info" "dav_fs"
+ "vhost_alias" "imagemap" "actions" "speling"
+ "proxy" "proxy_http"
+ "cache" "cache_disk"
+
+ # For compatibility with old configurations, the new module mod_access_compat is provided.
+ "access_compat"
+ ];
+ };
+
+ systemd.tmpfiles.rules =
+ let
+ svc = config.systemd.services."httpd${httpdName}".serviceConfig;
+ in
+ [
+ "d '${cfg.logDir}' 0700 ${svc.User} ${svc.Group}"
+ "Z '${cfg.logDir}' - ${svc.User} ${svc.Group}"
+ ];
+
systemd.services."httpd${httpdName}" =
+ let
+ vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts;
+ in
{ description = "Apache HTTPD";
wantedBy = [ "multi-user.target" ];
- wants = [ "keys.target" ];
- after = [ "network.target" "fs.target" "postgresql.service" "keys.target" ];
+ wants = concatLists (map (hostOpts: [ "acme-${hostOpts.hostName}.service" "acme-selfsigned-${hostOpts.hostName}.service" ]) vhostsACME);
+ after = [ "network.target" "fs.target" ] ++ map (hostOpts: "acme-selfsigned-${hostOpts.hostName}.service") vhostsACME;
path =
- [ httpd pkgs.coreutils pkgs.gnugrep ]
- ++ optional enablePHP pkgs.system-sendmail # Needed for PHP's mail() function.
- ++ concatMap (svc: svc.extraServerPath) allSubservices;
+ [ pkg pkgs.coreutils pkgs.gnugrep ]
+ ++ optional cfg.enablePHP pkgs.system-sendmail; # Needed for PHP's mail() function.
environment =
- optionalAttrs enablePHP { PHPRC = phpIni; }
- // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; }
- // (listToAttrs (concatMap (svc: svc.globalEnvVars) allSubservices));
+ optionalAttrs cfg.enablePHP { PHPRC = phpIni; }
+ // optionalAttrs cfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; };
preStart =
''
- mkdir -m 0750 -p ${mainCfg.stateDir}
- [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir}
-
- mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
- [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
-
- mkdir -m 0700 -p ${mainCfg.logDir}
-
# Get rid of old semaphores. These tend to accumulate across
# server restarts, eventually preventing it from restarting
# successfully.
- for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${mainCfg.user} ' | cut -f2 -d ' '); do
+ for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${cfg.user} ' | cut -f2 -d ' '); do
${pkgs.utillinux}/bin/ipcrm -s $i
done
-
- # Run the startup hooks for the subservices.
- for i in ${toString (map (svn: svn.startupScript) allSubservices)}; do
- echo Running Apache startup hook $i...
- $i
- done
'';
- serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}";
- serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop";
- serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful";
- serviceConfig.Type = "forking";
- serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid";
- serviceConfig.Restart = "always";
- serviceConfig.RestartSec = "5s";
+ serviceConfig = {
+ ExecStart = "@${pkg}/bin/httpd httpd -f ${httpdConf}";
+ ExecStop = "${pkg}/bin/httpd -f ${httpdConf} -k graceful-stop";
+ ExecReload = "${pkg}/bin/httpd -f ${httpdConf} -k graceful";
+ User = "root";
+ Group = cfg.group;
+ Type = "forking";
+ PIDFile = "${runtimeDir}/httpd.pid";
+ Restart = "always";
+ RestartSec = "5s";
+ RuntimeDirectory = "httpd_${httpdName} httpd_${httpdName}/runtime";
+ RuntimeDirectoryMode = "0750";
+ };
};
};