{ lib, config, ... }: let cfg = config.services.webserver; types = { location = lib.types.submodule { options = { proxyPort = lib.mkOption { type = lib.types.nullOr lib.types.int; default = null; }; root = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; }; basicAuthFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; }; extraConfig = lib.mkOption { type = lib.types.str; default = ""; }; }; }; vhost = lib.types.submodule { options = { proxyBuffering = lib.mkOption { type = lib.types.bool; default = true; }; locations = lib.mkOption { type = lib.types.attrsOf types.location; }; tailscaleAuth = lib.mkOption { type = lib.types.bool; default = false; }; extraConfig = lib.mkOption { type = lib.types.lines; default = ""; }; }; }; }; tailscaleAuthVhosts = lib.attrNames ( lib.filterAttrs (name: { tailscaleAuth, ... }: tailscaleAuth) cfg.vHosts ); in { options.services.webserver = { enable = lib.mkEnableOption "Enable nginx"; acme = { dnsChallenge = lib.mkEnableOption "Enable DNS challenge"; dnsProvider = lib.mkOption { type = lib.types.str; default = "hetzner"; }; }; vHosts = lib.mkOption { type = lib.types.attrsOf types.vhost; default = { }; }; tailscaleAuth.expectedTailnet = lib.mkOption { type = lib.types.str; default = ""; }; }; config = lib.mkIf cfg.enable { services = { nginx = { enable = lib.mkDefault true; recommendedProxySettings = true; recommendedTlsSettings = true; recommendedBrotliSettings = true; recommendedGzipSettings = true; recommendedZstdSettings = true; recommendedOptimisation = true; tailscaleAuth = { enable = (lib.length tailscaleAuthVhosts) > 0; virtualHosts = tailscaleAuthVhosts; expectedTailnet = cfg.tailscaleAuth.expectedTailnet; }; virtualHosts = lib.mapAttrs ( _: { proxyBuffering, locations, extraConfig, ... }: { forceSSL = true; enableACME = true; http2 = true; acmeRoot = lib.mkIf cfg.acme.dnsChallenge null; extraConfig = lib.concatLines [ (lib.optionalString (!proxyBuffering) "proxy_buffering off;") "charset utf-8;" extraConfig ]; locations = lib.mapAttrs ( _: { proxyPort, extraConfig, root, basicAuthFile, }: lib.mergeAttrsList [ { inherit extraConfig root basicAuthFile; } ( if (lib.isInt proxyPort) then { proxyWebsockets = true; proxyPass = "http://localhost:${toString proxyPort}"; } else { } ) ] ) locations; } ) cfg.vHosts; }; octodns.records = lib.filterAttrs (name: _: name != config.networking.domain) ( lib.mapAttrs' ( fqdn: { tailscaleAuth, ... }: { name = lib.removeSuffix ".${config.networking.domain}" fqdn; value = { CNAME = if tailscaleAuth then { target = "ts.${config.networking.domain}."; } else { toRoot = true; }; }; } ) cfg.vHosts ); }; security.acme = { acceptTerms = true; defaults = { email = "admin@j.repomaa.com"; dnsProvider = lib.mkIf cfg.acme.dnsChallenge cfg.acme.dnsProvider; }; }; modules.firewall.allInterfaces = [ "web" ]; }; }