{ config, pkgs, lib, ... }: with lib; let cfg = config.my.networking.caddy; in { options.my.networking.caddy = { enable = lib.mkEnableOption "Enable caddy as reverse proxy"; domainsList = lib.mkOption { type = lib.types.listOf (lib.types.attrsOf lib.types.str); description = '' A list of sets, each containing three parameters of type string: domain, email, and cloudflareApiKeyFile. ''; default = [ { domain = "example.com"; email = "user@domain.com"; cloudflareApiKeyFile = "/path/to/cloudflare/api/key"; } ]; }; # claudflareApiKeyFile = lib.mkOption { # default = ""; # type = lib.types.str; # description = '' # Cloudflare API key file # ''; # }; # domain = lib.mkOption { # default = "example.com"; # type = lib.types.str; # description = '' # The domain where Caddy is reachable # ''; # }; # email = lib.mkOption { # default = "user@domain.com"; # type = lib.types.str; # description = '' # Email for Certbot # ''; # }; }; config = lib.mkIf cfg.enable { # Insted on relying on caddy to provide TLS, we use certbot to get a certificate # https://aottr.dev/posts/2024/08/homelab-setting-up-caddy-reverse-proxy-with-ssl-on-nixos/ security.acme = { acceptTerms = true; # TESTING ONLY! # defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory"; certs = lib.mkMerge ( map (domainConfig: { "${domainConfig.domain}" = { group = config.services.caddy.group; email = domainConfig.email; domain = domainConfig.domain; extraDomainNames = [ "*.${domainConfig.domain}" ]; dnsProvider = "cloudflare"; dnsResolver = "1.1.1.1:53"; dnsPropagationCheck = true; environmentFile = domainConfig.cloudflareApiKeyFile; }; }) cfg.domainsList ); # certs."${cfg.domain}" = { # group = config.services.caddy.group; # domain = "${cfg.domain}"; # extraDomainNames = [ "*.${cfg.domain}" ]; # dnsProvider = "cloudflare"; # dnsResolver = "1.1.1.1:53"; # dnsPropagationCheck = true; # environmentFile = cfg.claudflareApiKeyFile; # }; }; services.caddy = { enable = true; globalConfig = '' admin :2024 servers { metrics } ''; extraConfig = lib.concatStringsSep "\n" ( map ( domainConfig: let certPath = config.security.acme.certs."${domainConfig.domain}".directory; in '' (cloudflare_${domainConfig.domain}) { tls ${certPath}/cert.pem ${certPath}/key.pem { protocols tls1.3 } } '' ) cfg.domainsList ); # extraConfig = # let # certPath = config.security.acme.certs."${cfg.domain}".directory; # in # '' # (cloudflare) { # tls ${certPath}/cert.pem ${certPath}/key.pem { # protocols tls1.3 # } # } # ''; }; systemd.services.caddy.serviceConfig = { AmbientCapabilities = "CAP_NET_BIND_SERVICE"; }; # By default, the module create a custom user but it lacks permission to read caddy files systemd.services.promtail.serviceConfig = { Group = lib.mkForce config.services.caddy.group; User = lib.mkForce config.services.caddy.user; }; services.promtail = { enable = true; configuration = { server.http_listen_port = 9080; server.grpc_listen_port = 0; clients = [ { url = "http://metrics.internal:3100/loki/api/v1/push"; } ]; scrape_configs = [ { job_name = "journal"; journal = { max_age = "12h"; labels = { job = "systemd-journal"; }; }; relabel_configs = [ { source_labels = [ "__journal__systemd_unit" ]; regex = "(.*)\\.service"; target_label = "service"; } { source_labels = [ "__journal__hostname" ]; target_label = "hostname"; } ]; } { job_name = "caddy"; static_configs = [ { targets = [ "localhost" ]; labels = { job = "caddylogs"; __path__ = "${config.services.caddy.logDir}/*.log"; }; } ]; } ]; }; }; networking.firewall.allowedTCPPorts = [ 80 443 2024 ]; networking.firewall.allowedUDPPorts = [ 80 443 ]; }; }