{
  name = "Traefik";
  description = ''
    Runs the Traefik reverse proxy.
  '';

  nixosModule =
    {
      lib,
      config,
      hosts,
      ...
    }:
    with lib;
    {
      options.traefik = {
        wildcardDomains = mkOption {
          type = types.listOf types.str;
          default = [ ];
        };
      };

      config =
        let
          cfg = config.traefik;
          roleRoutes = concatMap (
            hostname:
            concatMap (
              role:
              role.traefikRoutes {
                host = hosts.${hostname};
              }
            ) hosts.${hostname}.roles
          ) (builtins.attrNames hosts);
          hostRoutes = concatMap (hostname: hosts.${hostname}.traefikRoutes) (builtins.attrNames hosts);
          routes = roleRoutes ++ hostRoutes;
        in
        {
          deployment.tags = [ "infra" ];
          networking.firewall.allowedTCPPorts = [
            80
            443
          ];

          environment.persistence."/persistent" = {
            directories = [
              {
                directory = "/appdata/traefik/acme";
                user = "traefik";
                mode = "0700";
              }
            ];
          };

          sops.secrets = {
            "traefik/acmeEmail" = {
              owner = "traefik";
            };
            "traefik/CLOUDFLARE_EMAIL" = {
              owner = "traefik";
            };
            "traefik/CLOUDFLARE_DNS_API_TOKEN" = {
              owner = "traefik";
            };
          };
          sops.templates."traefik.env" = {
            owner = "traefik";
            content = ''
              acmeEmail="${config.sops.placeholder."traefik/acmeEmail"}"
              CLOUDFLARE_EMAIL="${config.sops.placeholder."traefik/CLOUDFLARE_EMAIL"}"
              CLOUDFLARE_DNS_API_TOKEN="${config.sops.placeholder."traefik/CLOUDFLARE_DNS_API_TOKEN"}"
            '';
          };

          services.traefik = {
            enable = true;

            environmentFiles = [
              config.sops.templates."traefik.env".path
            ];

            staticConfigOptions = {
              entryPoints = {
                web = {
                  address = ":80";

                  http = {
                    redirections = {
                      entryPoint = {
                        to = "websecure";
                        scheme = "https";
                      };
                    };
                  };
                };

                websecure = {
                  address = ":443";

                  http.tls = {
                    certResolver = "letsencrypt";
                    domains = map (domain: {
                      main = domain;
                      sans = [ "*.${domain}" ];
                    }) cfg.wildcardDomains;
                  };
                };
              };

              certificatesResolvers = {
                letsencrypt = {
                  acme = {
                    email = "$acmeEmail";
                    storage = "/appdata/traefik/acme/acme.json";
                    dnsChallenge = {
                      provider = "cloudflare";
                    };
                  };
                };
              };
            };

            dynamicConfigOptions = {
              http = {
                routers = listToAttrs (
                  map (route: {
                    name = route.name;
                    value = {
                      entrypoints = [ "websecure" ];
                      service = route.name;
                      rule = route.rule;
                      priority = route.priority or "0";
                    };
                  }) routes
                );
                services = listToAttrs (
                  map (route: {
                    name = route.name;
                    value.loadBalancer.servers = [ { url = route.target; } ];
                  }) routes
                );
              };
            };
          };
        };
    };
}