{ pkgs, lib, config, ... }: with lib; let cfg = config.modules.system.nginx; domain = "ramos.codes"; privateAccessRules = concatMapStringsSep "\n" (cidr: "allow ${cidr};") cfg.privateAllowCidrs + "\ndeny all;"; in { options.modules.system.nginx = { enable = mkEnableOption "Nginx Reverse Proxy"; privateAllowCidrs = mkOption { type = types.listOf types.str; default = [ "192.168.0.0/24" "10.8.0.0/24" ]; description = '' CIDR ranges allowed to access private vhosts (LAN + WireGuard). ''; }; searxng.enable = mkEnableOption "Publicly exposed SearXNG endpoint with secret path via sops"; }; config = mkIf cfg.enable { networking.firewall.allowedTCPPorts = [ 80 443 ]; systemd.services.nginx.serviceConfig.LimitNOFILE = 65536; environment.etc."fail2ban/filter.d/nginx-404.conf".text = '' [Definition] failregex = ^ - .+ "(GET|POST|HEAD|PUT|DELETE|PATCH) .+ HTTP/[0-9.]+" 404 ignoreregex = ''; environment.etc."fail2ban/filter.d/nginx-401.conf".text = '' [Definition] failregex = ^ - .+ "(GET|POST|HEAD|PUT|DELETE|PATCH) .+ HTTP/[0-9.]+" 401 ignoreregex = ''; services.fail2ban.jails.nginx-404 = '' enabled = true filter = nginx-404 logpath = /var/log/nginx/access.log maxretry = 10 findtime = 10m bantime = 24h ''; services.fail2ban.jails.nginx-401 = '' enabled = true filter = nginx-401 logpath = /var/log/nginx/access.log maxretry = 5 findtime = 10m bantime = 24h ''; security.acme = { acceptTerms = true; defaults.email = config.user.email; certs."${domain}" = { domain = "*.${domain}"; dnsProvider = "namecheap"; environmentFile = "/var/lib/acme/namecheap.env"; group = "nginx"; }; }; services.nginx = { enable = true; recommendedTlsSettings = true; recommendedOptimisation = true; recommendedGzipSettings = true; eventsConfig = "worker_connections 4096;"; # Catch-all default - friendly error for unknown subdomains virtualHosts."_" = { default = true; useACMEHost = domain; forceSSL = true; locations."/" = { return = "404 'Not Found: This subdomain does not exist.'"; extraConfig = '' add_header Content-Type text/plain; ''; }; }; virtualHosts."test.${domain}" = { useACMEHost = domain; forceSSL = true; locations."/" = { return = "200 'nginx is working'"; extraConfig = '' add_header Content-Type text/plain; ''; }; }; virtualHosts."wg.${domain}" = { useACMEHost = domain; forceSSL = true; locations."/" = { proxyPass = "http://127.0.0.1:${toString config.modules.system.wstunnel.listenPort}"; proxyWebsockets = true; extraConfig = '' proxy_read_timeout 3600s; proxy_send_timeout 3600s; ''; }; }; virtualHosts."searxng.${domain}" = mkIf cfg.searxng.enable { useACMEHost = domain; forceSSL = true; locations."/".return = "404"; extraConfig = '' include ${config.sops.templates."nginx-searxng-location.conf".path}; ''; }; virtualHosts."chat.${domain}" = { useACMEHost = domain; forceSSL = true; locations."/" = { proxyPass = "http://192.168.0.23:3080"; proxyWebsockets = true; extraConfig = privateAccessRules; }; }; virtualHosts."ai.${domain}" = { useACMEHost = domain; forceSSL = true; locations."/" = { proxyPass = "http://192.168.0.23:8000"; proxyWebsockets = true; }; }; virtualHosts."comfy.${domain}" = { useACMEHost = domain; forceSSL = true; locations."/" = { proxyPass = "http://192.168.0.23:8188"; proxyWebsockets = true; extraConfig = privateAccessRules; }; }; }; }; }