diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services/web-servers')
28 files changed, 5148 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix new file mode 100644 index 00000000000..098160ee369 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix @@ -0,0 +1,719 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + mainCfg = config.services.httpd; + + httpd = mainCfg.package.out; + + 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: + if cfg.listen == [] + then defaultListen cfg + else cfg.listen; + + 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" "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" + ] + ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ]) + ++ optional enableSSL "ssl" + ++ extraApacheModules; + + + allDenied = "Require all denied"; + allGranted = "Require all granted"; + + + 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 shmcb:${mainCfg.stateDir}/ssl_scache(512000) + + Mutex 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 + + <IfModule mod_mime_magic.c> + MIMEMagicFile ${httpd}/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} + ''; + + + confFile = pkgs.writeText "httpd.conf" '' + + ServerRoot ${httpd} + + DefaultRuntimeDir ${mainCfg.stateDir}/runtime + + PidFile ${mainCfg.stateDir}/httpd.pid + + ${optionalString (mainCfg.multiProcessingModule != "prefork") '' + # mod_cgid requires this. + ScriptSock ${mainCfg.stateDir}/cgisock + ''} + + <IfModule prefork.c> + MaxClients ${toString mainCfg.maxClients} + MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild} + </IfModule> + + ${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 (unique allModules) + } + + AddHandler type-map var + + <Files ~ "^\.ht"> + ${allDenied} + </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 + + TraceEnable off + + ${if enableSSL then sslConf else ""} + + # Fascist default - deny access to everything. + <Directory /> + Options FollowSymLinks + AllowOverride None + ${allDenied} + </Directory> + + # 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 + # want to serve. + <Directory /nix/store> + ${allGranted} + </Directory> + + # Generate directives for the main server. + ${perServerConf true mainCfg} + + ${let + makeVirtualHost = vhost: '' + <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}> + ${perServerConf false vhost} + </VirtualHost> + ''; + 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.httpd = { + + 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 <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"; } ]''; + 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 + <varname>name</varname> and <varname>path</varname> of the + module. + ''; + }; + + logPerVirtualHost = mkOption { + type = types.bool; + default = false; + description = '' + If enabled, each virtual host gets its own + <filename>access.log</filename> and + <filename>error.log</filename>, namely suffixed by the + <option>hostName</option> 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, + <filename>/run/httpd</filename>, is deleted at boot time. + ''; + }; + + virtualHosts = mkOption { + type = types.listOf (types.submodule ( + { options = import ./per-server-options.nix { + 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 <filename>php.ini</filename>."; + }; + + multiProcessingModule = mkOption { + type = types.str; + default = "prefork"; + example = "worker"; + description = + '' + 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 + number of child processes each running a number of + threads) and <literal>event</literal> (a recent variant of + <literal>worker</literal> 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.httpd.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 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); + + users.users = optionalAttrs (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 + { name = "wwwrun"; + gid = config.ids.gids.wwwrun; + }); + + environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices; + + services.httpd.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}" + ''; + + systemd.services.httpd = + { description = "Apache HTTPD"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "fs.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} + + 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/nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix new file mode 100644 index 00000000000..9d747549c27 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix @@ -0,0 +1,180 @@ +# This file defines the options that can be used both for the Apache +# main server configuration, and for the virtual hosts. (The latter +# has additional options that affect the web server as a whole, like +# the user/group to run under.) + +{ forMainServer, lib }: + +with lib; + +{ + + hostName = mkOption { + type = types.str; + default = "localhost"; + description = "Canonical hostname for the server."; + }; + + serverAliases = mkOption { + type = types.listOf types.str; + default = []; + example = ["www.example.org" "www.example.org:8080" "example.org"]; + description = '' + Additional names of virtual hosts served by this virtual host configuration. + ''; + }; + + listen = mkOption { + type = types.listOf (types.submodule ( + { + options = { + port = mkOption { + type = types.int; + description = "port to listen on"; + }; + ip = mkOption { + type = types.str; + default = "*"; + description = "Ip to listen on. 0.0.0.0 for ipv4 only, * for all."; + }; + }; + } )); + description = '' + List of { /* ip: "*"; */ port = 80;} to listen on + ''; + + default = []; + }; + + enableSSL = mkOption { + type = types.bool; + default = false; + description = "Whether to enable SSL (https) support."; + }; + + # Note: sslServerCert and sslServerKey can be left empty, but this + # only makes sense for virtual hosts (they will inherit from the + # main server). + + sslServerCert = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/host.cert"; + description = "Path to server SSL certificate."; + }; + + sslServerKey = mkOption { + type = types.path; + example = "/var/host.key"; + description = "Path to server SSL certificate key."; + }; + + sslServerChain = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/ca.pem"; + description = "Path to server SSL chain file."; + }; + + adminAddr = mkOption ({ + type = types.nullOr types.str; + example = "admin@example.org"; + description = "E-mail address of the server administrator."; + } // (if forMainServer then {} else {default = null;})); + + documentRoot = mkOption { + type = types.nullOr types.path; + default = null; + example = "/data/webserver/docs"; + description = '' + The path of Apache's document root directory. If left undefined, + an empty directory in the Nix store will be used as root. + ''; + }; + + servedDirs = mkOption { + type = types.listOf types.attrs; + default = []; + example = [ + { urlPath = "/nix"; + dir = "/home/eelco/Dev/nix-homepage"; + } + ]; + description = '' + This option provides a simple way to serve static directories. + ''; + }; + + servedFiles = mkOption { + type = types.listOf types.attrs; + default = []; + example = [ + { urlPath = "/foo/bar.png"; + file = "/home/eelco/some-file.png"; + } + ]; + description = '' + This option provides a simple way to serve individual, static files. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + <Directory /home> + Options FollowSymlinks + AllowOverride All + </Directory> + ''; + description = '' + These lines go to httpd.conf verbatim. They will go after + directories and directory aliases defined by default. + ''; + }; + + extraSubservices = mkOption { + type = types.listOf types.unspecified; + default = []; + description = "Extra subservices to enable in the webserver."; + }; + + enableUserDir = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable serving <filename>~/public_html</filename> as + <literal>/~<replaceable>username</replaceable></literal>. + ''; + }; + + globalRedirect = mkOption { + type = types.nullOr types.str; + default = null; + example = http://newserver.example.org/; + description = '' + If set, all requests for this host are redirected permanently to + the given URL. + ''; + }; + + logFormat = mkOption { + type = types.str; + default = "common"; + example = "combined"; + description = '' + Log format for Apache's log files. Possible values are: combined, common, referer, agent. + ''; + }; + + robotsEntries = mkOption { + type = types.lines; + default = ""; + example = "Disallow: /foo/"; + description = '' + Specification of pages to be ignored by web crawlers. See <link + xlink:href='http://www.robotstxt.org/'/> for details. + ''; + }; + +} diff --git a/nixpkgs/nixos/modules/services/web-servers/caddy.nix b/nixpkgs/nixos/modules/services/web-servers/caddy.nix new file mode 100644 index 00000000000..132c50735d9 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/caddy.nix @@ -0,0 +1,105 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.caddy; + configFile = pkgs.writeText "Caddyfile" cfg.config; +in { + options.services.caddy = { + enable = mkEnableOption "Caddy web server"; + + config = mkOption { + default = ""; + example = '' + example.com { + gzip + minify + log syslog + + root /srv/http + } + ''; + type = types.lines; + description = "Verbatim Caddyfile to use"; + }; + + ca = mkOption { + default = "https://acme-v02.api.letsencrypt.org/directory"; + example = "https://acme-staging-v02.api.letsencrypt.org/directory"; + type = types.str; + description = "Certificate authority ACME server. The default (Let's Encrypt production server) should be fine for most people."; + }; + + email = mkOption { + default = ""; + type = types.str; + description = "Email address (for Let's Encrypt certificate)"; + }; + + agree = mkOption { + default = false; + type = types.bool; + description = "Agree to Let's Encrypt Subscriber Agreement"; + }; + + dataDir = mkOption { + default = "/var/lib/caddy"; + type = types.path; + description = '' + The data directory, for storing certificates. Before 17.09, this + would create a .caddy directory. With 17.09 the contents of the + .caddy directory are in the specified data directory instead. + ''; + }; + + package = mkOption { + default = pkgs.caddy; + defaultText = "pkgs.caddy"; + type = types.package; + description = "Caddy package to use."; + }; + }; + + config = mkIf cfg.enable { + systemd.services.caddy = { + description = "Caddy web server"; + after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = mkIf (versionAtLeast config.system.stateVersion "17.09") + { CADDYPATH = cfg.dataDir; }; + serviceConfig = { + ExecStart = '' + ${cfg.package}/bin/caddy -root=/var/tmp -conf=${configFile} \ + -ca=${cfg.ca} -email=${cfg.email} ${optionalString cfg.agree "-agree"} + ''; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + Type = "simple"; + User = "caddy"; + Group = "caddy"; + Restart = "on-failure"; + StartLimitInterval = 86400; + StartLimitBurst = 5; + AmbientCapabilities = "cap_net_bind_service"; + CapabilityBoundingSet = "cap_net_bind_service"; + NoNewPrivileges = true; + LimitNPROC = 64; + LimitNOFILE = 1048576; + PrivateTmp = true; + PrivateDevices = true; + ProtectHome = true; + ProtectSystem = "full"; + ReadWriteDirectories = cfg.dataDir; + }; + }; + + users.users.caddy = { + group = "caddy"; + uid = config.ids.uids.caddy; + home = cfg.dataDir; + createHome = true; + }; + + users.groups.caddy.gid = config.ids.uids.caddy; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix b/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix new file mode 100644 index 00000000000..d6649fd472d --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix @@ -0,0 +1,77 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.darkhttpd; + + args = concatStringsSep " " ([ + cfg.rootDir + "--port ${toString cfg.port}" + "--addr ${cfg.address}" + ] ++ cfg.extraArgs + ++ optional cfg.hideServerId "--no-server-id" + ++ optional config.networking.enableIPv6 "--ipv6"); + +in { + options.services.darkhttpd = with types; { + enable = mkEnableOption "DarkHTTPd web server"; + + port = mkOption { + default = 80; + type = ints.u16; + description = '' + Port to listen on. + Pass 0 to let the system choose any free port for you. + ''; + }; + + address = mkOption { + default = "127.0.0.1"; + type = str; + description = '' + Address to listen on. + Pass `all` to listen on all interfaces. + ''; + }; + + rootDir = mkOption { + type = path; + description = '' + Path from which to serve files. + ''; + }; + + hideServerId = mkOption { + type = bool; + default = true; + description = '' + Don't identify the server type in headers or directory listings. + ''; + }; + + extraArgs = mkOption { + type = listOf str; + default = []; + description = '' + Additional configuration passed to the executable. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.darkhttpd = { + description = "Dark HTTPd"; + wants = [ "network.target" ]; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + DynamicUser = true; + ExecStart = "${pkgs.darkhttpd}/bin/darkhttpd ${args}"; + AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ]; + Restart = "on-failure"; + RestartSec = "2s"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix b/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix new file mode 100644 index 00000000000..a64a187255a --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix @@ -0,0 +1,72 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.fcgiwrap; +in { + + options = { + services.fcgiwrap = { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable fcgiwrap, a server for running CGI applications over FastCGI."; + }; + + preforkProcesses = mkOption { + type = types.int; + default = 1; + description = "Number of processes to prefork."; + }; + + socketType = mkOption { + type = types.enum [ "unix" "tcp" "tcp6" ]; + default = "unix"; + description = "Socket type: 'unix', 'tcp' or 'tcp6'."; + }; + + socketAddress = mkOption { + type = types.str; + default = "/run/fcgiwrap.sock"; + example = "1.2.3.4:5678"; + description = "Socket address. In case of a UNIX socket, this should be its filesystem path."; + }; + + user = mkOption { + type = types.nullOr types.str; + default = null; + description = "User permissions for the socket."; + }; + + group = mkOption { + type = types.nullOr types.str; + default = null; + description = "Group permissions for the socket."; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.fcgiwrap = { + after = [ "nss-user-lookup.target" ]; + wantedBy = optional (cfg.socketType != "unix") "multi-user.target"; + + serviceConfig = { + ExecStart = "${pkgs.fcgiwrap}/sbin/fcgiwrap -c ${builtins.toString cfg.preforkProcesses} ${ + if (cfg.socketType != "unix") then "-s ${cfg.socketType}:${cfg.socketAddress}" else "" + }"; + } // (if cfg.user != null && cfg.group != null then { + User = cfg.user; + Group = cfg.group; + } else { } ); + }; + + systemd.sockets = if (cfg.socketType == "unix") then { + fcgiwrap = { + wantedBy = [ "sockets.target" ]; + socketConfig.ListenStream = cfg.socketAddress; + }; + } else { }; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix b/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix new file mode 100644 index 00000000000..a6c4cbea122 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix @@ -0,0 +1,108 @@ +{ config, lib, pkgs, ...}: +let + cfg = config.services.hitch; + ocspDir = lib.optionalString cfg.ocsp-stapling.enabled "/var/cache/hitch/ocsp"; + hitchConfig = with lib; pkgs.writeText "hitch.conf" (concatStringsSep "\n" [ + ("backend = \"${cfg.backend}\"") + (concatMapStrings (s: "frontend = \"${s}\"\n") cfg.frontend) + (concatMapStrings (s: "pem-file = \"${s}\"\n") cfg.pem-files) + ("ciphers = \"${cfg.ciphers}\"") + ("ocsp-dir = \"${ocspDir}\"") + "user = \"${cfg.user}\"" + "group = \"${cfg.group}\"" + cfg.extraConfig + ]); +in +with lib; +{ + options = { + services.hitch = { + enable = mkEnableOption "Hitch Server"; + + backend = mkOption { + type = types.str; + description = '' + The host and port Hitch connects to when receiving + a connection in the form [HOST]:PORT + ''; + }; + + ciphers = mkOption { + type = types.str; + default = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + description = "The list of ciphers to use"; + }; + + frontend = mkOption { + type = types.either types.str (types.listOf types.str); + default = "[127.0.0.1]:443"; + description = '' + The port and interface of the listen endpoint in the ++ form [HOST]:PORT[+CERT]. + ''; + apply = toList; + }; + + pem-files = mkOption { + type = types.listOf types.path; + default = []; + description = "PEM files to use"; + }; + + ocsp-stapling = { + enabled = mkOption { + type = types.bool; + default = true; + description = "Whether to enable OCSP Stapling"; + }; + }; + + user = mkOption { + type = types.str; + default = "hitch"; + description = "The user to run as"; + }; + + group = mkOption { + type = types.str; + default = "hitch"; + description = "The group to run as"; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = "Additional configuration lines"; + }; + }; + + }; + + config = mkIf cfg.enable { + + systemd.services.hitch = { + description = "Hitch"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + preStart = '' + ${pkgs.hitch}/sbin/hitch -t --config ${hitchConfig} + '' + (optionalString cfg.ocsp-stapling.enabled '' + mkdir -p ${ocspDir} + chown -R hitch:hitch ${ocspDir} + ''); + serviceConfig = { + Type = "forking"; + ExecStart = "${pkgs.hitch}/sbin/hitch --daemon --config ${hitchConfig}"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + Restart = "always"; + RestartSec = "5s"; + LimitNOFILE = 131072; + }; + }; + + environment.systemPackages = [ pkgs.hitch ]; + + users.users.hitch.group = "hitch"; + users.groups.hitch = {}; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/hydron.nix b/nixpkgs/nixos/modules/services/web-servers/hydron.nix new file mode 100644 index 00000000000..a4a5a435b2e --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/hydron.nix @@ -0,0 +1,165 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.hydron; +in with lib; { + options.services.hydron = { + enable = mkEnableOption "hydron"; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/hydron"; + example = "/home/okina/hydron"; + description = "Location where hydron runs and stores data."; + }; + + interval = mkOption { + type = types.str; + default = "weekly"; + example = "06:00"; + description = '' + How often we run hydron import and possibly fetch tags. Runs by default every week. + + The format is described in + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry>. + ''; + }; + + password = mkOption { + type = types.str; + default = "hydron"; + example = "dumbpass"; + description = "Password for the hydron database."; + }; + + passwordFile = mkOption { + type = types.path; + default = "/run/keys/hydron-password-file"; + example = "/home/okina/hydron/keys/pass"; + description = "Password file for the hydron database."; + }; + + postgresArgs = mkOption { + type = types.str; + description = "Postgresql connection arguments."; + example = '' + { + "driver": "postgres", + "connection": "user=hydron password=dumbpass dbname=hydron sslmode=disable" + } + ''; + }; + + postgresArgsFile = mkOption { + type = types.path; + default = "/run/keys/hydron-postgres-args"; + example = "/home/okina/hydron/keys/postgres"; + description = "Postgresql connection arguments file."; + }; + + listenAddress = mkOption { + type = types.nullOr types.str; + default = null; + example = "127.0.0.1:8010"; + description = "Listen on a specific IP address and port."; + }; + + importPaths = mkOption { + type = types.listOf types.path; + default = []; + example = [ "/home/okina/Pictures" ]; + description = "Paths that hydron will recursively import."; + }; + + fetchTags = mkOption { + type = types.bool; + default = true; + description = "Fetch tags for imported images and webm from gelbooru."; + }; + }; + + config = mkIf cfg.enable { + services.hydron.passwordFile = mkDefault (pkgs.writeText "hydron-password-file" cfg.password); + services.hydron.postgresArgsFile = mkDefault (pkgs.writeText "hydron-postgres-args" cfg.postgresArgs); + services.hydron.postgresArgs = mkDefault '' + { + "driver": "postgres", + "connection": "user=hydron password=${cfg.password} host=/run/postgresql dbname=hydron sslmode=disable" + } + ''; + + services.postgresql = { + enable = true; + ensureDatabases = [ "hydron" ]; + ensureUsers = [ + { name = "hydron"; + ensurePermissions = { "DATABASE hydron" = "ALL PRIVILEGES"; }; + } + ]; + }; + + systemd.tmpfiles.rules = [ + "d '${cfg.dataDir}' 0750 hydron hydron - -" + "d '${cfg.dataDir}/.hydron' - hydron hydron - -" + "d '${cfg.dataDir}/images' - hydron hydron - -" + "Z '${cfg.dataDir}' - hydron hydron - -" + + "L+ '${cfg.dataDir}/.hydron/db_conf.json' - - - - ${cfg.postgresArgsFile}" + ]; + + systemd.services.hydron = { + description = "hydron"; + after = [ "network.target" "postgresql.service" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + User = "hydron"; + Group = "hydron"; + ExecStart = "${pkgs.hydron}/bin/hydron serve" + + optionalString (cfg.listenAddress != null) " -a ${cfg.listenAddress}"; + }; + }; + + systemd.services.hydron-fetch = { + description = "Import paths into hydron and possibly fetch tags"; + + serviceConfig = { + Type = "oneshot"; + User = "hydron"; + Group = "hydron"; + ExecStart = "${pkgs.hydron}/bin/hydron import " + + optionalString cfg.fetchTags "-f " + + (escapeShellArg cfg.dataDir) + "/images " + (escapeShellArgs cfg.importPaths); + }; + }; + + systemd.timers.hydron-fetch = { + description = "Automatically import paths into hydron and possibly fetch tags"; + after = [ "network.target" "hydron.service" ]; + wantedBy = [ "timers.target" ]; + + timerConfig = { + Persistent = true; + OnCalendar = cfg.interval; + }; + }; + + users = { + groups.hydron.gid = config.ids.gids.hydron; + + users.hydron = { + description = "hydron server service user"; + home = cfg.dataDir; + group = "hydron"; + uid = config.ids.uids.hydron; + }; + }; + }; + + imports = [ + (mkRenamedOptionModule [ "services" "hydron" "baseDir" ] [ "services" "hydron" "dataDir" ]) + ]; + + meta.maintainers = with maintainers; [ chiiruno ]; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh b/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh new file mode 100644 index 00000000000..2eb89a90f67 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh @@ -0,0 +1,72 @@ +set -e + +source $stdenv/setup + +mkdir -p $out/bin + +cat > $out/bin/control <<EOF +mkdir -p $logDir +chown -R $user $logDir +export PATH=$PATH:$su/bin + +start() +{ + su $user -s /bin/sh -c "$jboss/bin/run.sh \ + -Djboss.server.base.dir=$serverDir \ + -Djboss.server.base.url=file://$serverDir \ + -Djboss.server.temp.dir=$tempDir \ + -Djboss.server.log.dir=$logDir \ + -Djboss.server.lib.url=$libUrl \ + -c default" +} + +stop() +{ + su $user -s /bin/sh -c "$jboss/bin/shutdown.sh -S" +} + +if test "\$1" = start +then + trap stop 15 + + start +elif test "\$1" = stop +then + stop +elif test "\$1" = init +then + echo "Are you sure you want to create a new server instance (old server instance will be lost!)?" + read answer + + if ! test \$answer = "yes" + then + exit 1 + fi + + rm -rf $serverDir + mkdir -p $serverDir + cd $serverDir + cp -av $jboss/server/default . + sed -i -e "s|deploy/|$deployDir|" default/conf/jboss-service.xml + + if ! test "$useJK" = "" + then + sed -i -e 's|<attribute name="UseJK">false</attribute>|<attribute name="UseJK">true</attribute>|' default/deploy/jboss-web.deployer/META-INF/jboss-service.xml + sed -i -e 's|<Engine name="jboss.web" defaultHost="localhost">|<Engine name="jboss.web" defaultHost="localhost" jvmRoute="node1">|' default/deploy/jboss-web.deployer/server.xml + fi + + # Make files accessible for the server user + + chown -R $user $serverDir + for i in \`find $serverDir -type d\` + do + chmod 755 \$i + done + for i in \`find $serverDir -type f\` + do + chmod 644 \$i + done +fi +EOF + +chmod +x $out/bin/* diff --git a/nixpkgs/nixos/modules/services/web-servers/jboss/default.nix b/nixpkgs/nixos/modules/services/web-servers/jboss/default.nix new file mode 100644 index 00000000000..d28724281a8 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/jboss/default.nix @@ -0,0 +1,80 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.jboss; + + jbossService = pkgs.stdenv.mkDerivation { + name = "jboss-server"; + builder = ./builder.sh; + inherit (pkgs) jboss su; + inherit (cfg) tempDir logDir libUrl deployDir serverDir user useJK; + }; + +in + +{ + + ###### interface + + options = { + + services.jboss = { + + enable = mkOption { + default = false; + description = "Whether to enable JBoss. WARNING : this package is outdated and is known to have vulnerabilities."; + }; + + tempDir = mkOption { + default = "/tmp"; + description = "Location where JBoss stores its temp files"; + }; + + logDir = mkOption { + default = "/var/log/jboss"; + description = "Location of the logfile directory of JBoss"; + }; + + serverDir = mkOption { + description = "Location of the server instance files"; + default = "/var/jboss/server"; + }; + + deployDir = mkOption { + description = "Location of the deployment files"; + default = "/nix/var/nix/profiles/default/server/default/deploy/"; + }; + + libUrl = mkOption { + default = "file:///nix/var/nix/profiles/default/server/default/lib"; + description = "Location where the shared library JARs are stored"; + }; + + user = mkOption { + default = "nobody"; + description = "User account under which jboss runs."; + }; + + useJK = mkOption { + default = false; + description = "Whether to use to connector to the Apache HTTP server"; + }; + + }; + + }; + + + ###### implementation + + config = mkIf config.services.jboss.enable { + systemd.services.jboss = { + description = "JBoss server"; + script = "${jbossService}/bin/control start"; + wantedBy = [ "multi-user.target" ]; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix new file mode 100644 index 00000000000..9f25dc34f3f --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix @@ -0,0 +1,91 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.lighttpd.cgit; + pathPrefix = if stringLength cfg.subdir == 0 then "" else "/" + cfg.subdir; + configFile = pkgs.writeText "cgitrc" + '' + # default paths to static assets + css=${pathPrefix}/cgit.css + logo=${pathPrefix}/cgit.png + favicon=${pathPrefix}/favicon.ico + + # user configuration + ${cfg.configText} + ''; +in +{ + + options.services.lighttpd.cgit = { + + enable = mkOption { + default = false; + type = types.bool; + description = '' + If true, enable cgit (fast web interface for git repositories) as a + sub-service in lighttpd. + ''; + }; + + subdir = mkOption { + default = "cgit"; + example = ""; + type = types.str; + description = '' + The subdirectory in which to serve cgit. The web application will be + accessible at http://yourserver/''${subdir} + ''; + }; + + configText = mkOption { + default = ""; + example = '' + source-filter=''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py + about-filter=''${pkgs.cgit}/lib/cgit/filters/about-formatting.sh + cache-size=1000 + scan-path=/srv/git + ''; + type = types.lines; + description = '' + Verbatim contents of the cgit runtime configuration file. Documentation + (with cgitrc example file) is available in "man cgitrc". Or online: + http://git.zx2c4.com/cgit/tree/cgitrc.5.txt + ''; + }; + + }; + + config = mkIf cfg.enable { + + # make the cgitrc manpage available + environment.systemPackages = [ pkgs.cgit ]; + + # declare module dependencies + services.lighttpd.enableModules = [ "mod_cgi" "mod_alias" "mod_setenv" ]; + + services.lighttpd.extraConfig = '' + $HTTP["url"] =~ "^/${cfg.subdir}" { + cgi.assign = ( + "cgit.cgi" => "${pkgs.cgit}/cgit/cgit.cgi" + ) + alias.url = ( + "${pathPrefix}/cgit.css" => "${pkgs.cgit}/cgit/cgit.css", + "${pathPrefix}/cgit.png" => "${pkgs.cgit}/cgit/cgit.png", + "${pathPrefix}" => "${pkgs.cgit}/cgit/cgit.cgi" + ) + setenv.add-environment = ( + "CGIT_CONFIG" => "${configFile}" + ) + } + ''; + + systemd.services.lighttpd.preStart = '' + mkdir -p /var/cache/cgit + chown lighttpd:lighttpd /var/cache/cgit + ''; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix new file mode 100644 index 00000000000..3f262451c2c --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix @@ -0,0 +1,58 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.lighttpd.collectd; + + collectionConf = pkgs.writeText "collection.conf" '' + datadir: "${config.services.collectd.dataDir}" + libdir: "${config.services.collectd.package}/lib/collectd" + ''; + + defaultCollectionCgi = config.services.collectd.package.overrideDerivation(old: { + name = "collection.cgi"; + dontConfigure = true; + buildPhase = "true"; + installPhase = '' + substituteInPlace contrib/collection.cgi --replace '"/etc/collection.conf"' '$ENV{COLLECTION_CONF}' + cp contrib/collection.cgi $out + ''; + }); +in +{ + + options.services.lighttpd.collectd = { + + enable = mkEnableOption "collectd subservice accessible at http://yourserver/collectd"; + + collectionCgi = mkOption { + type = types.path; + default = defaultCollectionCgi; + description = '' + Path to collection.cgi script from (collectd sources)/contrib/collection.cgi + This option allows to use a customized version + ''; + }; + }; + + config = mkIf cfg.enable { + services.lighttpd.enableModules = [ "mod_cgi" "mod_alias" "mod_setenv" ]; + + services.lighttpd.extraConfig = '' + $HTTP["url"] =~ "^/collectd" { + cgi.assign = ( + ".cgi" => "${pkgs.perl}/bin/perl" + ) + alias.url = ( + "/collectd" => "${cfg.collectionCgi}" + ) + setenv.add-environment = ( + "PERL5LIB" => "${with pkgs.perlPackages; makePerlPath [ CGI HTMLParser URI pkgs.rrdtool ]}", + "COLLECTION_CONF" => "${collectionConf}" + ) + } + ''; + }; + +} diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix new file mode 100644 index 00000000000..7a3df26e47a --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix @@ -0,0 +1,256 @@ +# NixOS module for lighttpd web server + +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.lighttpd; + + # List of known lighttpd modules, ordered by how the lighttpd documentation + # recommends them being imported: + # http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails + # + # Some modules are always imported and should not appear in the config: + # disallowedModules = [ "mod_indexfile" "mod_dirlisting" "mod_staticfile" ]; + # + # For full module list, see the output of running ./configure in the lighttpd + # source. + allKnownModules = [ + "mod_rewrite" + "mod_redirect" + "mod_alias" + "mod_access" + "mod_auth" + "mod_status" + "mod_simple_vhost" + "mod_evhost" + "mod_userdir" + "mod_secdownload" + "mod_fastcgi" + "mod_proxy" + "mod_cgi" + "mod_ssi" + "mod_compress" + "mod_usertrack" + "mod_expire" + "mod_rrdtool" + "mod_accesslog" + # Remaining list of modules, order assumed to be unimportant. + "mod_authn_file" + "mod_authn_gssapi" + "mod_authn_ldap" + "mod_authn_mysql" + "mod_cml" + "mod_deflate" + "mod_evasive" + "mod_extforward" + "mod_flv_streaming" + "mod_geoip" + "mod_magnet" + "mod_mysql_vhost" + "mod_openssl" # since v1.4.46 + "mod_scgi" + "mod_setenv" + "mod_trigger_b4_dl" + "mod_uploadprogress" + "mod_vhostdb" # since v1.4.46 + "mod_webdav" + "mod_wstunnel" # since v1.4.46 + ]; + + maybeModuleString = moduleName: + if elem moduleName cfg.enableModules then ''"${moduleName}"'' else ""; + + modulesIncludeString = concatStringsSep ",\n" + (filter (x: x != "") (map maybeModuleString allKnownModules)); + + configFile = if cfg.configText != "" then + pkgs.writeText "lighttpd.conf" '' + ${cfg.configText} + '' + else + pkgs.writeText "lighttpd.conf" '' + server.document-root = "${cfg.document-root}" + server.port = ${toString cfg.port} + server.username = "lighttpd" + server.groupname = "lighttpd" + + # As for why all modules are loaded here, instead of having small + # server.modules += () entries in each sub-service extraConfig snippet, + # read this: + # + # http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails + # http://redmine.lighttpd.net/issues/2337 + # + # Basically, lighttpd doesn't want to load (or even silently ignore) a + # module for a second time, and there is no way to check if a module has + # been loaded already. So if two services were to put the same module in + # server.modules += (), that would break the lighttpd configuration. + server.modules = ( + ${modulesIncludeString} + ) + + # Logging (logs end up in systemd journal) + accesslog.use-syslog = "enable" + server.errorlog-use-syslog = "enable" + + ${lib.optionalString cfg.enableUpstreamMimeTypes '' + include "${pkgs.lighttpd}/share/lighttpd/doc/config/conf.d/mime.conf" + ''} + + static-file.exclude-extensions = ( ".fcgi", ".php", ".rb", "~", ".inc" ) + index-file.names = ( "index.html" ) + + ${if cfg.mod_userdir then '' + userdir.path = "public_html" + '' else ""} + + ${if cfg.mod_status then '' + status.status-url = "/server-status" + status.statistics-url = "/server-statistics" + status.config-url = "/server-config" + '' else ""} + + ${cfg.extraConfig} + ''; + +in + +{ + + options = { + + services.lighttpd = { + + enable = mkOption { + default = false; + type = types.bool; + description = '' + Enable the lighttpd web server. + ''; + }; + + port = mkOption { + default = 80; + type = types.int; + description = '' + TCP port number for lighttpd to bind to. + ''; + }; + + document-root = mkOption { + default = "/srv/www"; + type = types.path; + description = '' + Document-root of the web server. Must be readable by the "lighttpd" user. + ''; + }; + + mod_userdir = mkOption { + default = false; + type = types.bool; + description = '' + If true, requests in the form /~user/page.html are rewritten to take + the file public_html/page.html from the home directory of the user. + ''; + }; + + enableModules = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "mod_cgi" "mod_status" ]; + description = '' + List of lighttpd modules to enable. Sub-services take care of + enabling modules as needed, so this option is mainly for when you + want to add custom stuff to + <option>services.lighttpd.extraConfig</option> that depends on a + certain module. + ''; + }; + + enableUpstreamMimeTypes = mkOption { + type = types.bool; + default = true; + description = '' + Whether to include the list of mime types bundled with lighttpd + (upstream). If you disable this, no mime types will be added by + NixOS and you will have to add your own mime types in + <option>services.lighttpd.extraConfig</option>. + ''; + }; + + mod_status = mkOption { + default = false; + type = types.bool; + description = '' + Show server status overview at /server-status, statistics at + /server-statistics and list of loaded modules at /server-config. + ''; + }; + + configText = mkOption { + default = ""; + type = types.lines; + example = ''...verbatim config file contents...''; + description = '' + Overridable config file contents to use for lighttpd. By default, use + the contents automatically generated by NixOS. + ''; + }; + + extraConfig = mkOption { + default = ""; + type = types.lines; + description = '' + These configuration lines will be appended to the generated lighttpd + config file. Note that this mechanism does not work when the manual + <option>configText</option> option is used. + ''; + }; + + }; + + }; + + config = mkIf cfg.enable { + + assertions = [ + { assertion = all (x: elem x allKnownModules) cfg.enableModules; + message = '' + One (or more) modules in services.lighttpd.enableModules are + unrecognized. + + Known modules: ${toString allKnownModules} + + services.lighttpd.enableModules: ${toString cfg.enableModules} + ''; + } + ]; + + services.lighttpd.enableModules = mkMerge + [ (mkIf cfg.mod_status [ "mod_status" ]) + (mkIf cfg.mod_userdir [ "mod_userdir" ]) + # always load mod_accesslog so that we can log to the journal + [ "mod_accesslog" ] + ]; + + systemd.services.lighttpd = { + description = "Lighttpd Web Server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig.ExecStart = "${pkgs.lighttpd}/sbin/lighttpd -D -f ${configFile}"; + # SIGINT => graceful shutdown + serviceConfig.KillSignal = "SIGINT"; + }; + + users.users.lighttpd = { + group = "lighttpd"; + description = "lighttpd web server privilege separation user"; + uid = config.ids.uids.lighttpd; + }; + + users.groups.lighttpd.gid = config.ids.gids.lighttpd; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix new file mode 100644 index 00000000000..c494d6966a7 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix @@ -0,0 +1,52 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.gitweb; + package = pkgs.gitweb.override (optionalAttrs cfg.gitwebTheme { + gitwebTheme = true; + }); + +in +{ + + options.services.lighttpd.gitweb = { + + enable = mkOption { + default = false; + type = types.bool; + description = '' + If true, enable gitweb in lighttpd. Access it at http://yourserver/gitweb + ''; + }; + + }; + + config = mkIf config.services.lighttpd.gitweb.enable { + + # declare module dependencies + services.lighttpd.enableModules = [ "mod_cgi" "mod_redirect" "mod_alias" "mod_setenv" ]; + + services.lighttpd.extraConfig = '' + $HTTP["url"] =~ "^/gitweb" { + cgi.assign = ( + ".cgi" => "${pkgs.perl}/bin/perl" + ) + url.redirect = ( + "^/gitweb$" => "/gitweb/" + ) + alias.url = ( + "/gitweb/static/" => "${package}/static/", + "/gitweb/" => "${package}/gitweb.cgi" + ) + setenv.add-environment = ( + "GITWEB_CONFIG" => "${cfg.gitwebConfigFile}", + "HOME" => "${cfg.projectroot}" + ) + } + ''; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/web-servers/meguca.nix b/nixpkgs/nixos/modules/services/web-servers/meguca.nix new file mode 100644 index 00000000000..5a00070dc94 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/meguca.nix @@ -0,0 +1,174 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.meguca; + postgres = config.services.postgresql; +in with lib; { + options.services.meguca = { + enable = mkEnableOption "meguca"; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/meguca"; + example = "/home/okina/meguca"; + description = "Location where meguca stores it's database and links."; + }; + + password = mkOption { + type = types.str; + default = "meguca"; + example = "dumbpass"; + description = "Password for the meguca database."; + }; + + passwordFile = mkOption { + type = types.path; + default = "/run/keys/meguca-password-file"; + example = "/home/okina/meguca/keys/pass"; + description = "Password file for the meguca database."; + }; + + reverseProxy = mkOption { + type = types.nullOr types.str; + default = null; + example = "192.168.1.5"; + description = "Reverse proxy IP."; + }; + + sslCertificate = mkOption { + type = types.nullOr types.str; + default = null; + example = "/home/okina/meguca/ssl.cert"; + description = "Path to the SSL certificate."; + }; + + listenAddress = mkOption { + type = types.nullOr types.str; + default = null; + example = "127.0.0.1:8000"; + description = "Listen on a specific IP address and port."; + }; + + cacheSize = mkOption { + type = types.nullOr types.int; + default = null; + example = 256; + description = "Cache size in MB."; + }; + + postgresArgs = mkOption { + type = types.str; + example = "user=meguca password=dumbpass dbname=meguca sslmode=disable"; + description = "Postgresql connection arguments."; + }; + + postgresArgsFile = mkOption { + type = types.path; + default = "/run/keys/meguca-postgres-args"; + example = "/home/okina/meguca/keys/postgres"; + description = "Postgresql connection arguments file."; + }; + + compressTraffic = mkOption { + type = types.bool; + default = false; + description = "Compress all traffic with gzip."; + }; + + assumeReverseProxy = mkOption { + type = types.bool; + default = false; + description = "Assume the server is behind a reverse proxy, when resolving client IPs."; + }; + + httpsOnly = mkOption { + type = types.bool; + default = false; + description = "Serve and listen only through HTTPS."; + }; + + videoPaths = mkOption { + type = types.listOf types.path; + default = []; + example = [ "/home/okina/Videos/tehe_pero.webm" ]; + description = "Videos that will be symlinked into www/videos."; + }; + }; + + config = mkIf cfg.enable { + security.sudo.enable = cfg.enable; + services.postgresql.enable = cfg.enable; + services.postgresql.package = pkgs.postgresql_11; + services.meguca.passwordFile = mkDefault (pkgs.writeText "meguca-password-file" cfg.password); + services.meguca.postgresArgsFile = mkDefault (pkgs.writeText "meguca-postgres-args" cfg.postgresArgs); + services.meguca.postgresArgs = mkDefault "user=meguca password=${cfg.password} dbname=meguca sslmode=disable"; + + systemd.services.meguca = { + description = "meguca"; + after = [ "network.target" "postgresql.service" ]; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + # Ensure folder exists or create it and links and permissions are correct + mkdir -p ${escapeShellArg cfg.dataDir}/www + rm -rf ${escapeShellArg cfg.dataDir}/www/videos + ln -sf ${pkgs.meguca}/share/meguca/www/* ${escapeShellArg cfg.dataDir}/www + unlink ${escapeShellArg cfg.dataDir}/www/videos + mkdir -p ${escapeShellArg cfg.dataDir}/www/videos + + for vid in ${escapeShellArg cfg.videoPaths}; do + ln -sf $vid ${escapeShellArg cfg.dataDir}/www/videos + done + + chmod 750 ${escapeShellArg cfg.dataDir} + chown -R meguca:meguca ${escapeShellArg cfg.dataDir} + + # Ensure the database is correct or create it + ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createuser \ + -SDR meguca || true + ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createdb \ + -T template0 -E UTF8 -O meguca meguca || true + ${pkgs.sudo}/bin/sudo -u meguca ${postgres.package}/bin/psql \ + -c "ALTER ROLE meguca WITH PASSWORD '$(cat ${escapeShellArg cfg.passwordFile})';" || true + ''; + + script = '' + cd ${escapeShellArg cfg.dataDir} + + ${pkgs.meguca}/bin/meguca -d "$(cat ${escapeShellArg cfg.postgresArgsFile})"'' + + optionalString (cfg.reverseProxy != null) " -R ${cfg.reverseProxy}" + + optionalString (cfg.sslCertificate != null) " -S ${cfg.sslCertificate}" + + optionalString (cfg.listenAddress != null) " -a ${cfg.listenAddress}" + + optionalString (cfg.cacheSize != null) " -c ${toString cfg.cacheSize}" + + optionalString (cfg.compressTraffic) " -g" + + optionalString (cfg.assumeReverseProxy) " -r" + + optionalString (cfg.httpsOnly) " -s" + " start"; + + serviceConfig = { + PermissionsStartOnly = true; + Type = "forking"; + User = "meguca"; + Group = "meguca"; + ExecStop = "${pkgs.meguca}/bin/meguca stop"; + }; + }; + + users = { + groups.meguca.gid = config.ids.gids.meguca; + + users.meguca = { + description = "meguca server service user"; + home = cfg.dataDir; + createHome = true; + group = "meguca"; + uid = config.ids.uids.meguca; + }; + }; + }; + + imports = [ + (mkRenamedOptionModule [ "services" "meguca" "baseDir" ] [ "services" "meguca" "dataDir" ]) + ]; + + meta.maintainers = with maintainers; [ chiiruno ]; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix b/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix new file mode 100644 index 00000000000..f9b1a8b6ccc --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix @@ -0,0 +1,132 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.mighttpd2; + configFile = pkgs.writeText "mighty-config" cfg.config; + routingFile = pkgs.writeText "mighty-routing" cfg.routing; +in { + options.services.mighttpd2 = { + enable = mkEnableOption "Mighttpd2 web server"; + + config = mkOption { + default = ""; + example = '' + # Example configuration for Mighttpd 2 + Port: 80 + # IP address or "*" + Host: * + Debug_Mode: Yes # Yes or No + # If available, "nobody" is much more secure for User:. + User: root + # If available, "nobody" is much more secure for Group:. + Group: root + Pid_File: /run/mighty.pid + Logging: Yes # Yes or No + Log_File: /var/log/mighty # The directory must be writable by User: + Log_File_Size: 16777216 # bytes + Log_Backup_Number: 10 + Index_File: index.html + Index_Cgi: index.cgi + Status_File_Dir: /usr/local/share/mighty/status + Connection_Timeout: 30 # seconds + Fd_Cache_Duration: 10 # seconds + # Server_Name: Mighttpd/3.x.y + Tls_Port: 443 + Tls_Cert_File: cert.pem # should change this with an absolute path + # should change this with comma-separated absolute paths + Tls_Chain_Files: chain.pem + # Currently, Tls_Key_File must not be encrypted. + Tls_Key_File: privkey.pem # should change this with an absolute path + Service: 0 # 0 is HTTP only, 1 is HTTPS only, 2 is both + ''; + type = types.lines; + description = '' + Verbatim config file to use + (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html) + ''; + }; + + routing = mkOption { + default = ""; + example = '' + # Example routing for Mighttpd 2 + + # Domain lists + [localhost www.example.com] + + # Entries are looked up in the specified order + # All paths must end with "/" + + # A path to CGI scripts should be specified with "=>" + /~alice/cgi-bin/ => /home/alice/public_html/cgi-bin/ + + # A path to static files should be specified with "->" + /~alice/ -> /home/alice/public_html/ + /cgi-bin/ => /export/cgi-bin/ + + # Reverse proxy rules should be specified with ">>" + # /path >> host:port/path2 + # Either "host" or ":port" can be committed, but not both. + /app/cal/ >> example.net/calendar/ + # Yesod app in the same server + /app/wiki/ >> 127.0.0.1:3000/ + + / -> /export/www/ + ''; + type = types.lines; + description = '' + Verbatim routing file to use + (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html) + ''; + }; + + cores = mkOption { + default = null; + type = types.nullOr types.int; + description = '' + How many cores to use. + If null it will be determined automatically + ''; + }; + + }; + + config = mkIf cfg.enable { + assertions = + [ { assertion = cfg.routing != ""; + message = "You need at least one rule in mighttpd2.routing"; + } + ]; + systemd.services.mighttpd2 = { + description = "Mighttpd2 web server"; + after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = '' + ${pkgs.haskellPackages.mighttpd2}/bin/mighty \ + ${configFile} \ + ${routingFile} \ + +RTS -N${optionalString (cfg.cores != null) "${cfg.cores}"} + ''; + Type = "simple"; + User = "mighttpd2"; + Group = "mighttpd2"; + Restart = "on-failure"; + AmbientCapabilities = "cap_net_bind_service"; + CapabilityBoundingSet = "cap_net_bind_service"; + }; + }; + + users.users.mighttpd2 = { + group = "mighttpd2"; + uid = config.ids.uids.mighttpd2; + isSystemUser = true; + }; + + users.groups.mighttpd2.gid = config.ids.gids.mighttpd2; + }; + + meta.maintainers = with lib.maintainers; [ fgaz ]; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/minio.nix b/nixpkgs/nixos/modules/services/web-servers/minio.nix new file mode 100644 index 00000000000..cd123000f00 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/minio.nix @@ -0,0 +1,108 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.minio; +in +{ + meta.maintainers = [ maintainers.bachp ]; + + options.services.minio = { + enable = mkEnableOption "Minio Object Storage"; + + listenAddress = mkOption { + default = ":9000"; + type = types.str; + description = "Listen on a specific IP address and port."; + }; + + dataDir = mkOption { + default = "/var/lib/minio/data"; + type = types.path; + description = "The data directory, for storing the objects."; + }; + + configDir = mkOption { + default = "/var/lib/minio/config"; + type = types.path; + description = "The config directory, for the access keys and other settings."; + }; + + accessKey = mkOption { + default = ""; + type = types.str; + description = '' + Access key of 5 to 20 characters in length that clients use to access the server. + This overrides the access key that is generated by minio on first startup and stored inside the + <literal>configDir</literal> directory. + ''; + }; + + secretKey = mkOption { + default = ""; + type = types.str; + description = '' + Specify the Secret key of 8 to 40 characters in length that clients use to access the server. + This overrides the secret key that is generated by minio on first startup and stored inside the + <literal>configDir</literal> directory. + ''; + }; + + region = mkOption { + default = "us-east-1"; + type = types.str; + description = '' + The physical location of the server. By default it is set to us-east-1, which is same as AWS S3's and Minio's default region. + ''; + }; + + browser = mkOption { + default = true; + type = types.bool; + description = "Enable or disable access to web UI."; + }; + + package = mkOption { + default = pkgs.minio; + defaultText = "pkgs.minio"; + type = types.package; + description = "Minio package to use."; + }; + }; + + config = mkIf cfg.enable { + systemd.tmpfiles.rules = [ + "d '${cfg.configDir}' - minio minio - -" + "d '${cfg.dataDir}' - minio minio - -" + ]; + + systemd.services.minio = { + description = "Minio Object Storage"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --config-dir=${cfg.configDir} ${cfg.dataDir}"; + Type = "simple"; + User = "minio"; + Group = "minio"; + LimitNOFILE = 65536; + }; + environment = { + MINIO_REGION = "${cfg.region}"; + MINIO_BROWSER = "${if cfg.browser then "on" else "off"}"; + } // optionalAttrs (cfg.accessKey != "") { + MINIO_ACCESS_KEY = "${cfg.accessKey}"; + } // optionalAttrs (cfg.secretKey != "") { + MINIO_SECRET_KEY = "${cfg.secretKey}"; + }; + }; + + users.users.minio = { + group = "minio"; + uid = config.ids.uids.minio; + }; + + users.groups.minio.gid = config.ids.uids.minio; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix new file mode 100644 index 00000000000..e597f34700a --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix @@ -0,0 +1,709 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.nginx; + certs = config.security.acme.certs; + vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts; + acmeEnabledVhosts = filter (vhostConfig: vhostConfig.enableACME && vhostConfig.useACMEHost == null) vhostsConfigs; + virtualHosts = mapAttrs (vhostName: vhostConfig: + let + serverName = if vhostConfig.serverName != null + then vhostConfig.serverName + else vhostName; + in + vhostConfig // { + inherit serverName; + } // (optionalAttrs vhostConfig.enableACME { + sslCertificate = "${certs.${serverName}.directory}/fullchain.pem"; + sslCertificateKey = "${certs.${serverName}.directory}/key.pem"; + sslTrustedCertificate = "${certs.${serverName}.directory}/full.pem"; + }) // (optionalAttrs (vhostConfig.useACMEHost != null) { + sslCertificate = "${certs.${vhostConfig.useACMEHost}.directory}/fullchain.pem"; + sslCertificateKey = "${certs.${vhostConfig.useACMEHost}.directory}/key.pem"; + sslTrustedCertificate = "${certs.${vhostConfig.useACMEHost}.directory}/fullchain.pem"; + }) + ) cfg.virtualHosts; + enableIPv6 = config.networking.enableIPv6; + + recommendedProxyConfig = pkgs.writeText "nginx-recommended-proxy-headers.conf" '' + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header Accept-Encoding ""; + ''; + + upstreamConfig = toString (flip mapAttrsToList cfg.upstreams (name: upstream: '' + upstream ${name} { + ${toString (flip mapAttrsToList upstream.servers (name: server: '' + server ${name} ${optionalString server.backup "backup"}; + ''))} + ${upstream.extraConfig} + } + '')); + + configFile = pkgs.writers.writeNginxConfig "nginx.conf" '' + user ${cfg.user} ${cfg.group}; + error_log ${cfg.logError}; + daemon off; + + ${cfg.config} + + ${optionalString (cfg.eventsConfig != "" || cfg.config == "") '' + events { + ${cfg.eventsConfig} + } + ''} + + ${optionalString (cfg.httpConfig == "" && cfg.config == "") '' + http { + include ${cfg.package}/conf/mime.types; + include ${cfg.package}/conf/fastcgi.conf; + include ${cfg.package}/conf/uwsgi_params; + + ${optionalString (cfg.resolver.addresses != []) '' + resolver ${toString cfg.resolver.addresses} ${optionalString (cfg.resolver.valid != "") "valid=${cfg.resolver.valid}"} ${optionalString (!cfg.resolver.ipv6) "ipv6=off"}; + ''} + ${upstreamConfig} + + ${optionalString (cfg.recommendedOptimisation) '' + # optimisation + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + ''} + + ssl_protocols ${cfg.sslProtocols}; + ssl_ciphers ${cfg.sslCiphers}; + ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"} + + ${optionalString (cfg.recommendedTlsSettings) '' + ssl_session_cache shared:SSL:42m; + ssl_session_timeout 23m; + ssl_ecdh_curve secp384r1; + ssl_prefer_server_ciphers on; + ssl_stapling on; + ssl_stapling_verify on; + ''} + + ${optionalString (cfg.recommendedGzipSettings) '' + gzip on; + gzip_proxied any; + gzip_comp_level 5; + gzip_types + application/atom+xml + application/javascript + application/json + application/xml + application/xml+rss + image/svg+xml + text/css + text/javascript + text/plain + text/xml; + gzip_vary on; + ''} + + ${optionalString (cfg.recommendedProxySettings) '' + proxy_redirect off; + proxy_connect_timeout 90; + proxy_send_timeout 90; + proxy_read_timeout 90; + proxy_http_version 1.0; + include ${recommendedProxyConfig}; + ''} + + # $connection_upgrade is used for websocket proxying + map $http_upgrade $connection_upgrade { + default upgrade; + ''' close; + } + client_max_body_size ${cfg.clientMaxBodySize}; + + server_tokens ${if cfg.serverTokens then "on" else "off"}; + + ${cfg.commonHttpConfig} + + ${vhosts} + + ${optionalString cfg.statusPage '' + server { + listen 80; + ${optionalString enableIPv6 "listen [::]:80;" } + + server_name localhost; + + location /nginx_status { + stub_status on; + access_log off; + allow 127.0.0.1; + ${optionalString enableIPv6 "allow ::1;"} + deny all; + } + } + ''} + + ${cfg.appendHttpConfig} + }''} + + ${optionalString (cfg.httpConfig != "") '' + http { + include ${cfg.package}/conf/mime.types; + include ${cfg.package}/conf/fastcgi.conf; + include ${cfg.package}/conf/uwsgi_params; + ${cfg.httpConfig} + }''} + + ${cfg.appendConfig} + ''; + + configPath = if cfg.enableReload + then "/etc/nginx/nginx.conf" + else configFile; + + vhosts = concatStringsSep "\n" (mapAttrsToList (vhostName: vhost: + let + onlySSL = vhost.onlySSL || vhost.enableSSL; + hasSSL = onlySSL || vhost.addSSL || vhost.forceSSL; + + defaultListen = + if vhost.listen != [] then vhost.listen + else ((optionals hasSSL ( + singleton { addr = "0.0.0.0"; port = 443; ssl = true; } + ++ optional enableIPv6 { addr = "[::]"; port = 443; ssl = true; } + )) ++ optionals (!onlySSL) ( + singleton { addr = "0.0.0.0"; port = 80; ssl = false; } + ++ optional enableIPv6 { addr = "[::]"; port = 80; ssl = false; } + )); + + hostListen = + if vhost.forceSSL + then filter (x: x.ssl) defaultListen + else defaultListen; + + listenString = { addr, port, ssl, extraParameters ? [], ... }: + "listen ${addr}:${toString port} " + + optionalString ssl "ssl " + + optionalString (ssl && vhost.http2) "http2 " + + optionalString vhost.default "default_server " + + optionalString (extraParameters != []) (concatStringsSep " " extraParameters) + + ";"; + + redirectListen = filter (x: !x.ssl) defaultListen; + + acmeLocation = optionalString (vhost.enableACME || vhost.useACMEHost != null) '' + location /.well-known/acme-challenge { + ${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"} + root ${vhost.acmeRoot}; + auth_basic off; + } + ${optionalString (vhost.acmeFallbackHost != null) '' + location @acme-fallback { + auth_basic off; + proxy_pass http://${vhost.acmeFallbackHost}; + } + ''} + ''; + + in '' + ${optionalString vhost.forceSSL '' + server { + ${concatMapStringsSep "\n" listenString redirectListen} + + server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases}; + ${acmeLocation} + location / { + return 301 https://$host$request_uri; + } + } + ''} + + server { + ${concatMapStringsSep "\n" listenString hostListen} + server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases}; + ${acmeLocation} + ${optionalString (vhost.root != null) "root ${vhost.root};"} + ${optionalString (vhost.globalRedirect != null) '' + return 301 http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri; + ''} + ${optionalString hasSSL '' + ssl_certificate ${vhost.sslCertificate}; + ssl_certificate_key ${vhost.sslCertificateKey}; + ''} + ${optionalString (hasSSL && vhost.sslTrustedCertificate != null) '' + ssl_trusted_certificate ${vhost.sslTrustedCertificate}; + ''} + + ${optionalString (vhost.basicAuthFile != null || vhost.basicAuth != {}) '' + auth_basic secured; + auth_basic_user_file ${if vhost.basicAuthFile != null then vhost.basicAuthFile else mkHtpasswd vhostName vhost.basicAuth}; + ''} + + ${mkLocations vhost.locations} + + ${vhost.extraConfig} + } + '' + ) virtualHosts); + mkLocations = locations: concatStringsSep "\n" (map (config: '' + location ${config.location} { + ${optionalString (config.proxyPass != null && !cfg.proxyResolveWhileRunning) + "proxy_pass ${config.proxyPass};" + } + ${optionalString (config.proxyPass != null && cfg.proxyResolveWhileRunning) '' + set $nix_proxy_target "${config.proxyPass}"; + proxy_pass $nix_proxy_target; + ''} + ${optionalString config.proxyWebsockets '' + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + ''} + ${optionalString (config.index != null) "index ${config.index};"} + ${optionalString (config.tryFiles != null) "try_files ${config.tryFiles};"} + ${optionalString (config.root != null) "root ${config.root};"} + ${optionalString (config.alias != null) "alias ${config.alias};"} + ${optionalString (config.return != null) "return ${config.return};"} + ${config.extraConfig} + ${optionalString (config.proxyPass != null && cfg.recommendedProxySettings) "include ${recommendedProxyConfig};"} + } + '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations))); + mkHtpasswd = vhostName: authDef: pkgs.writeText "${vhostName}.htpasswd" ( + concatStringsSep "\n" (mapAttrsToList (user: password: '' + ${user}:{PLAIN}${password} + '') authDef) + ); +in + +{ + options = { + services.nginx = { + enable = mkEnableOption "Nginx Web Server"; + + statusPage = mkOption { + default = false; + type = types.bool; + description = " + Enable status page reachable from localhost on http://127.0.0.1/nginx_status. + "; + }; + + recommendedTlsSettings = mkOption { + default = false; + type = types.bool; + description = " + Enable recommended TLS settings. + "; + }; + + recommendedOptimisation = mkOption { + default = false; + type = types.bool; + description = " + Enable recommended optimisation settings. + "; + }; + + recommendedGzipSettings = mkOption { + default = false; + type = types.bool; + description = " + Enable recommended gzip settings. + "; + }; + + recommendedProxySettings = mkOption { + default = false; + type = types.bool; + description = " + Enable recommended proxy settings. + "; + }; + + package = mkOption { + default = pkgs.nginxStable; + defaultText = "pkgs.nginxStable"; + type = types.package; + description = " + Nginx package to use. This defaults to the stable version. Note + that the nginx team recommends to use the mainline version which + available in nixpkgs as <literal>nginxMainline</literal>. + "; + }; + + logError = mkOption { + default = "stderr"; + description = " + Configures logging. + The first parameter defines a file that will store the log. The + special value stderr selects the standard error file. Logging to + syslog can be configured by specifying the “syslog:” prefix. + The second parameter determines the level of logging, and can be + one of the following: debug, info, notice, warn, error, crit, + alert, or emerg. Log levels above are listed in the order of + increasing severity. Setting a certain log level will cause all + messages of the specified and more severe log levels to be logged. + If this parameter is omitted then error is used. + "; + }; + + preStart = mkOption { + type = types.lines; + default = '' + test -d ${cfg.stateDir}/logs || mkdir -m 750 -p ${cfg.stateDir}/logs + test `stat -c %a ${cfg.stateDir}` = "750" || chmod 750 ${cfg.stateDir} + test `stat -c %a ${cfg.stateDir}/logs` = "750" || chmod 750 ${cfg.stateDir}/logs + chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir} + ''; + description = " + Shell commands executed before the service's nginx is started. + "; + }; + + config = mkOption { + default = ""; + description = " + Verbatim nginx.conf configuration. + This is mutually exclusive with the structured configuration + via virtualHosts and the recommendedXyzSettings configuration + options. See appendConfig for appending to the generated http block. + "; + }; + + appendConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Configuration lines appended to the generated Nginx + configuration file. Commonly used by different modules + providing http snippets. <option>appendConfig</option> + can be specified more than once and it's value will be + concatenated (contrary to <option>config</option> which + can be set only once). + ''; + }; + + commonHttpConfig = mkOption { + type = types.lines; + default = ""; + example = '' + resolver 127.0.0.1 valid=5s; + + log_format myformat '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent"'; + ''; + description = '' + With nginx you must provide common http context definitions before + they are used, e.g. log_format, resolver, etc. inside of server + or location contexts. Use this attribute to set these definitions + at the appropriate location. + ''; + }; + + httpConfig = mkOption { + type = types.lines; + default = ""; + description = " + Configuration lines to be set inside the http block. + This is mutually exclusive with the structured configuration + via virtualHosts and the recommendedXyzSettings configuration + options. See appendHttpConfig for appending to the generated http block. + "; + }; + + eventsConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Configuration lines to be set inside the events block. + ''; + }; + + appendHttpConfig = mkOption { + type = types.lines; + default = ""; + description = " + Configuration lines to be appended to the generated http block. + This is mutually exclusive with using config and httpConfig for + specifying the whole http block verbatim. + "; + }; + + enableReload = mkOption { + default = false; + type = types.bool; + description = '' + Reload nginx when configuration file changes (instead of restart). + The configuration file is exposed at <filename>/etc/nginx/nginx.conf</filename>. + See also <literal>systemd.services.*.restartIfChanged</literal>. + ''; + }; + + stateDir = mkOption { + default = "/var/spool/nginx"; + description = " + Directory holding all state for nginx to run. + "; + }; + + user = mkOption { + type = types.str; + default = "nginx"; + description = "User account under which nginx runs."; + }; + + group = mkOption { + type = types.str; + default = "nginx"; + description = "Group account under which nginx runs."; + }; + + serverTokens = mkOption { + type = types.bool; + default = false; + description = "Show nginx version in headers and error pages."; + }; + + clientMaxBodySize = mkOption { + type = types.str; + default = "10m"; + description = "Set nginx global client_max_body_size."; + }; + + sslCiphers = mkOption { + type = types.str; + default = "EECDH+aRSA+AESGCM:EDH+aRSA:EECDH+aRSA:+AES256:+AES128:+SHA1:!CAMELLIA:!SEED:!3DES:!DES:!RC4:!eNULL"; + description = "Ciphers to choose from when negotiating tls handshakes."; + }; + + sslProtocols = mkOption { + type = types.str; + default = "TLSv1.2 TLSv1.3"; + example = "TLSv1 TLSv1.1 TLSv1.2 TLSv1.3"; + description = "Allowed TLS protocol versions."; + }; + + sslDhparam = mkOption { + type = types.nullOr types.path; + default = null; + example = "/path/to/dhparams.pem"; + description = "Path to DH parameters file."; + }; + + proxyResolveWhileRunning = mkOption { + type = types.bool; + default = false; + description = '' + Resolves domains of proxyPass targets at runtime + and not only at start, you have to set + services.nginx.resolver, too. + ''; + }; + + resolver = mkOption { + type = types.submodule { + options = { + addresses = mkOption { + type = types.listOf types.str; + default = []; + example = literalExample ''[ "[::1]" "127.0.0.1:5353" ]''; + description = "List of resolvers to use"; + }; + valid = mkOption { + type = types.str; + default = ""; + example = "30s"; + description = '' + By default, nginx caches answers using the TTL value of a response. + An optional valid parameter allows overriding it + ''; + }; + ipv6 = mkOption { + type = types.bool; + default = true; + description = '' + By default, nginx will look up both IPv4 and IPv6 addresses while resolving. + If looking up of IPv6 addresses is not desired, the ipv6=off parameter can be + specified. + ''; + }; + }; + }; + description = '' + Configures name servers used to resolve names of upstream servers into addresses + ''; + default = {}; + }; + + upstreams = mkOption { + type = types.attrsOf (types.submodule { + options = { + servers = mkOption { + type = types.attrsOf (types.submodule { + options = { + backup = mkOption { + type = types.bool; + default = false; + description = '' + Marks the server as a backup server. It will be passed + requests when the primary servers are unavailable. + ''; + }; + }; + }); + description = '' + Defines the address and other parameters of the upstream servers. + ''; + default = {}; + }; + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + These lines go to the end of the upstream verbatim. + ''; + }; + }; + }); + description = '' + Defines a group of servers to use as proxy target. + ''; + default = {}; + }; + + virtualHosts = mkOption { + type = types.attrsOf (types.submodule (import ./vhost-options.nix { + inherit config lib; + })); + default = { + localhost = {}; + }; + example = literalExample '' + { + "hydra.example.com" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://localhost:3000"; + }; + }; + }; + ''; + description = "Declarative vhost config"; + }; + }; + }; + + config = mkIf cfg.enable { + # TODO: test user supplied config file pases syntax test + + warnings = + let + deprecatedSSL = name: config: optional config.enableSSL + '' + config.services.nginx.virtualHosts.<name>.enableSSL is deprecated, + use config.services.nginx.virtualHosts.<name>.onlySSL instead. + ''; + + in flatten (mapAttrsToList deprecatedSSL virtualHosts); + + assertions = + let + hostOrAliasIsNull = l: l.root == null || l.alias == null; + in [ + { + assertion = all (host: all hostOrAliasIsNull (attrValues host.locations)) (attrValues virtualHosts); + message = "Only one of nginx root or alias can be specified on a location."; + } + + { + assertion = all (conf: with conf; + !(addSSL && (onlySSL || enableSSL)) && + !(forceSSL && (onlySSL || enableSSL)) && + !(addSSL && forceSSL) + ) (attrValues virtualHosts); + message = '' + Options services.nginx.service.virtualHosts.<name>.addSSL, + services.nginx.virtualHosts.<name>.onlySSL and services.nginx.virtualHosts.<name>.forceSSL + are mutually exclusive. + ''; + } + + { + assertion = all (conf: !(conf.enableACME && conf.useACMEHost != null)) (attrValues virtualHosts); + message = '' + Options services.nginx.service.virtualHosts.<name>.enableACME and + services.nginx.virtualHosts.<name>.useACMEHost are mutually exclusive. + ''; + } + ]; + + systemd.services.nginx = { + description = "Nginx Web Server"; + wantedBy = [ "multi-user.target" ]; + wants = concatLists (map (vhostConfig: ["acme-${vhostConfig.serverName}.service" "acme-selfsigned-${vhostConfig.serverName}.service"]) acmeEnabledVhosts); + after = [ "network.target" ] ++ map (vhostConfig: "acme-selfsigned-${vhostConfig.serverName}.service") acmeEnabledVhosts; + stopIfChanged = false; + preStart = + '' + ${cfg.preStart} + ${cfg.package}/bin/nginx -c ${configPath} -p ${cfg.stateDir} -t + ''; + serviceConfig = { + ExecStart = "${cfg.package}/bin/nginx -c ${configPath} -p ${cfg.stateDir}"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + Restart = "always"; + RestartSec = "10s"; + StartLimitInterval = "1min"; + }; + }; + + environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload { + source = configFile; + }; + + systemd.services.nginx-config-reload = mkIf cfg.enableReload { + wantedBy = [ "nginx.service" ]; + restartTriggers = [ configFile ]; + script = '' + if ${pkgs.systemd}/bin/systemctl -q is-active nginx.service ; then + ${pkgs.systemd}/bin/systemctl reload nginx.service + fi + ''; + serviceConfig.RemainAfterExit = true; + }; + + security.acme.certs = filterAttrs (n: v: v != {}) ( + let + acmePairs = map (vhostConfig: { name = vhostConfig.serverName; value = { + user = cfg.user; + group = lib.mkDefault cfg.group; + webroot = vhostConfig.acmeRoot; + extraDomains = genAttrs vhostConfig.serverAliases (alias: null); + postRun = '' + systemctl reload nginx + ''; + }; }) acmeEnabledVhosts; + in + listToAttrs acmePairs + ); + + users.users = optionalAttrs (cfg.user == "nginx") (singleton + { name = "nginx"; + group = cfg.group; + uid = config.ids.uids.nginx; + }); + + users.groups = optionalAttrs (cfg.group == "nginx") (singleton + { name = "nginx"; + gid = config.ids.gids.nginx; + }); + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix new file mode 100644 index 00000000000..272fd148018 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix @@ -0,0 +1,61 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.gitweb; + package = pkgs.gitweb.override (optionalAttrs cfg.gitwebTheme { + gitwebTheme = true; + }); + +in +{ + + options.services.nginx.gitweb = { + + enable = mkOption { + default = false; + type = types.bool; + description = '' + If true, enable gitweb in nginx. Access it at http://yourserver/gitweb + ''; + }; + + }; + + config = mkIf config.services.nginx.gitweb.enable { + + systemd.services.gitweb = { + description = "GitWeb service"; + script = "${package}/gitweb.cgi --fastcgi --nproc=1"; + environment = { + FCGI_SOCKET_PATH = "/run/gitweb/gitweb.sock"; + }; + serviceConfig = { + User = "nginx"; + Group = "nginx"; + RuntimeDirectory = [ "gitweb" ]; + }; + wantedBy = [ "multi-user.target" ]; + }; + + services.nginx = { + virtualHosts.default = { + locations."/gitweb/static/" = { + alias = "${package}/static/"; + }; + locations."/gitweb/" = { + extraConfig = '' + include ${pkgs.nginx}/conf/fastcgi_params; + fastcgi_param GITWEB_CONFIG ${cfg.gitwebConfigFile}; + fastcgi_pass unix:/run/gitweb/gitweb.sock; + ''; + }; + }; + }; + + }; + + meta.maintainers = with maintainers; [ gnidorah ]; + +} diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix new file mode 100644 index 00000000000..aeb9b1dd79e --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix @@ -0,0 +1,95 @@ +# This file defines the options that can be used both for the Apache +# main server configuration, and for the virtual hosts. (The latter +# has additional options that affect the web server as a whole, like +# the user/group to run under.) + +{ lib }: + +with lib; + +{ + options = { + proxyPass = mkOption { + type = types.nullOr types.str; + default = null; + example = "http://www.example.org/"; + description = '' + Adds proxy_pass directive and sets recommended proxy headers if + recommendedProxySettings is enabled. + ''; + }; + + proxyWebsockets = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Whether to supporty proxying websocket connections with HTTP/1.1. + ''; + }; + + index = mkOption { + type = types.nullOr types.str; + default = null; + example = "index.php index.html"; + description = '' + Adds index directive. + ''; + }; + + tryFiles = mkOption { + type = types.nullOr types.str; + default = null; + example = "$uri =404"; + description = '' + Adds try_files directive. + ''; + }; + + root = mkOption { + type = types.nullOr types.path; + default = null; + example = "/your/root/directory"; + description = '' + Root directory for requests. + ''; + }; + + alias = mkOption { + type = types.nullOr types.path; + default = null; + example = "/your/alias/directory"; + description = '' + Alias directory for requests. + ''; + }; + + return = mkOption { + type = types.nullOr types.str; + default = null; + example = "301 http://example.com$request_uri;"; + description = '' + Adds a return directive, for e.g. redirections. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + These lines go to the end of the location verbatim. + ''; + }; + + priority = mkOption { + type = types.int; + default = 1000; + description = '' + Order of this location block in relation to the others in the vhost. + The semantics are the same as with `lib.mkOrder`. Smaller values have + a greater priority. + ''; + }; + }; +} + diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix new file mode 100644 index 00000000000..15b933c984a --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix @@ -0,0 +1,228 @@ +# This file defines the options that can be used both for the Apache +# main server configuration, and for the virtual hosts. (The latter +# has additional options that affect the web server as a whole, like +# the user/group to run under.) + +{ lib, ... }: + +with lib; +{ + options = { + serverName = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Name of this virtual host. Defaults to attribute name in virtualHosts. + ''; + example = "example.org"; + }; + + serverAliases = mkOption { + type = types.listOf types.str; + default = []; + example = ["www.example.org" "example.org"]; + description = '' + Additional names of virtual hosts served by this virtual host configuration. + ''; + }; + + listen = mkOption { + type = with types; listOf (submodule { options = { + addr = mkOption { type = str; description = "IP address."; }; + port = mkOption { type = int; description = "Port number."; default = 80; }; + ssl = mkOption { type = bool; description = "Enable SSL."; default = false; }; + extraParameters = mkOption { type = listOf str; description = "Extra parameters of this listen directive."; default = []; example = [ "reuseport" "deferred" ]; }; + }; }); + default = []; + example = [ + { addr = "195.154.1.1"; port = 443; ssl = true;} + { addr = "192.154.1.1"; port = 80; } + ]; + description = '' + Listen addresses and ports for this virtual host. + IPv6 addresses must be enclosed in square brackets. + Note: this option overrides <literal>addSSL</literal> + and <literal>onlySSL</literal>. + ''; + }; + + enableACME = mkOption { + type = types.bool; + default = false; + description = '' + Whether to ask Let's Encrypt to sign a certificate for this vhost. + Alternately, you can use an existing certificate through <option>useACMEHost</option>. + ''; + }; + + useACMEHost = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + A host of an existing Let's Encrypt certificate to use. + This is useful if you have many subdomains and want to avoid hitting the + <link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>. + Alternately, you can generate a certificate through <option>enableACME</option>. + <emphasis>Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using <xref linkend="opt-security.acme.certs"/>.</emphasis> + ''; + }; + + acmeRoot = mkOption { + type = types.str; + default = "/var/lib/acme/acme-challenge"; + description = "Directory for the acme challenge which is PUBLIC, don't put certs or keys in here"; + }; + + acmeFallbackHost = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Host which to proxy requests to if acme challenge is not found. Useful + if you want multiple hosts to be able to verify the same domain name. + ''; + }; + + addSSL = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable HTTPS in addition to plain HTTP. This will set defaults for + <literal>listen</literal> to listen on all interfaces on the respective default + ports (80, 443). + ''; + }; + + onlySSL = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable HTTPS and reject plain HTTP connections. This will set + defaults for <literal>listen</literal> to listen on all interfaces on port 443. + ''; + }; + + enableSSL = mkOption { + type = types.bool; + visible = false; + default = false; + }; + + forceSSL = mkOption { + type = types.bool; + default = false; + description = '' + Whether to add a separate nginx server block that permanently redirects (301) + all plain HTTP traffic to HTTPS. This will set defaults for + <literal>listen</literal> to listen on all interfaces on the respective default + ports (80, 443), where the non-SSL listens are used for the redirect vhosts. + ''; + }; + + sslCertificate = mkOption { + type = types.path; + example = "/var/host.cert"; + description = "Path to server SSL certificate."; + }; + + sslCertificateKey = mkOption { + type = types.path; + example = "/var/host.key"; + description = "Path to server SSL certificate key."; + }; + + sslTrustedCertificate = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/root.cert"; + description = "Path to root SSL certificate for stapling and client certificates."; + }; + + http2 = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable HTTP 2. + Note that (as of writing) due to nginx's implementation, to disable + HTTP 2 you have to disable it on all vhosts that use a given + IP address / port. + If there is one server block configured to enable http2,then it is + enabled for all server blocks on this IP. + See https://stackoverflow.com/a/39466948/263061. + ''; + }; + + root = mkOption { + type = types.nullOr types.path; + default = null; + example = "/data/webserver/docs"; + description = '' + The path of the web root directory. + ''; + }; + + default = mkOption { + type = types.bool; + default = false; + description = '' + Makes this vhost the default. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + These lines go to the end of the vhost verbatim. + ''; + }; + + globalRedirect = mkOption { + type = types.nullOr types.str; + default = null; + example = "newserver.example.org"; + description = '' + If set, all requests for this host are redirected permanently to + the given hostname. + ''; + }; + + basicAuth = mkOption { + type = types.attrsOf types.str; + default = {}; + example = literalExample '' + { + user = "password"; + }; + ''; + description = '' + Basic Auth protection for a vhost. + + WARNING: This is implemented to store the password in plain text in the + nix store. + ''; + }; + + basicAuthFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Basic Auth password file for a vhost. + ''; + }; + + locations = mkOption { + type = types.attrsOf (types.submodule (import ./location-options.nix { + inherit lib; + })); + default = {}; + example = literalExample '' + { + "/" = { + proxyPass = "http://localhost:3000"; + }; + }; + ''; + description = "Declarative location config"; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix b/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix new file mode 100644 index 00000000000..4ab7e3f0c0a --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix @@ -0,0 +1,279 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.phpfpm; + + runtimeDir = "/run/phpfpm"; + + toStr = value: + if true == value then "yes" + else if false == value then "no" + else toString value; + + fpmCfgFile = pool: poolOpts: pkgs.writeText "phpfpm-${pool}.conf" '' + [global] + ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings)} + ${optionalString (cfg.extraConfig != null) cfg.extraConfig} + + [${pool}] + ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") poolOpts.settings)} + ${concatStringsSep "\n" (mapAttrsToList (n: v: "env[${n}] = ${toStr v}") poolOpts.phpEnv)} + ${optionalString (poolOpts.extraConfig != null) poolOpts.extraConfig} + ''; + + phpIni = poolOpts: pkgs.runCommand "php.ini" { + inherit (poolOpts) phpPackage phpOptions; + preferLocalBuild = true; + nixDefaults = '' + sendmail_path = "/run/wrappers/bin/sendmail -t -i" + ''; + passAsFile = [ "nixDefaults" "phpOptions" ]; + } '' + cat $phpPackage/etc/php.ini $nixDefaultsPath $phpOptionsPath > $out + ''; + + poolOpts = { name, ... }: + let + poolOpts = cfg.pools.${name}; + in + { + options = { + socket = mkOption { + type = types.str; + readOnly = true; + description = '' + Path to the unix socket file on which to accept FastCGI requests. + <note><para>This option is read-only and managed by NixOS.</para></note> + ''; + }; + + listen = mkOption { + type = types.str; + default = ""; + example = "/path/to/unix/socket"; + description = '' + The address on which to accept FastCGI requests. + ''; + }; + + phpPackage = mkOption { + type = types.package; + default = cfg.phpPackage; + defaultText = "config.services.phpfpm.phpPackage"; + description = '' + The PHP package to use for running this PHP-FPM pool. + ''; + }; + + phpOptions = mkOption { + type = types.lines; + default = cfg.phpOptions; + defaultText = "config.services.phpfpm.phpOptions"; + description = '' + "Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool." + ''; + }; + + phpEnv = lib.mkOption { + type = with types; attrsOf str; + default = {}; + description = '' + Environment variables used for this PHP-FPM pool. + ''; + example = literalExample '' + { + HOSTNAME = "$HOSTNAME"; + TMP = "/tmp"; + TMPDIR = "/tmp"; + TEMP = "/tmp"; + } + ''; + }; + + user = mkOption { + type = types.str; + description = "User account under which this pool runs."; + }; + + group = mkOption { + type = types.str; + description = "Group account under which this pool runs."; + }; + + settings = mkOption { + type = with types; attrsOf (oneOf [ str int bool ]); + default = {}; + description = '' + PHP-FPM pool directives. Refer to the "List of pool directives" section of + <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/> + for details. Note that settings names must be enclosed in quotes (e.g. + <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>). + ''; + example = literalExample '' + { + "pm" = "dynamic"; + "pm.max_children" = 75; + "pm.start_servers" = 10; + "pm.min_spare_servers" = 5; + "pm.max_spare_servers" = 20; + "pm.max_requests" = 500; + } + ''; + }; + + extraConfig = mkOption { + type = with types; nullOr lines; + default = null; + description = '' + Extra lines that go into the pool configuration. + See the documentation on <literal>php-fpm.conf</literal> for + details on configuration directives. + ''; + }; + }; + + config = { + socket = if poolOpts.listen == "" then "${runtimeDir}/${name}.sock" else poolOpts.listen; + group = mkDefault poolOpts.user; + + settings = mapAttrs (name: mkDefault){ + listen = poolOpts.socket; + user = poolOpts.user; + group = poolOpts.group; + }; + }; + }; + +in { + + options = { + services.phpfpm = { + settings = mkOption { + type = with types; attrsOf (oneOf [ str int bool ]); + default = {}; + description = '' + PHP-FPM global directives. Refer to the "List of global php-fpm.conf directives" section of + <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/> + for details. Note that settings names must be enclosed in quotes (e.g. + <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>). + You need not specify the options <literal>error_log</literal> or + <literal>daemonize</literal> here, since they are generated by NixOS. + ''; + }; + + extraConfig = mkOption { + type = with types; nullOr lines; + default = null; + description = '' + Extra configuration that should be put in the global section of + the PHP-FPM configuration file. Do not specify the options + <literal>error_log</literal> or + <literal>daemonize</literal> here, since they are generated by + NixOS. + ''; + }; + + phpPackage = mkOption { + type = types.package; + default = pkgs.php; + defaultText = "pkgs.php"; + description = '' + The PHP package to use for running the PHP-FPM service. + ''; + }; + + phpOptions = mkOption { + type = types.lines; + default = ""; + example = + '' + date.timezone = "CET" + ''; + description = '' + Options appended to the PHP configuration file <filename>php.ini</filename>. + ''; + }; + + pools = mkOption { + type = types.attrsOf (types.submodule poolOpts); + default = {}; + example = literalExample '' + { + mypool = { + user = "php"; + group = "php"; + phpPackage = pkgs.php; + settings = ''' + "pm" = "dynamic"; + "pm.max_children" = 75; + "pm.start_servers" = 10; + "pm.min_spare_servers" = 5; + "pm.max_spare_servers" = 20; + "pm.max_requests" = 500; + '''; + } + }''; + description = '' + PHP-FPM pools. If no pools are defined, the PHP-FPM + service is disabled. + ''; + }; + }; + }; + + config = mkIf (cfg.pools != {}) { + + warnings = + mapAttrsToList (pool: poolOpts: '' + Using config.services.phpfpm.pools.${pool}.listen is deprecated and will become unsupported in a future release. Please reference the read-only option config.services.phpfpm.pools.${pool}.socket to access the path of your socket. + '') (filterAttrs (pool: poolOpts: poolOpts.listen != "") cfg.pools) ++ + mapAttrsToList (pool: poolOpts: '' + Using config.services.phpfpm.pools.${pool}.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.pools.${pool}.settings. + '') (filterAttrs (pool: poolOpts: poolOpts.extraConfig != null) cfg.pools) ++ + optional (cfg.extraConfig != null) '' + Using config.services.phpfpm.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.settings. + '' + ; + + services.phpfpm.settings = { + error_log = "syslog"; + daemonize = false; + }; + + systemd.slices.phpfpm = { + description = "PHP FastCGI Process manager pools slice"; + }; + + systemd.targets.phpfpm = { + description = "PHP FastCGI Process manager pools target"; + wantedBy = [ "multi-user.target" ]; + }; + + systemd.services = mapAttrs' (pool: poolOpts: + nameValuePair "phpfpm-${pool}" { + description = "PHP FastCGI Process Manager service for pool ${pool}"; + after = [ "network.target" ]; + wantedBy = [ "phpfpm.target" ]; + partOf = [ "phpfpm.target" ]; + serviceConfig = let + cfgFile = fpmCfgFile pool poolOpts; + iniFile = phpIni poolOpts; + in { + Slice = "phpfpm.slice"; + PrivateDevices = true; + ProtectSystem = "full"; + ProtectHome = true; + # XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work + RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK"; + Type = "notify"; + ExecStart = "${poolOpts.phpPackage}/bin/php-fpm -y ${cfgFile} -c ${iniFile}"; + ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID"; + RuntimeDirectory = "phpfpm"; + RuntimeDirectoryPreserve = true; # Relevant when multiple processes are running + }; + } + ) cfg.pools; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/shellinabox.nix b/nixpkgs/nixos/modules/services/web-servers/shellinabox.nix new file mode 100644 index 00000000000..58a02ac59c3 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/shellinabox.nix @@ -0,0 +1,122 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.shellinabox; + + # If a certificate file is specified, shellinaboxd requires + # a file descriptor to retrieve it + fd = "3"; + createFd = optionalString (cfg.certFile != null) "${fd}<${cfg.certFile}"; + + # Command line arguments for the shellinabox daemon + args = [ "--background" ] + ++ optional (! cfg.enableSSL) "--disable-ssl" + ++ optional (cfg.certFile != null) "--cert-fd=${fd}" + ++ optional (cfg.certDirectory != null) "--cert=${cfg.certDirectory}" + ++ cfg.extraOptions; + + # Command to start shellinaboxd + cmd = "${pkgs.shellinabox}/bin/shellinaboxd ${concatStringsSep " " args}"; + + # Command to start shellinaboxd if certFile is specified + wrappedCmd = "${pkgs.bash}/bin/bash -c 'exec ${createFd} && ${cmd}'"; + +in + +{ + + ###### interface + + options = { + services.shellinabox = { + enable = mkEnableOption "shellinabox daemon"; + + user = mkOption { + type = types.str; + default = "root"; + description = '' + User to run shellinaboxd as. If started as root, the server drops + privileges by changing to nobody, unless overridden by the + <literal>--user</literal> option. + ''; + }; + + enableSSL = mkOption { + type = types.bool; + default = false; + description = '' + Whether or not to enable SSL (https) support. + ''; + }; + + certDirectory = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/certs"; + description = '' + The daemon will look in this directory far any certificates. + If the browser negotiated a Server Name Identification the daemon + will look for a matching certificate-SERVERNAME.pem file. If no SNI + handshake takes place, it will fall back on using the certificate in the + certificate.pem file. + + If no suitable certificate is installed, shellinaboxd will attempt to + create a new self-signed certificate. This will only succeed if, after + dropping privileges, shellinaboxd has write permissions for this + directory. + ''; + }; + + certFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/certificate.pem"; + description = "Path to server SSL certificate."; + }; + + extraOptions = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "--port=443" "--service /:LOGIN" ]; + description = '' + A list of strings to be appended to the command line arguments + for shellinaboxd. Please see the manual page + <link xlink:href="https://code.google.com/p/shellinabox/wiki/shellinaboxd_man"/> + for a full list of available arguments. + ''; + }; + + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + + assertions = + [ { assertion = cfg.enableSSL == true + -> cfg.certDirectory != null || cfg.certFile != null; + message = "SSL is enabled for shellinabox, but no certDirectory or certFile has been specefied."; } + { assertion = ! (cfg.certDirectory != null && cfg.certFile != null); + message = "Cannot set both certDirectory and certFile for shellinabox."; } + ]; + + systemd.services.shellinaboxd = { + description = "Shellinabox Web Server Daemon"; + + wantedBy = [ "multi-user.target" ]; + requires = [ "sshd.service" ]; + after = [ "sshd.service" ]; + + serviceConfig = { + Type = "forking"; + User = "${cfg.user}"; + ExecStart = "${if cfg.certFile == null then "${cmd}" else "${wrappedCmd}"}"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/tomcat.nix b/nixpkgs/nixos/modules/services/web-servers/tomcat.nix new file mode 100644 index 00000000000..68261c50324 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/tomcat.nix @@ -0,0 +1,425 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.tomcat; + tomcat = cfg.package; +in + +{ + + meta = { + maintainers = with maintainers; [ danbst ]; + }; + + ###### interface + + options = { + + services.tomcat = { + enable = mkEnableOption "Apache Tomcat"; + + package = mkOption { + type = types.package; + default = pkgs.tomcat85; + defaultText = "pkgs.tomcat85"; + example = lib.literalExample "pkgs.tomcat9"; + description = '' + Which tomcat package to use. + ''; + }; + + purifyOnStart = mkOption { + type = types.bool; + default = false; + description = '' + On startup, the `baseDir` directory is populated with various files, + subdirectories and symlinks. If this option is enabled, these items + (except for the `logs` and `work` subdirectories) are first removed. + This prevents interference from remainders of an old configuration + (libraries, webapps, etc.), so it's recommended to enable this option. + ''; + }; + + baseDir = mkOption { + type = lib.types.path; + default = "/var/tomcat"; + description = '' + Location where Tomcat stores configuration files, web applications + and logfiles. Note that it is partially cleared on each service startup + if `purifyOnStart` is enabled. + ''; + }; + + logDirs = mkOption { + default = []; + type = types.listOf types.path; + description = "Directories to create in baseDir/logs/"; + }; + + extraConfigFiles = mkOption { + default = []; + type = types.listOf types.path; + description = "Extra configuration files to pull into the tomcat conf directory"; + }; + + extraEnvironment = mkOption { + type = types.listOf types.str; + default = []; + example = [ "ENVIRONMENT=production" ]; + description = "Environment Variables to pass to the tomcat service"; + }; + + extraGroups = mkOption { + default = []; + example = [ "users" ]; + description = "Defines extra groups to which the tomcat user belongs."; + }; + + user = mkOption { + type = types.str; + default = "tomcat"; + description = "User account under which Apache Tomcat runs."; + }; + + group = mkOption { + type = types.str; + default = "tomcat"; + description = "Group account under which Apache Tomcat runs."; + }; + + javaOpts = mkOption { + type = types.either (types.listOf types.str) types.str; + default = ""; + description = "Parameters to pass to the Java Virtual Machine which spawns Apache Tomcat"; + }; + + catalinaOpts = mkOption { + type = types.either (types.listOf types.str) types.str; + default = ""; + description = "Parameters to pass to the Java Virtual Machine which spawns the Catalina servlet container"; + }; + + sharedLibs = mkOption { + type = types.listOf types.str; + default = []; + description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications"; + }; + + serverXml = mkOption { + type = types.lines; + default = ""; + description = " + Verbatim server.xml configuration. + This is mutually exclusive with the virtualHosts options. + "; + }; + + commonLibs = mkOption { + type = types.listOf types.str; + default = []; + description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications and the servlet container"; + }; + + webapps = mkOption { + type = types.listOf types.path; + default = [ tomcat.webapps ]; + defaultText = "[ pkgs.tomcat85.webapps ]"; + description = "List containing WAR files or directories with WAR files which are web applications to be deployed on Tomcat"; + }; + + virtualHosts = mkOption { + type = types.listOf (types.submodule { + options = { + name = mkOption { + type = types.str; + description = "name of the virtualhost"; + }; + aliases = mkOption { + type = types.listOf types.str; + description = "aliases of the virtualhost"; + default = []; + }; + webapps = mkOption { + type = types.listOf types.path; + description = '' + List containing web application WAR files and/or directories containing + web applications and configuration files for the virtual host. + ''; + default = []; + }; + }; + }); + default = []; + description = "List consisting of a virtual host name and a list of web applications to deploy on each virtual host"; + }; + + logPerVirtualHost = mkOption { + type = types.bool; + default = false; + description = "Whether to enable logging per virtual host."; + }; + + jdk = mkOption { + type = types.package; + default = pkgs.jdk; + defaultText = "pkgs.jdk"; + description = "Which JDK to use."; + }; + + axis2 = { + + enable = mkOption { + default = false; + type = types.bool; + description = "Whether to enable an Apache Axis2 container"; + }; + + services = mkOption { + default = []; + type = types.listOf types.str; + description = "List containing AAR files or directories with AAR files which are web services to be deployed on Axis2"; + }; + + }; + + }; + + }; + + + ###### implementation + + config = mkIf config.services.tomcat.enable { + + users.groups = singleton + { name = "tomcat"; + gid = config.ids.gids.tomcat; + }; + + users.users = singleton + { name = "tomcat"; + uid = config.ids.uids.tomcat; + description = "Tomcat user"; + home = "/homeless-shelter"; + extraGroups = cfg.extraGroups; + }; + + systemd.services.tomcat = { + description = "Apache Tomcat server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + preStart = '' + ${lib.optionalString cfg.purifyOnStart '' + # Delete most directories/symlinks we create from the existing base directory, + # to get rid of remainders of an old configuration. + # The list of directories to delete is taken from the "mkdir" command below, + # excluding "logs" (because logs are valuable) and "work" (because normally + # session files are there), and additionally including "bin". + rm -rf ${cfg.baseDir}/{conf,virtualhosts,temp,lib,shared/lib,webapps,bin} + ''} + + # Create the base directory + mkdir -p \ + ${cfg.baseDir}/{conf,virtualhosts,logs,temp,lib,shared/lib,webapps,work} + chown ${cfg.user}:${cfg.group} \ + ${cfg.baseDir}/{conf,virtualhosts,logs,temp,lib,shared/lib,webapps,work} + + # Create a symlink to the bin directory of the tomcat component + ln -sfn ${tomcat}/bin ${cfg.baseDir}/bin + + # Symlink the config files in the conf/ directory (except for catalina.properties and server.xml) + for i in $(ls ${tomcat}/conf | grep -v catalina.properties | grep -v server.xml); do + ln -sfn ${tomcat}/conf/$i ${cfg.baseDir}/conf/`basename $i` + done + + ${if cfg.extraConfigFiles != [] then '' + for i in ${toString cfg.extraConfigFiles}; do + ln -sfn $i ${cfg.baseDir}/conf/`basename $i` + done + '' else ""} + + # Create a modified catalina.properties file + # Change all references from CATALINA_HOME to CATALINA_BASE and add support for shared libraries + sed -e 's|''${catalina.home}|''${catalina.base}|g' \ + -e 's|shared.loader=|shared.loader=''${catalina.base}/shared/lib/*.jar|' \ + ${tomcat}/conf/catalina.properties > ${cfg.baseDir}/conf/catalina.properties + + ${if cfg.serverXml != "" then '' + cp -f ${pkgs.writeTextDir "server.xml" cfg.serverXml}/* ${cfg.baseDir}/conf/ + '' else + let + hostElementForVirtualHost = virtualHost: '' + <Host name="${virtualHost.name}" appBase="virtualhosts/${virtualHost.name}/webapps" + unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false"> + '' + concatStrings (innerElementsForVirtualHost virtualHost) + '' + </Host> + ''; + innerElementsForVirtualHost = virtualHost: + (map (alias: '' + <Alias>${alias}</Alias> + '') virtualHost.aliases) + ++ (optional cfg.logPerVirtualHost '' + <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs/${virtualHost.name}" + prefix="${virtualHost.name}_access_log." pattern="combined" resolveHosts="false"/> + ''); + hostElementsString = concatMapStringsSep "\n" hostElementForVirtualHost cfg.virtualHosts; + hostElementsSedString = replaceStrings ["\n"] ["\\\n"] hostElementsString; + in '' + # Create a modified server.xml which also includes all virtual hosts + sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\\"${escapeShellArg hostElementsSedString} \ + ${tomcat}/conf/server.xml > ${cfg.baseDir}/conf/server.xml + '' + } + ${optionalString (cfg.logDirs != []) '' + for i in ${toString cfg.logDirs}; do + mkdir -p ${cfg.baseDir}/logs/$i + chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/$i + done + ''} + ${optionalString cfg.logPerVirtualHost (toString (map (h: '' + mkdir -p ${cfg.baseDir}/logs/${h.name} + chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/${h.name} + '') cfg.virtualHosts))} + + # Symlink all the given common libs files or paths into the lib/ directory + for i in ${tomcat} ${toString cfg.commonLibs}; do + if [ -f $i ]; then + # If the given web application is a file, symlink it into the common/lib/ directory + ln -sfn $i ${cfg.baseDir}/lib/`basename $i` + elif [ -d $i ]; then + # If the given web application is a directory, then iterate over the files + # in the special purpose directories and symlink them into the tomcat tree + + for j in $i/lib/*; do + ln -sfn $j ${cfg.baseDir}/lib/`basename $j` + done + fi + done + + # Symlink all the given shared libs files or paths into the shared/lib/ directory + for i in ${toString cfg.sharedLibs}; do + if [ -f $i ]; then + # If the given web application is a file, symlink it into the common/lib/ directory + ln -sfn $i ${cfg.baseDir}/shared/lib/`basename $i` + elif [ -d $i ]; then + # If the given web application is a directory, then iterate over the files + # in the special purpose directories and symlink them into the tomcat tree + + for j in $i/shared/lib/*; do + ln -sfn $j ${cfg.baseDir}/shared/lib/`basename $j` + done + fi + done + + # Symlink all the given web applications files or paths into the webapps/ directory + for i in ${toString cfg.webapps}; do + if [ -f $i ]; then + # If the given web application is a file, symlink it into the webapps/ directory + ln -sfn $i ${cfg.baseDir}/webapps/`basename $i` + elif [ -d $i ]; then + # If the given web application is a directory, then iterate over the files + # in the special purpose directories and symlink them into the tomcat tree + + for j in $i/webapps/*; do + ln -sfn $j ${cfg.baseDir}/webapps/`basename $j` + done + + # Also symlink the configuration files if they are included + if [ -d $i/conf/Catalina ]; then + for j in $i/conf/Catalina/*; do + mkdir -p ${cfg.baseDir}/conf/Catalina/localhost + ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j` + done + fi + fi + done + + ${toString (map (virtualHost: '' + # Create webapps directory for the virtual host + mkdir -p ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps + + # Modify ownership + chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps + + # Symlink all the given web applications files or paths into the webapps/ directory + # of this virtual host + for i in "${if virtualHost ? webapps then toString virtualHost.webapps else ""}"; do + if [ -f $i ]; then + # If the given web application is a file, symlink it into the webapps/ directory + ln -sfn $i ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $i` + elif [ -d $i ]; then + # If the given web application is a directory, then iterate over the files + # in the special purpose directories and symlink them into the tomcat tree + + for j in $i/webapps/*; do + ln -sfn $j ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $j` + done + + # Also symlink the configuration files if they are included + if [ -d $i/conf/Catalina ]; then + for j in $i/conf/Catalina/*; do + mkdir -p ${cfg.baseDir}/conf/Catalina/${virtualHost.name} + ln -sfn $j ${cfg.baseDir}/conf/Catalina/${virtualHost.name}/`basename $j` + done + fi + fi + done + '') cfg.virtualHosts)} + + ${optionalString cfg.axis2.enable '' + # Copy the Axis2 web application + cp -av ${pkgs.axis2}/webapps/axis2 ${cfg.baseDir}/webapps + + # Turn off addressing, which causes many errors + sed -i -e 's%<module ref="addressing"/>%<!-- <module ref="addressing"/> -->%' ${cfg.baseDir}/webapps/axis2/WEB-INF/conf/axis2.xml + + # Modify permissions on the Axis2 application + chown -R ${cfg.user}:${cfg.group} ${cfg.baseDir}/webapps/axis2 + + # Symlink all the given web service files or paths into the webapps/axis2/WEB-INF/services directory + for i in ${toString cfg.axis2.services}; do + if [ -f $i ]; then + # If the given web service is a file, symlink it into the webapps/axis2/WEB-INF/services + ln -sfn $i ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $i` + elif [ -d $i ]; then + # If the given web application is a directory, then iterate over the files + # in the special purpose directories and symlink them into the tomcat tree + + for j in $i/webapps/axis2/WEB-INF/services/*; do + ln -sfn $j ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $j` + done + + # Also symlink the configuration files if they are included + if [ -d $i/conf/Catalina ]; then + for j in $i/conf/Catalina/*; do + ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j` + done + fi + fi + done + ''} + ''; + + serviceConfig = { + Type = "forking"; + PermissionsStartOnly = true; + PIDFile="/run/tomcat/tomcat.pid"; + RuntimeDirectory = "tomcat"; + User = cfg.user; + Environment=[ + "CATALINA_BASE=${cfg.baseDir}" + "CATALINA_PID=/run/tomcat/tomcat.pid" + "JAVA_HOME='${cfg.jdk}'" + "JAVA_OPTS='${builtins.toString cfg.javaOpts}'" + "CATALINA_OPTS='${builtins.toString cfg.catalinaOpts}'" + ] ++ cfg.extraEnvironment; + ExecStart = "${tomcat}/bin/startup.sh"; + ExecStop = "${tomcat}/bin/shutdown.sh"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/traefik.nix b/nixpkgs/nixos/modules/services/web-servers/traefik.nix new file mode 100644 index 00000000000..8de7df0d446 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/traefik.nix @@ -0,0 +1,124 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.traefik; + configFile = + if cfg.configFile == null then + pkgs.runCommand "config.toml" { + buildInputs = [ pkgs.remarshal ]; + preferLocalBuild = true; + } '' + remarshal -if json -of toml \ + < ${pkgs.writeText "config.json" (builtins.toJSON cfg.configOptions)} \ + > $out + '' + else cfg.configFile; + +in { + options.services.traefik = { + enable = mkEnableOption "Traefik web server"; + + configFile = mkOption { + default = null; + example = literalExample "/path/to/config.toml"; + type = types.nullOr types.path; + description = '' + Path to verbatim traefik.toml to use. + (Using that option has precedence over <literal>configOptions</literal>) + ''; + }; + + configOptions = mkOption { + description = '' + Config for Traefik. + ''; + type = types.attrs; + default = { + defaultEntryPoints = ["http"]; + entryPoints.http.address = ":80"; + }; + example = { + defaultEntrypoints = [ "http" ]; + web.address = ":8080"; + entryPoints.http.address = ":80"; + + file = {}; + frontends = { + frontend1 = { + backend = "backend1"; + routes.test_1.rule = "Host:localhost"; + }; + }; + backends.backend1 = { + servers.server1.url = "http://localhost:8000"; + }; + }; + }; + + dataDir = mkOption { + default = "/var/lib/traefik"; + type = types.path; + description = '' + Location for any persistent data traefik creates, ie. acme + ''; + }; + + group = mkOption { + default = "traefik"; + type = types.str; + example = "docker"; + description = '' + Set the group that traefik runs under. + For the docker backend this needs to be set to <literal>docker</literal> instead. + ''; + }; + + package = mkOption { + default = pkgs.traefik; + defaultText = "pkgs.traefik"; + type = types.package; + description = "Traefik package to use."; + }; + }; + + config = mkIf cfg.enable { + systemd.tmpfiles.rules = [ + "d '${cfg.dataDir}' 0700 traefik traefik - -" + ]; + + systemd.services.traefik = { + description = "Traefik web server"; + after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = ''${cfg.package.bin}/bin/traefik --configfile=${configFile}''; + Type = "simple"; + User = "traefik"; + Group = cfg.group; + Restart = "on-failure"; + StartLimitInterval = 86400; + StartLimitBurst = 5; + AmbientCapabilities = "cap_net_bind_service"; + CapabilityBoundingSet = "cap_net_bind_service"; + NoNewPrivileges = true; + LimitNPROC = 64; + LimitNOFILE = 1048576; + PrivateTmp = true; + PrivateDevices = true; + ProtectHome = true; + ProtectSystem = "full"; + ReadWriteDirectories = cfg.dataDir; + }; + }; + + users.users.traefik = { + group = "traefik"; + home = cfg.dataDir; + createHome = true; + }; + + users.groups.traefik = {}; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/unit/default.nix b/nixpkgs/nixos/modules/services/web-servers/unit/default.nix new file mode 100644 index 00000000000..a4a9d370d64 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/unit/default.nix @@ -0,0 +1,125 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.unit; + + configFile = pkgs.writeText "unit.json" cfg.config; + +in { + options = { + services.unit = { + enable = mkEnableOption "Unit App Server"; + package = mkOption { + type = types.package; + default = pkgs.unit; + defaultText = "pkgs.unit"; + description = "Unit package to use."; + }; + user = mkOption { + type = types.str; + default = "unit"; + description = "User account under which unit runs."; + }; + group = mkOption { + type = types.str; + default = "unit"; + description = "Group account under which unit runs."; + }; + stateDir = mkOption { + default = "/var/spool/unit"; + description = "Unit data directory."; + }; + logDir = mkOption { + default = "/var/log/unit"; + description = "Unit log directory."; + }; + config = mkOption { + type = types.str; + default = '' + { + "listeners": {}, + "applications": {} + } + ''; + example = literalExample '' + { + "listeners": { + "*:8300": { + "application": "example-php-72" + } + }, + "applications": { + "example-php-72": { + "type": "php 7.2", + "processes": 4, + "user": "nginx", + "group": "nginx", + "root": "/var/www", + "index": "index.php", + "options": { + "file": "/etc/php.d/default.ini", + "admin": { + "max_execution_time": "30", + "max_input_time": "30", + "display_errors": "off", + "display_startup_errors": "off", + "open_basedir": "/dev/urandom:/proc/cpuinfo:/proc/meminfo:/etc/ssl/certs:/var/www", + "disable_functions": "exec,passthru,shell_exec,system" + } + } + } + } + } + ''; + description = "Unit configuration in JSON format. More details here https://unit.nginx.org/configuration"; + }; + }; + }; + + config = mkIf cfg.enable { + + environment.systemPackages = [ cfg.package ]; + + systemd.tmpfiles.rules = [ + "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.logDir}' 0750 ${cfg.user} ${cfg.group} - -" + ]; + + systemd.services.unit = { + description = "Unit App Server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ curl ]; + preStart = '' + test -f '/run/unit/control.unit.sock' || rm -f '/run/unit/control.unit.sock' + ''; + postStart = '' + curl -X PUT --data-binary '@${configFile}' --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config' + ''; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID"; + CapabilityBoundingSet = "CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID"; + ExecStart = '' + ${cfg.package}/bin/unitd --control 'unix:/run/unit/control.unit.sock' --pid '/run/unit/unit.pid' \ + --log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' --no-daemon \ + --user ${cfg.user} --group ${cfg.group} + ''; + RuntimeDirectory = "unit"; + RuntimeDirectoryMode = "0750"; + }; + }; + + users.users = optionalAttrs (cfg.user == "unit") (singleton { + name = "unit"; + group = cfg.group; + }); + + users.groups = optionalAttrs (cfg.group == "unit") (singleton { + name = "unit"; + }); + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix b/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix new file mode 100644 index 00000000000..af70f32f32d --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix @@ -0,0 +1,160 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.uwsgi; + + uwsgi = pkgs.uwsgi.override { + plugins = cfg.plugins; + }; + + buildCfg = name: c: + let + plugins = + if any (n: !any (m: m == n) cfg.plugins) (c.plugins or []) + then throw "`plugins` attribute in UWSGI configuration contains plugins not in config.services.uwsgi.plugins" + else c.plugins or cfg.plugins; + + hasPython = v: filter (n: n == "python${v}") plugins != []; + hasPython2 = hasPython "2"; + hasPython3 = hasPython "3"; + + python = + if hasPython2 && hasPython3 then + throw "`plugins` attribute in UWSGI configuration shouldn't contain both python2 and python3" + else if hasPython2 then uwsgi.python2 + else if hasPython3 then uwsgi.python3 + else null; + + pythonEnv = python.withPackages (c.pythonPackages or (self: [])); + + uwsgiCfg = { + uwsgi = + if c.type == "normal" + then { + inherit plugins; + } // removeAttrs c [ "type" "pythonPackages" ] + // optionalAttrs (python != null) { + pythonpath = "${pythonEnv}/${python.sitePackages}"; + env = + # Argh, uwsgi expects list of key-values there instead of a dictionary. + let env' = c.env or []; + getPath = + x: if hasPrefix "PATH=" x + then substring (stringLength "PATH=") (stringLength x) x + else null; + oldPaths = filter (x: x != null) (map getPath env'); + in env' ++ [ "PATH=${optionalString (oldPaths != []) "${last oldPaths}:"}${pythonEnv}/bin" ]; + } + else if c.type == "emperor" + then { + emperor = if builtins.typeOf c.vassals != "set" then c.vassals + else pkgs.buildEnv { + name = "vassals"; + paths = mapAttrsToList buildCfg c.vassals; + }; + } // removeAttrs c [ "type" "vassals" ] + else throw "`type` attribute in UWSGI configuration should be either 'normal' or 'emperor'"; + }; + + in pkgs.writeTextDir "${name}.json" (builtins.toJSON uwsgiCfg); + +in { + + options = { + services.uwsgi = { + + enable = mkOption { + type = types.bool; + default = false; + description = "Enable uWSGI"; + }; + + runDir = mkOption { + type = types.path; + default = "/run/uwsgi"; + description = "Where uWSGI communication sockets can live"; + }; + + instance = mkOption { + type = types.attrs; + default = { + type = "normal"; + }; + example = literalExample '' + { + type = "emperor"; + vassals = { + moin = { + type = "normal"; + pythonPackages = self: with self; [ moinmoin ]; + socket = "${config.services.uwsgi.runDir}/uwsgi.sock"; + }; + }; + } + ''; + description = '' + uWSGI configuration. It awaits an attribute <literal>type</literal> inside which can be either + <literal>normal</literal> or <literal>emperor</literal>. + + For <literal>normal</literal> mode you can specify <literal>pythonPackages</literal> as a function + from libraries set into a list of libraries. <literal>pythonpath</literal> will be set accordingly. + + For <literal>emperor</literal> mode, you should use <literal>vassals</literal> attribute + which should be either a set of names and configurations or a path to a directory. + + Other attributes will be used in configuration file as-is. Notice that you can redefine + <literal>plugins</literal> setting here. + ''; + }; + + plugins = mkOption { + type = types.listOf types.str; + default = []; + description = "Plugins used with uWSGI"; + }; + + user = mkOption { + type = types.str; + default = "uwsgi"; + description = "User account under which uwsgi runs."; + }; + + group = mkOption { + type = types.str; + default = "uwsgi"; + description = "Group account under which uwsgi runs."; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.uwsgi = { + wantedBy = [ "multi-user.target" ]; + preStart = '' + mkdir -p ${cfg.runDir} + chown ${cfg.user}:${cfg.group} ${cfg.runDir} + ''; + serviceConfig = { + Type = "notify"; + ExecStart = "${uwsgi}/bin/uwsgi --uid ${cfg.user} --gid ${cfg.group} --json ${buildCfg "server" cfg.instance}/server.json"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID"; + NotifyAccess = "main"; + KillSignal = "SIGQUIT"; + }; + }; + + users.users = optionalAttrs (cfg.user == "uwsgi") (singleton + { name = "uwsgi"; + group = cfg.group; + uid = config.ids.uids.uwsgi; + }); + + users.groups = optionalAttrs (cfg.group == "uwsgi") (singleton + { name = "uwsgi"; + gid = config.ids.gids.uwsgi; + }); + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix b/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix new file mode 100644 index 00000000000..63f967185c2 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix @@ -0,0 +1,113 @@ +{ config, lib, pkgs, ...}: + +with lib; + +let + cfg = config.services.varnish; + + commandLine = "-f ${pkgs.writeText "default.vcl" cfg.config}" + + optionalString (cfg.extraModules != []) " -p vmod_path='${makeSearchPathOutput "lib" "lib/varnish/vmods" ([cfg.package] ++ cfg.extraModules)}' -r vmod_path"; +in +{ + options = { + services.varnish = { + enable = mkEnableOption "Varnish Server"; + + package = mkOption { + type = types.package; + default = pkgs.varnish5; + defaultText = "pkgs.varnish5"; + description = '' + The package to use + ''; + }; + + http_address = mkOption { + type = types.str; + default = "*:6081"; + description = " + HTTP listen address and port. + "; + }; + + config = mkOption { + type = types.lines; + description = " + Verbatim default.vcl configuration. + "; + }; + + stateDir = mkOption { + type = types.path; + default = "/var/spool/varnish/${config.networking.hostName}"; + description = " + Directory holding all state for Varnish to run. + "; + }; + + extraModules = mkOption { + type = types.listOf types.package; + default = []; + example = literalExample "[ pkgs.varnish5Packages.geoip ]"; + description = " + Varnish modules (except 'std'). + "; + }; + + extraCommandLine = mkOption { + type = types.str; + default = ""; + example = "-s malloc,256M"; + description = " + Command line switches for varnishd (run 'varnishd -?' to get list of options) + "; + }; + }; + + }; + + config = mkIf cfg.enable { + + systemd.services.varnish = { + description = "Varnish"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + preStart = '' + mkdir -p ${cfg.stateDir} + chown -R varnish:varnish ${cfg.stateDir} + ''; + postStop = '' + rm -rf ${cfg.stateDir} + ''; + serviceConfig = { + Type = "simple"; + PermissionsStartOnly = true; + ExecStart = "${cfg.package}/sbin/varnishd -a ${cfg.http_address} -n ${cfg.stateDir} -F ${cfg.extraCommandLine} ${commandLine}"; + Restart = "always"; + RestartSec = "5s"; + User = "varnish"; + Group = "varnish"; + AmbientCapabilities = "cap_net_bind_service"; + NoNewPrivileges = true; + LimitNOFILE = 131072; + }; + }; + + environment.systemPackages = [ cfg.package ]; + + # check .vcl syntax at compile time (e.g. before nixops deployment) + system.extraDependencies = [ + (pkgs.stdenv.mkDerivation { + name = "check-varnish-syntax"; + buildCommand = "${cfg.package}/sbin/varnishd -C ${commandLine} 2> $out || (cat $out; exit 1)"; + }) + ]; + + users.users.varnish = { + group = "varnish"; + uid = config.ids.uids.varnish; + }; + + users.groups.varnish.gid = config.ids.uids.varnish; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-servers/zope2.nix b/nixpkgs/nixos/modules/services/web-servers/zope2.nix new file mode 100644 index 00000000000..3abd506827c --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/zope2.nix @@ -0,0 +1,258 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.zope2; + + zope2Opts = { name, ... }: { + options = { + + name = mkOption { + default = "${name}"; + type = types.str; + description = "The name of the zope2 instance. If undefined, the name of the attribute set will be used."; + }; + + threads = mkOption { + default = 2; + type = types.int; + description = "Specify the number of threads that Zope's ZServer web server will use to service requests. "; + }; + + http_address = mkOption { + default = "localhost:8080"; + type = types.str; + description = "Give a port and address for the HTTP server."; + }; + + user = mkOption { + default = "zope2"; + type = types.str; + description = "The name of the effective user for the Zope process."; + }; + + clientHome = mkOption { + default = "/var/lib/zope2/${name}"; + type = types.path; + description = "Home directory of zope2 instance."; + }; + extra = mkOption { + default = + '' + <zodb_db main> + mount-point / + cache-size 30000 + <blobstorage> + blob-dir /var/lib/zope2/${name}/blobstorage + <filestorage> + path /var/lib/zope2/${name}/filestorage/Data.fs + </filestorage> + </blobstorage> + </zodb_db> + ''; + type = types.lines; + description = "Extra zope.conf"; + }; + + packages = mkOption { + type = types.listOf types.package; + description = "The list of packages you want to make available to the zope2 instance."; + }; + + }; + }; + +in + +{ + + ###### interface + + options = { + + services.zope2.instances = mkOption { + default = {}; + type = with types; attrsOf (submodule zope2Opts); + example = literalExample '' + { + plone01 = { + http_address = "127.0.0.1:8080"; + extra = + ''' + <zodb_db main> + mount-point / + cache-size 30000 + <blobstorage> + blob-dir /var/lib/zope2/plone01/blobstorage + <filestorage> + path /var/lib/zope2/plone01/filestorage/Data.fs + </filestorage> + </blobstorage> + </zodb_db> + '''; + }; + } + ''; + description = "zope2 instances to be created automaticaly by the system."; + }; + }; + + ###### implementation + + config = mkIf (cfg.instances != {}) { + + users.users.zope2.uid = config.ids.uids.zope2; + + systemd.services = + let + + createZope2Instance = opts: name: + let + interpreter = pkgs.writeScript "interpreter" + '' + import sys + + _interactive = True + if len(sys.argv) > 1: + _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:') + _interactive = False + for (_opt, _val) in _options: + if _opt == '-i': + _interactive = True + elif _opt == '-c': + exec _val + elif _opt == '-m': + sys.argv[1:] = _args + _args = [] + __import__("runpy").run_module( + _val, {}, "__main__", alter_sys=True) + + if _args: + sys.argv[:] = _args + __file__ = _args[0] + del _options, _args + execfile(__file__) + + if _interactive: + del _interactive + __import__("code").interact(banner="", local=globals()) + ''; + env = pkgs.buildEnv { + name = "zope2-${name}-env"; + paths = [ + pkgs.python27 + pkgs.python27Packages.recursivePthLoader + pkgs.python27Packages."plone.recipe.zope2instance" + ] ++ attrValues pkgs.python27.modules + ++ opts.packages; + postBuild = + '' + echo "#!$out/bin/python" > $out/bin/interpreter + cat ${interpreter} >> $out/bin/interpreter + ''; + }; + conf = pkgs.writeText "zope2-${name}-conf" + '' + %define INSTANCEHOME ${env} + instancehome $INSTANCEHOME + %define CLIENTHOME ${opts.clientHome}/${opts.name} + clienthome $CLIENTHOME + + debug-mode off + security-policy-implementation C + verbose-security off + default-zpublisher-encoding utf-8 + zserver-threads ${toString opts.threads} + effective-user ${opts.user} + + pid-filename ${opts.clientHome}/${opts.name}/pid + lock-filename ${opts.clientHome}/${opts.name}/lock + python-check-interval 1000 + enable-product-installation off + + <environment> + zope_i18n_compile_mo_files false + </environment> + + <eventlog> + level INFO + <logfile> + path /var/log/zope2/${name}.log + level INFO + </logfile> + </eventlog> + + <logger access> + level WARN + <logfile> + path /var/log/zope2/${name}-Z2.log + format %(message)s + </logfile> + </logger> + + <http-server> + address ${opts.http_address} + </http-server> + + <zodb_db temporary> + <temporarystorage> + name temporary storage for sessioning + </temporarystorage> + mount-point /temp_folder + container-class Products.TemporaryFolder.TemporaryContainer + </zodb_db> + + ${opts.extra} + ''; + ctlScript = pkgs.writeScript "zope2-${name}-ctl-script" + '' + #!${env}/bin/python + + import sys + import plone.recipe.zope2instance.ctl + + if __name__ == '__main__': + sys.exit(plone.recipe.zope2instance.ctl.main( + ["-C", "${conf}"] + + sys.argv[1:])) + ''; + + ctl = pkgs.writeScript "zope2-${name}-ctl" + '' + #!${pkgs.bash}/bin/bash -e + export PYTHONHOME=${env} + exec ${ctlScript} "$@" + ''; + in { + #description = "${name} instance"; + after = [ "network.target" ]; # with RelStorage also add "postgresql.service" + wantedBy = [ "multi-user.target" ]; + path = opts.packages; + preStart = + '' + mkdir -p /var/log/zope2/ + touch /var/log/zope2/${name}.log + touch /var/log/zope2/${name}-Z2.log + chown ${opts.user} /var/log/zope2/${name}.log + chown ${opts.user} /var/log/zope2/${name}-Z2.log + + mkdir -p ${opts.clientHome}/filestorage ${opts.clientHome}/blobstorage + mkdir -p ${opts.clientHome}/${opts.name} + chown ${opts.user} ${opts.clientHome} -R + + ${ctl} adduser admin admin + ''; + + serviceConfig.Type = "forking"; + serviceConfig.ExecStart = "${ctl} start"; + serviceConfig.ExecStop = "${ctl} stop"; + serviceConfig.ExecReload = "${ctl} restart"; + }; + + in listToAttrs (map (name: { name = "zope2-${name}"; value = createZope2Instance (builtins.getAttr name cfg.instances) name; }) (builtins.attrNames cfg.instances)); + + }; + +} |