Merge branch 'main' into feat/headscale

This commit is contained in:
ashie 2026-03-19 20:18:10 +00:00
commit 9803eebb1d
2 changed files with 65 additions and 2 deletions

View file

@ -102,11 +102,19 @@
myModules.nginx = { myModules.nginx = {
enable = true; enable = true;
email = "info@ashisgreat.xyz"; email = "info@ashisgreat.xyz";
rateLimit = {
enable = true;
requests = 10;
burst = 20;
};
domains = { domains = {
"search.ashisgreat.xyz" = { "search.ashisgreat.xyz" = {
port = 8888; port = 8888;
# SearXNG sets its own CSP in settings.yml — omit at Nginx level to avoid conflicts # SearXNG sets its own CSP in settings.yml — omit at Nginx level to avoid conflicts
contentSecurityPolicy = null; contentSecurityPolicy = null;
# Search engine — slightly more permissive for bot traffic
rateLimit.requests = 20;
rateLimit.burst = 40;
}; };
}; };
}; };

View file

@ -1,5 +1,5 @@
# Nginx Reverse Proxy Module # Nginx Reverse Proxy Module
# Provides: Nginx with automatic Let's Encrypt certificates and security headers # Provides: Nginx with automatic Let's Encrypt certificates, security headers, and rate limiting
# #
# Usage: # Usage:
# myModules.nginx = { # myModules.nginx = {
@ -32,6 +32,28 @@ in
description = "Email address for Let's Encrypt registration"; description = "Email address for Let's Encrypt registration";
}; };
rateLimit = {
enable = lib.mkEnableOption "Nginx rate limiting";
zone = lib.mkOption {
type = lib.types.str;
default = "10m";
description = "Size of the shared memory zone for rate limiting";
};
requests = lib.mkOption {
type = lib.types.int;
default = 10;
description = "Number of requests allowed per second (burst applies on top)";
};
burst = lib.mkOption {
type = lib.types.int;
default = 20;
description = "Maximum burst of requests allowed beyond the rate";
};
};
domains = lib.mkOption { domains = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule { type = lib.types.attrsOf (lib.types.submodule {
options = { options = {
@ -52,6 +74,26 @@ in
description = "Content-Security-Policy header value. Set to null to omit."; description = "Content-Security-Policy header value. Set to null to omit.";
}; };
rateLimit = {
enable = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
description = "Enable rate limiting for this vhost. Defaults to global rateLimit.enable.";
};
requests = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = "Requests per second for this vhost. Defaults to global rateLimit.requests.";
};
burst = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = "Burst size for this vhost. Defaults to global rateLimit.burst.";
};
};
extraLocations = lib.mkOption { extraLocations = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule { type = lib.types.attrsOf (lib.types.submodule {
options = { options = {
@ -98,6 +140,14 @@ in
recommendedProxySettings = true; recommendedProxySettings = true;
recommendedTlsSettings = true; recommendedTlsSettings = true;
# Rate limiting zones (one per domain for per-domain limits)
commonHttpConfig = lib.optionalString cfg.rateLimit.enable ''
# Global rate limiting zone
limit_req_zone $binary_remote_addr zone=global:10m rate=${toString cfg.rateLimit.requests}r/s;
# Limit connection flooding
limit_conn_zone $binary_remote_addr zone=connlimit:10m;
'';
virtualHosts = lib.mapAttrs' (domain: opts: { virtualHosts = lib.mapAttrs' (domain: opts: {
name = domain; name = domain;
value = { value = {
@ -127,7 +177,12 @@ in
locations = { locations = {
"/" = { "/" = {
proxyPass = "http://127.0.0.1:${toString opts.port}"; proxyPass = "http://127.0.0.1:${toString opts.port}";
extraConfig = opts.extraConfig; extraConfig = opts.extraConfig + lib.optionalString (if opts.rateLimit.enable != null then opts.rateLimit.enable else cfg.rateLimit.enable) ''
# Rate limiting
limit_req zone=global burst=${toString (if opts.rateLimit.burst != null then opts.rateLimit.burst else cfg.rateLimit.burst)} nodelay;
limit_conn connlimit 30;
limit_req_status 429;
'';
}; };
} // lib.mapAttrs' (locPath: locOpts: { } // lib.mapAttrs' (locPath: locOpts: {
name = locPath; name = locPath;