From 581c499c06bcc834e084c49f284e18611fbc139b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Isma=C3=ABl=20Bouya?= Date: Sun, 12 May 2019 23:14:58 +0200 Subject: [PATCH] Make httpd service builder --- modules/default.nix | 2 +- modules/private/default.nix | 6 + .../private/httpd-service-builder.nix | 19 +- nixops/modules/websites/apache/httpd_inte.nix | 731 ------------------ nixops/modules/websites/apache/httpd_prod.nix | 731 ------------------ nixops/modules/websites/default.nix | 8 - 6 files changed, 18 insertions(+), 1479 deletions(-) create mode 100644 modules/private/default.nix rename nixops/modules/websites/apache/httpd_tools.nix => modules/private/httpd-service-builder.nix (97%) delete mode 100644 nixops/modules/websites/apache/httpd_inte.nix delete mode 100644 nixops/modules/websites/apache/httpd_prod.nix diff --git a/modules/default.nix b/modules/default.nix index 2c993c5..6c49160 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -8,4 +8,4 @@ mastodon = ./webapps/mastodon.nix; mediagoblin = ./webapps/mediagoblin.nix; peertube = ./webapps/peertube.nix; -} +} // (if builtins.pathExists ./private then import ./private else {}) diff --git a/modules/private/default.nix b/modules/private/default.nix new file mode 100644 index 0000000..ba46374 --- /dev/null +++ b/modules/private/default.nix @@ -0,0 +1,6 @@ +{ + # adatped from nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix + httpdProd = import ./httpd-service-builder.nix { httpdName = "Prod"; withUsers = false; }; + httpdInte = import ./httpd-service-builder.nix { httpdName = "Inte"; withUsers = false; }; + httpdTools = import ./httpd-service-builder.nix { httpdName = "Tools"; withUsers = true; }; +} diff --git a/nixops/modules/websites/apache/httpd_tools.nix b/modules/private/httpd-service-builder.nix similarity index 97% rename from nixops/modules/websites/apache/httpd_tools.nix rename to modules/private/httpd-service-builder.nix index c48d0d2..0f0fe22 100644 --- a/nixops/modules/websites/apache/httpd_tools.nix +++ b/modules/private/httpd-service-builder.nix @@ -1,10 +1,13 @@ +# 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 - mainCfg = config.services.httpdTools; + mainCfg = config.services."httpd${httpdName}"; httpd = mainCfg.package.out; @@ -435,7 +438,7 @@ in options = { - services.httpdTools = { + services."httpd${httpdName}" = { enable = mkOption { type = types.bool; @@ -642,7 +645,7 @@ in } # Include the options shared between the main server and virtual hosts. - // (import ./per-server-options.nix { + // (import { inherit lib; forMainServer = true; }); @@ -652,7 +655,7 @@ in ###### implementation - config = mkIf config.services.httpdTools.enable { + config = mkIf config.services."httpd${httpdName}".enable { assertions = [ { assertion = mainCfg.enableSSL == true -> mainCfg.sslServerCert != null @@ -662,21 +665,21 @@ in warnings = map (cfg: ''apache-httpd's port option is deprecated. Use listen = [{/*ip = "*"; */ port = ${toString cfg.port};}]; instead'' ) (lib.filter (cfg: cfg.port != 0) allHosts); - users.users = optionalAttrs (mainCfg.user == "wwwrun") (singleton + users.users = optionalAttrs (withUsers && mainCfg.user == "wwwrun") (singleton { name = "wwwrun"; group = mainCfg.group; description = "Apache httpd user"; uid = config.ids.uids.wwwrun; }); - users.groups = optionalAttrs (mainCfg.group == "wwwrun") (singleton + users.groups = optionalAttrs (withUsers && mainCfg.group == "wwwrun") (singleton { name = "wwwrun"; gid = config.ids.gids.wwwrun; }); environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices; - services.httpdTools.phpOptions = + services."httpd${httpdName}".phpOptions = '' ; Needed for PHP's mail() function. sendmail_path = sendmail -t -i @@ -689,7 +692,7 @@ in date.timezone = "${config.time.timeZone}" ''; - systemd.services.httpdTools = + systemd.services."httpd${httpdName}" = { description = "Apache HTTPD"; wantedBy = [ "multi-user.target" ]; diff --git a/nixops/modules/websites/apache/httpd_inte.nix b/nixops/modules/websites/apache/httpd_inte.nix deleted file mode 100644 index 5046a28..0000000 --- a/nixops/modules/websites/apache/httpd_inte.nix +++ /dev/null @@ -1,731 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - - mainCfg = config.services.httpdInte; - - httpd = mainCfg.package.out; - - version24 = !versionOlder httpd.version "2.4"; - - httpdConf = mainCfg.configFile; - - php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ }; - - phpMajorVersion = head (splitString "." php.version); - - mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; }; - - defaultListen = cfg: if cfg.enableSSL - then [{ip = "*"; port = 443;}] - else [{ip = "*"; port = 80;}]; - - getListen = cfg: - let list = (lib.optional (cfg.port != 0) {ip = "*"; port = cfg.port;}) ++ cfg.listen; - in if list == [] - then defaultListen cfg - else list; - - listenToString = l: "${l.ip}:${toString l.port}"; - - extraModules = attrByPath ["extraModules"] [] mainCfg; - extraForeignModules = filter isAttrs extraModules; - extraApacheModules = filter isString extraModules; - - - 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" - (if version24 then "authn_core" else "authn_alias") - - # Authorization: is the user allowed access? - "authz_user" "authz_groupfile" "authz_host" - - # 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" - ] - ++ optionals version24 [ - "mpm_${mainCfg.multiProcessingModule}" - "authz_core" - "unixd" - "cache" "cache_disk" - "slotmem_shm" - "socache_shmcb" - # For compatibility with old configurations, the new module mod_access_compat is provided. - "access_compat" - ] - ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ]) - ++ optional enableSSL "ssl" - ++ extraApacheModules; - - - allDenied = if version24 then '' - Require all denied - '' else '' - Order deny,allow - Deny from all - ''; - - allGranted = if version24 then '' - Require all granted - '' else '' - Order allow,deny - Allow from all - ''; - - - loggingConf = (if mainCfg.logFormat != "none" then '' - ErrorLog ${mainCfg.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 ${mainCfg.logDir}/access.log ${mainCfg.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 ${if version24 then "shmcb" else "shm"}:${mainCfg.stateDir}/ssl_scache(512000) - - ${if version24 then "Mutex" else "SSLMutex"} posixsem - - SSLRandomSeed startup builtin - SSLRandomSeed connect builtin - - SSLProtocol ${mainCfg.sslProtocols} - SSLCipherSuite ${mainCfg.sslCiphers} - SSLHonorCipherOrder on - ''; - - - mimeConf = '' - TypesConfig ${httpd}/conf/mime.types - - AddType application/x-x509-ca-cert .crt - AddType application/x-pkcs7-crl .crl - AddType application/x-httpd-php .php .phtml - - - MIMEMagicFile ${httpd}/conf/magic - - ''; - - - 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}" - - - Options Indexes FollowSymLinks - AllowOverride None - ${allGranted} - - ''; - - 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 - - - AllowOverride FileInfo AuthConfig Limit Indexes - Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec - - ${allGranted} - - - ${allDenied} - - - - '' 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}/ - - Options +Indexes - ${allGranted} - AllowOverride All - - ''; - in concatMapStrings makeDirConf cfg.servedDirs - } - - ${concatMapStrings (svc: svc.extraConfig) subservices} - - ${cfg.extraConfig} - ''; - - - confFile = pkgs.writeText "httpd.conf" '' - - ServerRoot ${httpd} - - ${optionalString version24 '' - DefaultRuntimeDir ${mainCfg.stateDir}/runtime - ''} - - PidFile ${mainCfg.stateDir}/httpd.pid - - ${optionalString (mainCfg.multiProcessingModule != "prefork") '' - # mod_cgid requires this. - ScriptSock ${mainCfg.stateDir}/cgisock - ''} - - - MaxClients ${toString mainCfg.maxClients} - MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild} - - - ${let - listen = concatMap getListen allHosts; - toStr = listen: "Listen ${listenToString listen}\n"; - uniqueListen = uniqList {inputList = map toStr listen;}; - in concatStrings uniqueListen - } - - User ${mainCfg.user} - Group ${mainCfg.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 allModules - } - - AddHandler type-map var - - - ${allDenied} - - - ${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 - - TraceEnable off - - ${if enableSSL then sslConf else ""} - - # Fascist default - deny access to everything. - - Options FollowSymLinks - AllowOverride None - ${allDenied} - - - # Generate directives for the main server. - ${perServerConf true mainCfg} - - # Always enable virtual hosts; it doesn't seem to hurt. - ${let - listen = concatMap getListen allHosts; - uniqueListen = uniqList {inputList = listen;}; - directives = concatMapStrings (listen: "NameVirtualHost ${listenToString listen}\n") uniqueListen; - in optionalString (!version24) directives - } - - ${let - makeVirtualHost = vhost: '' - - ${perServerConf false vhost} - - ''; - in concatMapStrings makeVirtualHost mainCfg.virtualHosts - } - ''; - - - 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)); - preferLocalBuild = true; - } - '' - cat ${php}/etc/php.ini > $out - echo "$options" >> $out - ''; - -in - - -{ - - ###### interface - - options = { - - services.httpdInte = { - - enable = mkOption { - type = types.bool; - default = false; - description = "Whether to enable 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 = '' - Cnfiguration lines appended to the generated Apache - configuration file. Note that this mechanism may not work - when is overridden. - ''; - }; - - extraModules = mkOption { - type = types.listOf types.unspecified; - default = []; - example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.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. - ''; - }; - - logPerVirtualHost = mkOption { - type = types.bool; - default = false; - 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. The account is created - automatically if it doesn't exist. - ''; - }; - - group = mkOption { - type = types.str; - default = "wwwrun"; - description = '' - Group under which httpd runs. The account is created - automatically if it doesn't exist. - ''; - }; - - logDir = mkOption { - 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, - /run/httpd, is deleted at boot time. - ''; - }; - - virtualHosts = mkOption { - type = types.listOf (types.submodule ( - { options = import { - inherit lib; - forMainServer = false; - }; - })); - default = []; - example = [ - { hostName = "foo"; - documentRoot = "/data/webroot-foo"; - } - { hostName = "bar"; - documentRoot = "/data/webroot-bar"; - } - ]; - description = '' - 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. - ''; - }; - - 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.str; - 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"; - example = "All -SSLv2 -SSLv3"; - description = "Allowed SSL/TLS protocol versions."; - }; - } - - # Include the options shared between the main server and virtual hosts. - // (import ./per-server-options.nix { - inherit lib; - forMainServer = true; - }); - - }; - - - ###### implementation - - config = mkIf config.services.httpdInte.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."; } - ]; - - warnings = map (cfg: ''apache-httpd's port option is deprecated. Use listen = [{/*ip = "*"; */ port = ${toString cfg.port};}]; instead'' ) (lib.filter (cfg: cfg.port != 0) allHosts); - - environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices; - - services.httpdInte.phpOptions = - '' - ; Needed for PHP's mail() function. - sendmail_path = sendmail -t -i - - ; Don't advertise PHP - expose_php = off - '' + optionalString (!isNull config.time.timeZone) '' - - ; Apparently PHP doesn't use $TZ. - date.timezone = "${config.time.timeZone}" - ''; - - systemd.services.httpdInte = - { description = "Apache HTTPD"; - - wantedBy = [ "multi-user.target" ]; - wants = [ "keys.target" ]; - after = [ "network.target" "fs.target" "postgresql.service" "keys.target" ]; - - path = - [ httpd pkgs.coreutils pkgs.gnugrep ] - ++ optional enablePHP pkgs.system-sendmail # Needed for PHP's mail() function. - ++ concatMap (svc: svc.extraServerPath) allSubservices; - - environment = - optionalAttrs enablePHP { PHPRC = phpIni; } - // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; } - // (listToAttrs (concatMap (svc: svc.globalEnvVars) allSubservices)); - - preStart = - '' - mkdir -m 0750 -p ${mainCfg.stateDir} - [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir} - ${optionalString version24 '' - 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 - ${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"; - }; - - }; -} diff --git a/nixops/modules/websites/apache/httpd_prod.nix b/nixops/modules/websites/apache/httpd_prod.nix deleted file mode 100644 index 4b646f5..0000000 --- a/nixops/modules/websites/apache/httpd_prod.nix +++ /dev/null @@ -1,731 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - - mainCfg = config.services.httpdProd; - - httpd = mainCfg.package.out; - - version24 = !versionOlder httpd.version "2.4"; - - httpdConf = mainCfg.configFile; - - php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ }; - - phpMajorVersion = head (splitString "." php.version); - - mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; }; - - defaultListen = cfg: if cfg.enableSSL - then [{ip = "*"; port = 443;}] - else [{ip = "*"; port = 80;}]; - - getListen = cfg: - let list = (lib.optional (cfg.port != 0) {ip = "*"; port = cfg.port;}) ++ cfg.listen; - in if list == [] - then defaultListen cfg - else list; - - listenToString = l: "${l.ip}:${toString l.port}"; - - extraModules = attrByPath ["extraModules"] [] mainCfg; - extraForeignModules = filter isAttrs extraModules; - extraApacheModules = filter isString extraModules; - - - 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" - (if version24 then "authn_core" else "authn_alias") - - # Authorization: is the user allowed access? - "authz_user" "authz_groupfile" "authz_host" - - # 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" - ] - ++ optionals version24 [ - "mpm_${mainCfg.multiProcessingModule}" - "authz_core" - "unixd" - "cache" "cache_disk" - "slotmem_shm" - "socache_shmcb" - # For compatibility with old configurations, the new module mod_access_compat is provided. - "access_compat" - ] - ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ]) - ++ optional enableSSL "ssl" - ++ extraApacheModules; - - - allDenied = if version24 then '' - Require all denied - '' else '' - Order deny,allow - Deny from all - ''; - - allGranted = if version24 then '' - Require all granted - '' else '' - Order allow,deny - Allow from all - ''; - - - loggingConf = (if mainCfg.logFormat != "none" then '' - ErrorLog ${mainCfg.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 ${mainCfg.logDir}/access.log ${mainCfg.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 ${if version24 then "shmcb" else "shm"}:${mainCfg.stateDir}/ssl_scache(512000) - - ${if version24 then "Mutex" else "SSLMutex"} posixsem - - SSLRandomSeed startup builtin - SSLRandomSeed connect builtin - - SSLProtocol ${mainCfg.sslProtocols} - SSLCipherSuite ${mainCfg.sslCiphers} - SSLHonorCipherOrder on - ''; - - - mimeConf = '' - TypesConfig ${httpd}/conf/mime.types - - AddType application/x-x509-ca-cert .crt - AddType application/x-pkcs7-crl .crl - AddType application/x-httpd-php .php .phtml - - - MIMEMagicFile ${httpd}/conf/magic - - ''; - - - 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}" - - - Options Indexes FollowSymLinks - AllowOverride None - ${allGranted} - - ''; - - 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 - - - AllowOverride FileInfo AuthConfig Limit Indexes - Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec - - ${allGranted} - - - ${allDenied} - - - - '' 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}/ - - Options +Indexes - ${allGranted} - AllowOverride All - - ''; - in concatMapStrings makeDirConf cfg.servedDirs - } - - ${concatMapStrings (svc: svc.extraConfig) subservices} - - ${cfg.extraConfig} - ''; - - - confFile = pkgs.writeText "httpd.conf" '' - - ServerRoot ${httpd} - - ${optionalString version24 '' - DefaultRuntimeDir ${mainCfg.stateDir}/runtime - ''} - - PidFile ${mainCfg.stateDir}/httpd.pid - - ${optionalString (mainCfg.multiProcessingModule != "prefork") '' - # mod_cgid requires this. - ScriptSock ${mainCfg.stateDir}/cgisock - ''} - - - MaxClients ${toString mainCfg.maxClients} - MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild} - - - ${let - listen = concatMap getListen allHosts; - toStr = listen: "Listen ${listenToString listen}\n"; - uniqueListen = uniqList {inputList = map toStr listen;}; - in concatStrings uniqueListen - } - - User ${mainCfg.user} - Group ${mainCfg.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 allModules - } - - AddHandler type-map var - - - ${allDenied} - - - ${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 - - TraceEnable off - - ${if enableSSL then sslConf else ""} - - # Fascist default - deny access to everything. - - Options FollowSymLinks - AllowOverride None - ${allDenied} - - - # Generate directives for the main server. - ${perServerConf true mainCfg} - - # Always enable virtual hosts; it doesn't seem to hurt. - ${let - listen = concatMap getListen allHosts; - uniqueListen = uniqList {inputList = listen;}; - directives = concatMapStrings (listen: "NameVirtualHost ${listenToString listen}\n") uniqueListen; - in optionalString (!version24) directives - } - - ${let - makeVirtualHost = vhost: '' - - ${perServerConf false vhost} - - ''; - in concatMapStrings makeVirtualHost mainCfg.virtualHosts - } - ''; - - - 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)); - preferLocalBuild = true; - } - '' - cat ${php}/etc/php.ini > $out - echo "$options" >> $out - ''; - -in - - -{ - - ###### interface - - options = { - - services.httpdProd = { - - enable = mkOption { - type = types.bool; - default = false; - description = "Whether to enable 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 = '' - Cnfiguration lines appended to the generated Apache - configuration file. Note that this mechanism may not work - when is overridden. - ''; - }; - - extraModules = mkOption { - type = types.listOf types.unspecified; - default = []; - example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.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. - ''; - }; - - logPerVirtualHost = mkOption { - type = types.bool; - default = false; - 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. The account is created - automatically if it doesn't exist. - ''; - }; - - group = mkOption { - type = types.str; - default = "wwwrun"; - description = '' - Group under which httpd runs. The account is created - automatically if it doesn't exist. - ''; - }; - - logDir = mkOption { - 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, - /run/httpd, is deleted at boot time. - ''; - }; - - virtualHosts = mkOption { - type = types.listOf (types.submodule ( - { options = import { - inherit lib; - forMainServer = false; - }; - })); - default = []; - example = [ - { hostName = "foo"; - documentRoot = "/data/webroot-foo"; - } - { hostName = "bar"; - documentRoot = "/data/webroot-bar"; - } - ]; - description = '' - 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. - ''; - }; - - 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.str; - 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"; - example = "All -SSLv2 -SSLv3"; - description = "Allowed SSL/TLS protocol versions."; - }; - } - - # Include the options shared between the main server and virtual hosts. - // (import ./per-server-options.nix { - inherit lib; - forMainServer = true; - }); - - }; - - - ###### implementation - - config = mkIf config.services.httpdProd.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."; } - ]; - - warnings = map (cfg: ''apache-httpd's port option is deprecated. Use listen = [{/*ip = "*"; */ port = ${toString cfg.port};}]; instead'' ) (lib.filter (cfg: cfg.port != 0) allHosts); - - environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices; - - services.httpdProd.phpOptions = - '' - ; Needed for PHP's mail() function. - sendmail_path = sendmail -t -i - - ; Don't advertise PHP - expose_php = off - '' + optionalString (!isNull config.time.timeZone) '' - - ; Apparently PHP doesn't use $TZ. - date.timezone = "${config.time.timeZone}" - ''; - - systemd.services.httpdProd = - { description = "Apache HTTPD"; - - wantedBy = [ "multi-user.target" ]; - wants = [ "keys.target" ]; - after = [ "network.target" "fs.target" "postgresql.service" "keys.target" ]; - - path = - [ httpd pkgs.coreutils pkgs.gnugrep ] - ++ optional enablePHP pkgs.system-sendmail # Needed for PHP's mail() function. - ++ concatMap (svc: svc.extraServerPath) allSubservices; - - environment = - optionalAttrs enablePHP { PHPRC = phpIni; } - // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; } - // (listToAttrs (concatMap (svc: svc.globalEnvVars) allSubservices)); - - preStart = - '' - mkdir -m 0750 -p ${mainCfg.stateDir} - [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir} - ${optionalString version24 '' - 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 - ${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"; - }; - - }; -} diff --git a/nixops/modules/websites/default.nix b/nixops/modules/websites/default.nix index 8bbb344..627d01a 100644 --- a/nixops/modules/websites/default.nix +++ b/nixops/modules/websites/default.nix @@ -131,14 +131,6 @@ in ./tools/diaspora.nix ./tools/ether.nix ./tools/peertube.nix - # built using: - # sed -e "s/services\.httpd/services\.httpdProd/g" .nix-defexpr/channels/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix - # Removed allGranted - # And removed users / groups - ./apache/httpd_prod.nix - ./apache/httpd_inte.nix - # except for this one for users/groups - ./apache/httpd_tools.nix # Adapted from base phpfpm ./phpfpm ]; -- 2.41.0