X-Git-Url: https://git.immae.eu/?p=perso%2FImmae%2FConfig%2FNix.git;a=blobdiff_plain;f=modules%2Fwebsites%2Fhttpd-service-builder.nix;fp=modules%2Fwebsites%2Fhttpd-service-builder.nix;h=0000000000000000000000000000000000000000;hp=1f7488dc63de57afcadc5ebc02aecbb8ee402882;hb=1a64deeb894dc95e2645a75771732c6cc53a79ad;hpb=fa25ffd4583cc362075cd5e1b4130f33306103f0 diff --git a/modules/websites/httpd-service-builder.nix b/modules/websites/httpd-service-builder.nix deleted file mode 100644 index 1f7488d..0000000 --- a/modules/websites/httpd-service-builder.nix +++ /dev/null @@ -1,735 +0,0 @@ -# to help backporting this builder should stay as close as possible to -# nixos/modules/services/web-servers/apache-httpd/default.nix -{ httpdName, withUsers ? true }: -{ config, lib, pkgs, ... }: - -with lib; - -let - - cfg = config.services.httpd."${httpdName}"; - - runtimeDir = "/run/httpd_${httpdName}"; - - pkg = cfg.package.out; - - httpdConf = cfg.configFile; - - php = cfg.phpPackage.override { apacheHttpd = pkg.dev; /* otherwise it only gets .out */ }; - - phpMajorVersion = lib.versions.major (lib.getVersion php); - - mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; }; - - vhosts = attrValues cfg.virtualHosts; - - 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; } - ); - - listenInfo = unique (concatMap mkListenInfo vhosts); - - enableHttp2 = any (vhost: vhost.http2) vhosts; - enableSSL = any (listen: listen.ssl) listenInfo; - enableUserDir = any (vhost: vhost.enableUserDir) vhosts; - - # 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 cfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ]) - ++ optional enableHttp2 "http2" - ++ optional enableSSL "ssl" - ++ 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 cfg.logFormat != "none" then '' - ErrorLog ${cfg.logDir}/error.log - - LogLevel notice - - LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined - LogFormat "%h %l %u %t \"%r\" %>s %b" common - LogFormat "%{Referer}i -> %U" referer - LogFormat "%{User-agent}i" agent - - 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 - - ''; - - - sslConf = '' - - SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000) - - Mutex posixsem - - SSLRandomSeed startup builtin - SSLRandomSeed connect builtin - - SSLProtocol ${cfg.sslProtocols} - SSLCipherSuite ${cfg.sslCiphers} - SSLHonorCipherOrder on - - ''; - - - mimeConf = '' - 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 - - - MIMEMagicFile ${pkg}/conf/magic - - ''; - - 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/" - - AllowOverride None - Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec - Require method GET POST OPTIONS - Require all granted - - ''; - in - optionalString (listen != []) '' - - ServerName ${hostOpts.hostName} - ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases} - ServerAdmin ${adminAddr} - - SSLEngine off - - ${acmeChallenge} - ${if hostOpts.forceSSL then '' - - RewriteEngine on - RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge [NC] - RewriteCond %{HTTPS} off - RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} - - '' else mkVHostCommonConf hostOpts} - - '' + - optionalString (listenSSL != []) '' - - ServerName ${hostOpts.hostName} - ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases} - ServerAdmin ${adminAddr} - SSLEngine on - SSLCertificateFile ${sslServerCert} - SSLCertificateKeyFile ${sslServerKey} - ${optionalString hostOpts.http2 "Protocols h2 h2c http/1.1"} - ${acmeChallenge} - ${mkVHostCommonConf hostOpts} - - '' - ; - - 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: '' - - ${optionalString (config.proxyPass != null) '' - - ProxyPass ${config.proxyPass} - ProxyPassReverse ${config.proxyPass} - - ''} - ${optionalString (config.index != null) '' - - DirectoryIndex ${config.index} - - ''} - ${optionalString (config.alias != null) '' - - Alias "${config.alias}" - - ''} - ${config.extraConfig} - - '') (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}" - - - Options Indexes FollowSymLinks - AllowOverride None - Require all granted - - - ${optionalString hostOpts.enableUserDir '' - UserDir public_html - UserDir disabled root - - AllowOverride FileInfo AuthConfig Limit Indexes - Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec - - Require all granted - - - Require all denied - - - ''} - - ${optionalString (hostOpts.globalRedirect != null && hostOpts.globalRedirect != "") '' - RedirectPermanent / ${hostOpts.globalRedirect} - ''} - - ${ - let makeDirConf = elem: '' - Alias ${elem.urlPath} ${elem.dir}/ - - Options +Indexes - Require all granted - AllowOverride All - - ''; - in concatMapStrings makeDirConf hostOpts.servedDirs - } - - ${mkLocations hostOpts.locations} - ${hostOpts.extraConfig} - '' - ; - - - confFile = pkgs.writeText "httpd.conf" '' - - ServerRoot ${pkg} - ServerName ${config.networking.hostName} - DefaultRuntimeDir ${runtimeDir}/runtime - - PidFile ${runtimeDir}/httpd.pid - - ${optionalString (cfg.multiProcessingModule != "prefork") '' - # mod_cgid requires this. - ScriptSock ${runtimeDir}/cgisock - ''} - - - MaxClients ${toString cfg.maxClients} - MaxRequestsPerChild ${toString cfg.maxRequestsPerChild} - - - ${let - 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 ${cfg.user} - Group ${cfg.group} - - ${let - 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 - - - Require all denied - - - ${mimeConf} - ${loggingConf} - ${browserHacks} - - 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 - - ${sslConf} - - # Fascist default - deny access to everything. - - Options FollowSymLinks - AllowOverride None - Require all denied - - - ${cfg.extraConfig} - - ${concatMapStringsSep "\n" mkVHostConf vhosts} - ''; - - # Generate the PHP configuration file. Should probably be factored - # out into a separate module. - phpIni = pkgs.runCommand "php.ini" - { options = cfg.phpOptions; - preferLocalBuild = true; - } - '' - cat ${php}/etc/php.ini > $out - echo "$options" >> $out - ''; - -in - - -{ - - 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 = mkEnableOption "the Apache HTTP Server"; - - package = mkOption { - type = types.package; - default = pkgs.apacheHttpd; - defaultText = "pkgs.apacheHttpd"; - description = '' - Overridable attribute of the Apache HTTP Server package to use. - ''; - }; - - configFile = mkOption { - type = types.path; - default = confFile; - defaultText = "confFile"; - example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ..."''; - description = '' - Override the configuration file used by Apache. By default, - NixOS generates one automatically. - ''; - }; - - extraConfig = mkOption { - type = types.lines; - default = ""; - description = '' - Configuration lines appended to the generated Apache - configuration file. Note that this mechanism will not work - when is overridden. - ''; - }; - - extraModules = mkOption { - type = types.listOf types.unspecified; - default = []; - example = literalExample '' - [ - "proxy_connect" - { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; } - ] - ''; - description = '' - 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 - name and path of the - module. - ''; - }; - - 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 for more details. - ''; - }; - - logPerVirtualHost = mkOption { - type = types.bool; - default = true; - description = '' - If enabled, each virtual host gets its own - access.log and - error.log, namely suffixed by the - of the virtual host. - ''; - }; - - user = mkOption { - type = types.str; - default = "wwwrun"; - description = '' - User account under which httpd runs. - ''; - }; - - group = mkOption { - type = types.str; - default = "wwwrun"; - description = '' - Group under which httpd runs. - ''; - }; - - logDir = mkOption { - type = types.path; - default = "/var/log/httpd"; - description = '' - Directory for Apache's log files. It is created automatically. - ''; - }; - - virtualHosts = mkOption { - type = with types; attrsOf (submodule (import )); - 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"; - }; - } - ''; - description = '' - Specification of the virtual hosts served by Apache. Each - element should be an attribute set specifying the - configuration of the virtual host. - ''; - }; - - enableMellon = mkOption { - type = types.bool; - default = false; - description = "Whether to enable the mod_auth_mellon module."; - }; - - enablePHP = mkOption { - type = types.bool; - default = false; - description = "Whether to enable the PHP module."; - }; - - phpPackage = mkOption { - type = types.package; - default = pkgs.php; - defaultText = "pkgs.php"; - description = '' - Overridable attribute of the PHP package to use. - ''; - }; - - enablePerl = mkOption { - type = types.bool; - default = false; - description = "Whether to enable the Perl module (mod_perl)."; - }; - - phpOptions = mkOption { - type = types.lines; - default = ""; - example = - '' - date.timezone = "CET" - ''; - description = '' - Options appended to the PHP configuration file php.ini. - ''; - }; - - multiProcessingModule = mkOption { - type = types.enum [ "event" "prefork" "worker" ]; - default = "prefork"; - example = "worker"; - description = - '' - Multi-processing module to be used by Apache. Available - modules are prefork (the default; - handles each request in a separate child process), - worker (hybrid approach that starts a - number of child processes each running a number of - threads) and event (a recent variant of - worker that handles persistent - connections more efficiently). - ''; - }; - - maxClients = mkOption { - type = types.int; - default = 150; - example = 8; - description = "Maximum number of httpd processes (prefork)"; - }; - - maxRequestsPerChild = mkOption { - type = types.int; - default = 0; - example = 500; - description = '' - Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited. - ''; - }; - - sslCiphers = mkOption { - type = types.str; - default = "HIGH:!aNULL:!MD5:!EXP"; - description = "Cipher Suite available for negotiation in SSL proxy handshake."; - }; - - sslProtocols = mkOption { - type = types.str; - default = "All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1"; - example = "All -SSLv2 -SSLv3"; - description = "Allowed SSL/TLS protocol versions."; - }; - }; - - }; - - # implementation - - config = mkIf cfg.enable { - - assertions = [ - { - assertion = all (hostOpts: !hostOpts.enableSSL) vhosts; - message = '' - The option `services.httpd.virtualHosts..enableSSL` no longer has any effect; please remove it. - Select one of `services.httpd.virtualHosts..addSSL`, `services.httpd.virtualHosts..forceSSL`, - or `services.httpd.virtualHosts..onlySSL`. - ''; - } - { - assertion = all (hostOpts: with hostOpts; !(addSSL && onlySSL) && !(forceSSL && onlySSL) && !(addSSL && forceSSL)) vhosts; - message = '' - Options `services.httpd.virtualHosts..addSSL`, - `services.httpd.virtualHosts..onlySSL` and `services.httpd.virtualHosts..forceSSL` - are mutually exclusive. - ''; - } - { - assertion = all (hostOpts: !(hostOpts.enableACME && hostOpts.useACMEHost != null)) vhosts; - message = '' - Options `services.httpd.virtualHosts..enableACME` and - `services.httpd.virtualHosts..useACMEHost` are mutually exclusive. - ''; - } - ]; - - 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 && 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); - - environment.systemPackages = [ pkg ]; - - # required for "apachectl configtest" - environment.etc."httpd/httpd_${httpdName}.conf".source = httpdConf; - - services.httpd."${httpdName}" = { phpOptions = - '' - ; Needed for PHP's mail() function. - sendmail_path = sendmail -t -i - - ; Don't advertise PHP - expose_php = off - '' + optionalString (config.time.timeZone != null) '' - - ; Apparently PHP doesn't use $TZ. - 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 = 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 = - [ pkg pkgs.coreutils pkgs.gnugrep ] - ++ optional cfg.enablePHP pkgs.system-sendmail; # Needed for PHP's mail() function. - - environment = - optionalAttrs cfg.enablePHP { PHPRC = phpIni; } - // optionalAttrs cfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; }; - - preStart = - '' - # 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 ' ${cfg.user} ' | cut -f2 -d ' '); do - ${pkgs.utillinux}/bin/ipcrm -s $i - done - ''; - - 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"; - }; - }; - - }; -}