Merge pull request #78802 from aanderse/httpd-cleanup

nixos/httpd: module cleanup
This commit is contained in:
Aaron Andersen 2020-01-31 21:09:25 -05:00 committed by GitHub
commit be1c62932f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 110 additions and 108 deletions

View File

@ -154,7 +154,7 @@ in
}; };
virtualHost = mkOption { virtualHost = mkOption {
type = types.submodule (import ../web-servers/apache-httpd/per-server-options.nix); type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
example = literalExample '' example = literalExample ''
{ hostName = "example.org"; { hostName = "example.org";
adminAddr = "webmaster@example.org"; adminAddr = "webmaster@example.org";

View File

@ -100,7 +100,7 @@ in
}; };
virtualHost = mkOption { virtualHost = mkOption {
type = types.submodule (import ../web-servers/apache-httpd/per-server-options.nix); type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
example = literalExample '' example = literalExample ''
{ {
hostName = "survey.example.org"; hostName = "survey.example.org";

View File

@ -290,7 +290,7 @@ in
}; };
virtualHost = mkOption { virtualHost = mkOption {
type = types.submodule (import ../web-servers/apache-httpd/per-server-options.nix); type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
example = literalExample '' example = literalExample ''
{ {
hostName = "mediawiki.example.org"; hostName = "mediawiki.example.org";

View File

@ -140,7 +140,7 @@ in
}; };
virtualHost = mkOption { virtualHost = mkOption {
type = types.submodule (import ../web-servers/apache-httpd/per-server-options.nix); type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
example = literalExample '' example = literalExample ''
{ {
hostName = "moodle.example.org"; hostName = "moodle.example.org";

View File

@ -209,7 +209,7 @@ let
}; };
virtualHost = mkOption { virtualHost = mkOption {
type = types.submodule (import ../web-servers/apache-httpd/per-server-options.nix); type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
example = literalExample '' example = literalExample ''
{ {
adminAddr = "webmaster@example.org"; adminAddr = "webmaster@example.org";

View File

@ -113,7 +113,7 @@ in
}; };
virtualHost = mkOption { virtualHost = mkOption {
type = types.submodule (import ../web-servers/apache-httpd/per-server-options.nix); type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
example = literalExample '' example = literalExample ''
{ {
hostName = "zabbix.example.org"; hostName = "zabbix.example.org";

View File

@ -4,21 +4,21 @@ with lib;
let let
mainCfg = config.services.httpd; cfg = config.services.httpd;
runtimeDir = "/run/httpd"; runtimeDir = "/run/httpd";
httpd = mainCfg.package.out; pkg = cfg.package.out;
httpdConf = mainCfg.configFile; httpdConf = cfg.configFile;
php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ }; php = cfg.phpPackage.override { apacheHttpd = pkg.dev; /* otherwise it only gets .out */ };
phpMajorVersion = lib.versions.major (lib.getVersion php); phpMajorVersion = lib.versions.major (lib.getVersion php);
mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; }; mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; };
vhosts = attrValues mainCfg.virtualHosts; vhosts = attrValues cfg.virtualHosts;
mkListenInfo = hostOpts: mkListenInfo = hostOpts:
if hostOpts.listen != [] then hostOpts.listen if hostOpts.listen != [] then hostOpts.listen
@ -41,23 +41,18 @@ let
"mime" "autoindex" "negotiation" "dir" "mime" "autoindex" "negotiation" "dir"
"alias" "rewrite" "alias" "rewrite"
"unixd" "slotmem_shm" "socache_shmcb" "unixd" "slotmem_shm" "socache_shmcb"
"mpm_${mainCfg.multiProcessingModule}" "mpm_${cfg.multiProcessingModule}"
] ]
++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ]) ++ (if cfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
++ optional enableSSL "ssl" ++ optional enableSSL "ssl"
++ optional enableUserDir "userdir" ++ optional enableUserDir "userdir"
++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; } ++ optional cfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
++ optional mainCfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; } ++ optional cfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
++ optional mainCfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; } ++ optional cfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
++ mainCfg.extraModules; ++ cfg.extraModules;
loggingConf = (if cfg.logFormat != "none" then ''
allDenied = "Require all denied"; ErrorLog ${cfg.logDir}/error.log
allGranted = "Require all granted";
loggingConf = (if mainCfg.logFormat != "none" then ''
ErrorLog ${mainCfg.logDir}/error.log
LogLevel notice LogLevel notice
@ -66,7 +61,7 @@ let
LogFormat "%{Referer}i -> %U" referer LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent LogFormat "%{User-agent}i" agent
CustomLog ${mainCfg.logDir}/access.log ${mainCfg.logFormat} CustomLog ${cfg.logDir}/access.log ${cfg.logFormat}
'' else '' '' else ''
ErrorLog /dev/null ErrorLog /dev/null
''); '');
@ -88,6 +83,7 @@ let
sslConf = '' sslConf = ''
<IfModule mod_ssl.c>
SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000) SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000)
Mutex posixsem Mutex posixsem
@ -95,27 +91,28 @@ let
SSLRandomSeed startup builtin SSLRandomSeed startup builtin
SSLRandomSeed connect builtin SSLRandomSeed connect builtin
SSLProtocol ${mainCfg.sslProtocols} SSLProtocol ${cfg.sslProtocols}
SSLCipherSuite ${mainCfg.sslCiphers} SSLCipherSuite ${cfg.sslCiphers}
SSLHonorCipherOrder on SSLHonorCipherOrder on
</IfModule>
''; '';
mimeConf = '' mimeConf = ''
TypesConfig ${httpd}/conf/mime.types TypesConfig ${pkg}/conf/mime.types
AddType application/x-x509-ca-cert .crt AddType application/x-x509-ca-cert .crt
AddType application/x-pkcs7-crl .crl AddType application/x-pkcs7-crl .crl
AddType application/x-httpd-php .php .phtml AddType application/x-httpd-php .php .phtml
<IfModule mod_mime_magic.c> <IfModule mod_mime_magic.c>
MIMEMagicFile ${httpd}/conf/magic MIMEMagicFile ${pkg}/conf/magic
</IfModule> </IfModule>
''; '';
mkVHostConf = hostOpts: mkVHostConf = hostOpts:
let let
adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else mainCfg.adminAddr; adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr;
listen = filter (listen: !listen.ssl) (mkListenInfo hostOpts); listen = filter (listen: !listen.ssl) (mkListenInfo hostOpts);
listenSSL = filter (listen: listen.ssl) (mkListenInfo hostOpts); listenSSL = filter (listen: listen.ssl) (mkListenInfo hostOpts);
@ -203,9 +200,9 @@ let
'') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations))); '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
in in
'' ''
${optionalString mainCfg.logPerVirtualHost '' ${optionalString cfg.logPerVirtualHost ''
ErrorLog ${mainCfg.logDir}/error-${hostOpts.hostName}.log ErrorLog ${cfg.logDir}/error-${hostOpts.hostName}.log
CustomLog ${mainCfg.logDir}/access-${hostOpts.hostName}.log ${hostOpts.logFormat} CustomLog ${cfg.logDir}/access-${hostOpts.hostName}.log ${hostOpts.logFormat}
''} ''}
${optionalString (hostOpts.robotsEntries != "") '' ${optionalString (hostOpts.robotsEntries != "") ''
@ -217,7 +214,7 @@ let
<Directory "${documentRoot}"> <Directory "${documentRoot}">
Options Indexes FollowSymLinks Options Indexes FollowSymLinks
AllowOverride None AllowOverride None
${allGranted} Require all granted
</Directory> </Directory>
${optionalString hostOpts.enableUserDir '' ${optionalString hostOpts.enableUserDir ''
@ -244,7 +241,7 @@ let
Alias ${elem.urlPath} ${elem.dir}/ Alias ${elem.urlPath} ${elem.dir}/
<Directory ${elem.dir}> <Directory ${elem.dir}>
Options +Indexes Options +Indexes
${allGranted} Require all granted
AllowOverride All AllowOverride All
</Directory> </Directory>
''; '';
@ -259,20 +256,20 @@ let
confFile = pkgs.writeText "httpd.conf" '' confFile = pkgs.writeText "httpd.conf" ''
ServerRoot ${httpd} ServerRoot ${pkg}
ServerName ${config.networking.hostName} ServerName ${config.networking.hostName}
DefaultRuntimeDir ${runtimeDir}/runtime DefaultRuntimeDir ${runtimeDir}/runtime
PidFile ${runtimeDir}/httpd.pid PidFile ${runtimeDir}/httpd.pid
${optionalString (mainCfg.multiProcessingModule != "prefork") '' ${optionalString (cfg.multiProcessingModule != "prefork") ''
# mod_cgid requires this. # mod_cgid requires this.
ScriptSock ${runtimeDir}/cgisock ScriptSock ${runtimeDir}/cgisock
''} ''}
<IfModule prefork.c> <IfModule prefork.c>
MaxClients ${toString mainCfg.maxClients} MaxClients ${toString cfg.maxClients}
MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild} MaxRequestsPerChild ${toString cfg.maxRequestsPerChild}
</IfModule> </IfModule>
${let ${let
@ -281,12 +278,12 @@ let
in concatStringsSep "\n" uniqueListen in concatStringsSep "\n" uniqueListen
} }
User ${mainCfg.user} User ${cfg.user}
Group ${mainCfg.group} Group ${cfg.group}
${let ${let
mkModule = module: mkModule = module:
if isString module then { name = module; path = "${httpd}/modules/mod_${module}.so"; } if isString module then { name = module; path = "${pkg}/modules/mod_${module}.so"; }
else if isAttrs module then { inherit (module) name path; } else if isAttrs module then { inherit (module) name path; }
else throw "Expecting either a string or attribute set including a name and path."; else throw "Expecting either a string or attribute set including a name and path.";
in in
@ -296,37 +293,37 @@ let
AddHandler type-map var AddHandler type-map var
<Files ~ "^\.ht"> <Files ~ "^\.ht">
${allDenied} Require all denied
</Files> </Files>
${mimeConf} ${mimeConf}
${loggingConf} ${loggingConf}
${browserHacks} ${browserHacks}
Include ${httpd}/conf/extra/httpd-default.conf Include ${pkg}/conf/extra/httpd-default.conf
Include ${httpd}/conf/extra/httpd-autoindex.conf Include ${pkg}/conf/extra/httpd-autoindex.conf
Include ${httpd}/conf/extra/httpd-multilang-errordoc.conf Include ${pkg}/conf/extra/httpd-multilang-errordoc.conf
Include ${httpd}/conf/extra/httpd-languages.conf Include ${pkg}/conf/extra/httpd-languages.conf
TraceEnable off TraceEnable off
${if enableSSL then sslConf else ""} ${sslConf}
# Fascist default - deny access to everything. # Fascist default - deny access to everything.
<Directory /> <Directory />
Options FollowSymLinks Options FollowSymLinks
AllowOverride None AllowOverride None
${allDenied} Require all denied
</Directory> </Directory>
# But do allow access to files in the store so that we don't have # But do allow access to files in the store so that we don't have
# to generate <Directory> clauses for every generated file that we # to generate <Directory> clauses for every generated file that we
# want to serve. # want to serve.
<Directory /nix/store> <Directory /nix/store>
${allGranted} Require all granted
</Directory> </Directory>
${mainCfg.extraConfig} ${cfg.extraConfig}
${concatMapStringsSep "\n" mkVHostConf vhosts} ${concatMapStringsSep "\n" mkVHostConf vhosts}
''; '';
@ -334,7 +331,7 @@ let
# Generate the PHP configuration file. Should probably be factored # Generate the PHP configuration file. Should probably be factored
# out into a separate module. # out into a separate module.
phpIni = pkgs.runCommand "php.ini" phpIni = pkgs.runCommand "php.ini"
{ options = mainCfg.phpOptions; { options = cfg.phpOptions;
preferLocalBuild = true; preferLocalBuild = true;
} }
'' ''
@ -367,17 +364,13 @@ in
(mkRemovedOptionModule [ "services" "httpd" "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.") (mkRemovedOptionModule [ "services" "httpd" "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
]; ];
###### interface # interface
options = { options = {
services.httpd = { services.httpd = {
enable = mkOption { enable = mkEnableOption "the Apache HTTP Server";
type = types.bool;
default = false;
description = "Whether to enable the Apache HTTP Server.";
};
package = mkOption { package = mkOption {
type = types.package; type = types.package;
@ -404,7 +397,7 @@ in
default = ""; default = "";
description = '' description = ''
Configuration lines appended to the generated Apache Configuration lines appended to the generated Apache
configuration file. Note that this mechanism may not work configuration file. Note that this mechanism will not work
when <option>configFile</option> is overridden. when <option>configFile</option> is overridden.
''; '';
}; };
@ -458,8 +451,7 @@ in
type = types.str; type = types.str;
default = "wwwrun"; default = "wwwrun";
description = '' description = ''
User account under which httpd runs. The account is created User account under which httpd runs.
automatically if it doesn't exist.
''; '';
}; };
@ -467,8 +459,7 @@ in
type = types.str; type = types.str;
default = "wwwrun"; default = "wwwrun";
description = '' description = ''
Group under which httpd runs. The account is created Group under which httpd runs.
automatically if it doesn't exist.
''; '';
}; };
@ -481,10 +472,10 @@ in
}; };
virtualHosts = mkOption { virtualHosts = mkOption {
type = with types; attrsOf (submodule (import ./per-server-options.nix)); type = with types; attrsOf (submodule (import ./vhost-options.nix));
default = { default = {
localhost = { localhost = {
documentRoot = "${httpd}/htdocs"; documentRoot = "${pkg}/htdocs";
}; };
}; };
example = literalExample '' example = literalExample ''
@ -540,12 +531,13 @@ in
'' ''
date.timezone = "CET" date.timezone = "CET"
''; '';
description = description = ''
"Options appended to the PHP configuration file <filename>php.ini</filename>."; Options appended to the PHP configuration file <filename>php.ini</filename>.
'';
}; };
multiProcessingModule = mkOption { multiProcessingModule = mkOption {
type = types.str; type = types.enum [ "event" "prefork" "worker" ];
default = "prefork"; default = "prefork";
example = "worker"; example = "worker";
description = description =
@ -572,8 +564,9 @@ in
type = types.int; type = types.int;
default = 0; default = 0;
example = 500; example = 500;
description = description = ''
"Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited"; Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited.
'';
}; };
sslCiphers = mkOption { sslCiphers = mkOption {
@ -592,10 +585,9 @@ in
}; };
# implementation
###### implementation config = mkIf cfg.enable {
config = mkIf config.services.httpd.enable {
assertions = [ assertions = [
{ {
@ -626,30 +618,30 @@ in
warnings = warnings =
mapAttrsToList (name: hostOpts: '' 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. 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 != []) mainCfg.virtualHosts); '') (filterAttrs (name: hostOpts: hostOpts.servedFiles != []) cfg.virtualHosts);
users.users = optionalAttrs (mainCfg.user == "wwwrun") { users.users = optionalAttrs (cfg.user == "wwwrun") {
wwwrun = { wwwrun = {
group = mainCfg.group; group = cfg.group;
description = "Apache httpd user"; description = "Apache httpd user";
uid = config.ids.uids.wwwrun; uid = config.ids.uids.wwwrun;
}; };
}; };
users.groups = optionalAttrs (mainCfg.group == "wwwrun") { users.groups = optionalAttrs (cfg.group == "wwwrun") {
wwwrun.gid = config.ids.gids.wwwrun; wwwrun.gid = config.ids.gids.wwwrun;
}; };
security.acme.certs = mapAttrs (name: hostOpts: { security.acme.certs = mapAttrs (name: hostOpts: {
user = mainCfg.user; user = cfg.user;
group = mkDefault mainCfg.group; group = mkDefault cfg.group;
email = if hostOpts.adminAddr != null then hostOpts.adminAddr else mainCfg.adminAddr; email = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr;
webroot = hostOpts.acmeRoot; webroot = hostOpts.acmeRoot;
extraDomains = genAttrs hostOpts.serverAliases (alias: null); extraDomains = genAttrs hostOpts.serverAliases (alias: null);
postRun = "systemctl reload httpd.service"; postRun = "systemctl reload httpd.service";
}) (filterAttrs (name: hostOpts: hostOpts.enableACME) mainCfg.virtualHosts); }) (filterAttrs (name: hostOpts: hostOpts.enableACME) cfg.virtualHosts);
environment.systemPackages = [httpd]; environment.systemPackages = [ pkg ];
# required for "apachectl configtest" # required for "apachectl configtest"
environment.etc."httpd/httpd.conf".source = httpdConf; environment.etc."httpd/httpd.conf".source = httpdConf;
@ -689,6 +681,15 @@ in
"access_compat" "access_compat"
]; ];
systemd.tmpfiles.rules =
let
svc = config.systemd.services.httpd.serviceConfig;
in
[
"d '${cfg.logDir}' 0700 ${svc.User} ${svc.Group}"
"Z '${cfg.logDir}' - ${svc.User} ${svc.Group}"
];
systemd.services.httpd = systemd.services.httpd =
let let
vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts; vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts;
@ -700,35 +701,36 @@ in
after = [ "network.target" "fs.target" ] ++ map (hostOpts: "acme-selfsigned-${hostOpts.hostName}.service") vhostsACME; after = [ "network.target" "fs.target" ] ++ map (hostOpts: "acme-selfsigned-${hostOpts.hostName}.service") vhostsACME;
path = path =
[ httpd pkgs.coreutils pkgs.gnugrep ] [ pkg pkgs.coreutils pkgs.gnugrep ]
++ optional mainCfg.enablePHP pkgs.system-sendmail; # Needed for PHP's mail() function. ++ optional cfg.enablePHP pkgs.system-sendmail; # Needed for PHP's mail() function.
environment = environment =
optionalAttrs mainCfg.enablePHP { PHPRC = phpIni; } optionalAttrs cfg.enablePHP { PHPRC = phpIni; }
// optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; }; // optionalAttrs cfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; };
preStart = preStart =
'' ''
mkdir -m 0700 -p ${mainCfg.logDir}
# Get rid of old semaphores. These tend to accumulate across # Get rid of old semaphores. These tend to accumulate across
# server restarts, eventually preventing it from restarting # server restarts, eventually preventing it from restarting
# successfully. # 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 ${pkgs.utillinux}/bin/ipcrm -s $i
done done
''; '';
serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}"; serviceConfig = {
serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop"; ExecStart = "@${pkg}/bin/httpd httpd -f ${httpdConf}";
serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful"; ExecStop = "${pkg}/bin/httpd -f ${httpdConf} -k graceful-stop";
serviceConfig.Group = mainCfg.group; ExecReload = "${pkg}/bin/httpd -f ${httpdConf} -k graceful";
serviceConfig.Type = "forking"; User = "root";
serviceConfig.PIDFile = "${runtimeDir}/httpd.pid"; Group = cfg.group;
serviceConfig.Restart = "always"; Type = "forking";
serviceConfig.RestartSec = "5s"; PIDFile = "${runtimeDir}/httpd.pid";
serviceConfig.RuntimeDirectory = "httpd httpd/runtime"; Restart = "always";
serviceConfig.RuntimeDirectoryMode = "0750"; RestartSec = "5s";
RuntimeDirectory = "httpd httpd/runtime";
RuntimeDirectoryMode = "0750";
};
}; };
}; };