{ config, lib, pkgs, namespace, ... }: with lib; let name = "caddy-internal"; cfg = config.${namespace}.services.${name}; net = lib.${namespace}.network; caddyPackage = pkgs.caddy.withPlugins { plugins = [ "github.com/caddy-dns/cloudflare@v0.2.3" ]; hash = "sha256-20o+14cn/eeLuf1c8uGE1ODRZGC0oxocaIVlv4tFSvA="; }; # Build a virtual-host block for one proxy entry. # Access is restricted to LAN + Nebula subnets; all other clients get 403. mkProxyBlock = entryName: proxyCfg: let fqdn = "${proxyCfg.subdomain}.${net.domain}"; in '' @${entryName} host ${fqdn} handle @${entryName} { @${entryName}_internal { remote_ip ${net.subnet.lan} ${net.subnet.nebula} host ${fqdn} } handle @${entryName}_internal { reverse_proxy ${proxyCfg.upstream} ${proxyCfg.extraCaddyConfig} } handle { respond "Forbidden" 403 } } ''; proxyBlocks = lib.concatStringsSep "\n" ( lib.mapAttrsToList mkProxyBlock (lib.filterAttrs (_: p: p.enable) cfg.proxies) ); caddy-internal = lib.${namespace}.mkModule { inherit config name; description = "Internal-only Caddy reverse proxy with HTTPS via Cloudflare DNS challenge"; options = { proxies = mkOption { type = types.attrsOf ( types.submodule { options = { enable = lib.${namespace}.mkBoolOpt true "Whether to enable this proxy entry"; subdomain = lib.${namespace}.mkOpt types.str "" "Subdomain under ${net.domain}"; upstream = lib.${namespace}.mkOpt types.str "" "Upstream address (e.g. http://127.0.0.1:8123)"; extraCaddyConfig = lib.${namespace}.mkOpt types.lines "" "Extra Caddyfile directives for this entry"; }; } ); default = { }; description = "Internal services to proxy, each restricted to LAN + Nebula subnets"; }; }; moduleConfig = { services.caddy = { enable = true; package = caddyPackage; environmentFile = config.sops.templates."caddy-internal.env".path; email = "jalle008@proton.me"; enableReload = true; dataDir = "${cfg.configDir}/caddy"; globalConfig = '' metrics http_port 80 https_port 443 default_bind 0.0.0.0 ''; virtualHosts."*.${net.domain}" = { extraConfig = '' tls { dns cloudflare {$CLOUDFLARE_DNS_API_TOKEN} } ${proxyBlocks} ''; }; }; networking.firewall.allowedTCPPorts = [ 80 443 ]; }; }; in { imports = [ caddy-internal ./sops.nix ]; }