aboutsummaryrefslogtreecommitdiff
path: root/infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx
diff options
context:
space:
mode:
authorMx Kookie <kookie@spacekookie.de>2020-10-31 19:35:09 +0100
committerMx Kookie <kookie@spacekookie.de>2020-10-31 19:35:09 +0100
commitc4625b175f8200f643fd6e11010932ea44c78433 (patch)
treebce3f89888c8ac3991fa5569a878a9eab6801ccc /infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx
parent49f735974dd103039ddc4cb576bb76555164a9e7 (diff)
parentd661aa56a8843e991261510c1bb28fdc2f6975ae (diff)
Add 'infra/libkookie/' from commit 'd661aa56a8843e991261510c1bb28fdc2f6975ae'
git-subtree-dir: infra/libkookie git-subtree-mainline: 49f735974dd103039ddc4cb576bb76555164a9e7 git-subtree-split: d661aa56a8843e991261510c1bb28fdc2f6975ae
Diffstat (limited to 'infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx')
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix795
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix94
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix94
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix229
4 files changed, 1212 insertions, 0 deletions
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix b/infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
new file mode 100644
index 000000000000..39bcb14e5afe
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
@@ -0,0 +1,795 @@
+{ 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;
+ dependentCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts);
+ virtualHosts = mapAttrs (vhostName: vhostConfig:
+ let
+ serverName = if vhostConfig.serverName != null
+ then vhostConfig.serverName
+ else vhostName;
+ certName = if vhostConfig.useACMEHost != null
+ then vhostConfig.useACMEHost
+ else serverName;
+ in
+ vhostConfig // {
+ inherit serverName certName;
+ } // (optionalAttrs (vhostConfig.enableACME || vhostConfig.useACMEHost != null) {
+ sslCertificate = "${certs.${certName}.directory}/fullchain.pem";
+ sslCertificateKey = "${certs.${certName}.directory}/key.pem";
+ sslTrustedCertificate = "${certs.${certName}.directory}/chain.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}
+ }
+ ''));
+
+ commonHttpConfig = ''
+ # The mime type definitions included with nginx are very incomplete, so
+ # we use a list of mime types from the mailcap package, which is also
+ # used by most other Linux distributions by default.
+ include ${pkgs.mailcap}/etc/nginx/mime.types;
+ include ${cfg.package}/conf/fastcgi.conf;
+ include ${cfg.package}/conf/uwsgi_params;
+ '';
+
+ configFile = pkgs.writers.writeNginxConfig "nginx.conf" ''
+ pid /run/nginx/nginx.pid;
+ error_log ${cfg.logError};
+ daemon off;
+
+ ${cfg.config}
+
+ ${optionalString (cfg.eventsConfig != "" || cfg.config == "") ''
+ events {
+ ${cfg.eventsConfig}
+ }
+ ''}
+
+ ${optionalString (cfg.httpConfig == "" && cfg.config == "") ''
+ http {
+ ${commonHttpConfig}
+
+ ${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 4096;
+ ''}
+
+ ssl_protocols ${cfg.sslProtocols};
+ ssl_ciphers ${cfg.sslCiphers};
+ ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"}
+
+ ${optionalString (cfg.recommendedTlsSettings) ''
+ # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate
+
+ ssl_session_timeout 1d;
+ ssl_session_cache shared:SSL:10m;
+ # Breaks forward secrecy: https://github.com/mozilla/server-side-tls/issues/135
+ ssl_session_tickets off;
+ # We don't enable insecure ciphers by default, so this allows
+ # clients to pick the most performant, per https://github.com/mozilla/server-side-tls/issues/260
+ ssl_prefer_server_ciphers off;
+
+ # OCSP stapling
+ 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};
+ ''}
+
+ ${optionalString (cfg.mapHashBucketSize != null) ''
+ map_hash_bucket_size ${toString cfg.mapHashBucketSize};
+ ''}
+
+ ${optionalString (cfg.mapHashMaxSize != null) ''
+ map_hash_max_size ${toString cfg.mapHashMaxSize};
+ ''}
+
+ # $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 {
+ ${commonHttpConfig}
+ ${cfg.httpConfig}
+ }''}
+
+ ${cfg.appendConfig}
+ '';
+
+ configPath = if cfg.enableReload
+ then "/etc/nginx/nginx.conf"
+ else configFile;
+
+ execCommand = "${cfg.package}/bin/nginx -c '${configPath}'";
+
+ 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 = "";
+ 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>.
+ '';
+ };
+
+ 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;
+ # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate
+ default = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
+ 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.
+ '';
+ };
+
+ mapHashBucketSize = mkOption {
+ type = types.nullOr (types.enum [ 32 64 128 ]);
+ default = null;
+ description = ''
+ Sets the bucket size for the map variables hash tables. Default
+ value depends on the processor’s cache line size.
+ '';
+ };
+
+ mapHashMaxSize = mkOption {
+ type = types.nullOr types.ints.positive;
+ default = null;
+ description = ''
+ Sets the maximum size of the map variables hash tables.
+ '';
+ };
+
+ 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";
+ };
+ };
+ };
+
+ imports = [
+ (mkRemovedOptionModule [ "services" "nginx" "stateDir" ] ''
+ The Nginx log directory has been moved to /var/log/nginx, the cache directory
+ to /var/cache/nginx. The option services.nginx.stateDir has been removed.
+ '')
+ ];
+
+ 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 (certName: [ "acme-finished-${certName}.target" ]) dependentCertNames);
+ after = [ "network.target" ] ++ map (certName: "acme-selfsigned-${certName}.service") dependentCertNames;
+ # Nginx needs to be started in order to be able to request certificates
+ # (it's hosting the acme challenge after all)
+ # This fixes https://github.com/NixOS/nixpkgs/issues/81842
+ before = map (certName: "acme-${certName}.service") dependentCertNames;
+ stopIfChanged = false;
+ preStart = ''
+ ${cfg.preStart}
+ ${execCommand} -t
+ '';
+ serviceConfig = {
+ ExecStart = execCommand;
+ ExecReload = [
+ "${execCommand} -t"
+ "${pkgs.coreutils}/bin/kill -HUP $MAINPID"
+ ];
+ Restart = "always";
+ RestartSec = "10s";
+ StartLimitInterval = "1min";
+ # User and group
+ User = cfg.user;
+ Group = cfg.group;
+ # Runtime directory and mode
+ RuntimeDirectory = "nginx";
+ RuntimeDirectoryMode = "0750";
+ # Cache directory and mode
+ CacheDirectory = "nginx";
+ CacheDirectoryMode = "0750";
+ # Logs directory and mode
+ LogsDirectory = "nginx";
+ LogsDirectoryMode = "0750";
+ # Capabilities
+ AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ];
+ CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ];
+ # Security
+ NoNewPrivileges = true;
+ # Sandboxing
+ ProtectSystem = "strict";
+ ProtectHome = mkDefault true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ProtectHostname = true;
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectControlGroups = true;
+ RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+ LockPersonality = true;
+ MemoryDenyWriteExecute = !(builtins.any (mod: (mod.allowMemoryWriteExecute or false)) pkgs.nginx.modules);
+ RestrictRealtime = true;
+ RestrictSUIDSGID = true;
+ PrivateMounts = true;
+ # System Call Filtering
+ SystemCallArchitectures = "native";
+ };
+ };
+
+ environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload {
+ source = configFile;
+ };
+
+ # postRun hooks on cert renew can't be used to restart Nginx since renewal
+ # runs as the unprivileged acme user. sslTargets are added to wantedBy + before
+ # which allows the acme-finished-$cert.target to signify the successful updating
+ # of certs end-to-end.
+ systemd.services.nginx-config-reload = let
+ sslServices = map (certName: "acme-${certName}.service") dependentCertNames;
+ sslTargets = map (certName: "acme-finished-${certName}.target") dependentCertNames;
+ in mkIf (cfg.enableReload || sslServices != []) {
+ wants = optionals (cfg.enableReload) [ "nginx.service" ];
+ wantedBy = sslServices ++ [ "multi-user.target" ];
+ # Before the finished targets, after the renew services.
+ # This service might be needed for HTTP-01 challenges, but we only want to confirm
+ # certs are updated _after_ config has been reloaded.
+ before = sslTargets;
+ after = sslServices;
+ restartTriggers = optionals (cfg.enableReload) [ configFile ];
+ # Block reloading if not all certs exist yet.
+ # Happens when config changes add new vhosts/certs.
+ unitConfig.ConditionPathExists = optionals (sslServices != []) (map (certName: certs.${certName}.directory + "/fullchain.pem") dependentCertNames);
+ serviceConfig = {
+ Type = "oneshot";
+ TimeoutSec = 60;
+ ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active nginx.service";
+ ExecStart = "/run/current-system/systemd/bin/systemctl reload nginx.service";
+ };
+ };
+
+ security.acme.certs = let
+ acmePairs = map (vhostConfig: nameValuePair vhostConfig.serverName {
+ group = mkDefault cfg.group;
+ webroot = vhostConfig.acmeRoot;
+ extraDomainNames = vhostConfig.serverAliases;
+ # Filter for enableACME-only vhosts. Don't want to create dud certs
+ }) (filter (vhostConfig: vhostConfig.useACMEHost == null) acmeEnabledVhosts);
+ in listToAttrs acmePairs;
+
+ users.users = optionalAttrs (cfg.user == "nginx") {
+ nginx = {
+ group = cfg.group;
+ uid = config.ids.uids.nginx;
+ };
+ };
+
+ users.groups = optionalAttrs (cfg.group == "nginx") {
+ nginx.gid = config.ids.gids.nginx;
+ };
+
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix b/infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix
new file mode 100644
index 000000000000..f7fb07bb7975
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix
@@ -0,0 +1,94 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.nginx.gitweb;
+ gitwebConfig = config.services.gitweb;
+ package = pkgs.gitweb.override (optionalAttrs gitwebConfig.gitwebTheme {
+ gitwebTheme = true;
+ });
+
+in
+{
+
+ options.services.nginx.gitweb = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If true, enable gitweb in nginx.
+ '';
+ };
+
+ location = mkOption {
+ default = "/gitweb";
+ type = types.str;
+ description = ''
+ Location to serve gitweb on.
+ '';
+ };
+
+ user = mkOption {
+ default = "nginx";
+ type = types.str;
+ description = ''
+ Existing user that the CGI process will belong to. (Default almost surely will do.)
+ '';
+ };
+
+ group = mkOption {
+ default = "nginx";
+ type = types.str;
+ description = ''
+ Group that the CGI process will belong to. (Set to <literal>config.services.gitolite.group</literal> if you are using gitolite.)
+ '';
+ };
+
+ virtualHost = mkOption {
+ default = "_";
+ type = types.str;
+ description = ''
+ VirtualHost to serve gitweb on. Default is catch-all.
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.gitweb = {
+ description = "GitWeb service";
+ script = "${package}/gitweb.cgi --fastcgi --nproc=1";
+ environment = {
+ FCGI_SOCKET_PATH = "/run/gitweb/gitweb.sock";
+ };
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ RuntimeDirectory = [ "gitweb" ];
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ services.nginx = {
+ virtualHosts.${cfg.virtualHost} = {
+ locations."${cfg.location}/static/" = {
+ alias = "${package}/static/";
+ };
+ locations."${cfg.location}/" = {
+ extraConfig = ''
+ include ${pkgs.nginx}/conf/fastcgi_params;
+ fastcgi_param GITWEB_CONFIG ${gitwebConfig.gitwebConfigFile};
+ fastcgi_pass unix:/run/gitweb/gitweb.sock;
+ '';
+ };
+ };
+ };
+
+ };
+
+ meta.maintainers = with maintainers; [ gnidorah ];
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix b/infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
new file mode 100644
index 000000000000..3d9e391ecf20
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -0,0 +1,94 @@
+# This file defines the options that can be used both for the Nginx
+# 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/infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix b/infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
new file mode 100644
index 000000000000..455854e2a965
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -0,0 +1,229 @@
+# This file defines the options that can be used both for the Nginx
+# 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.
+ Can be created via: <command>htpasswd -c &lt;filename&gt; &lt;username&gt;</command>
+ '';
+ };
+
+ locations = mkOption {
+ type = types.attrsOf (types.submodule (import ./location-options.nix {
+ inherit lib;
+ }));
+ default = {};
+ example = literalExample ''
+ {
+ "/" = {
+ proxyPass = "http://localhost:3000";
+ };
+ };
+ '';
+ description = "Declarative location config";
+ };
+ };
+}