{ config, lib, namespace, ... }: with lib; let cfg = config.${namespace}.services.traefik; # Process extraServices into service configurations extraServiceConfigs = let makeService = service: nameValuePair service.name { loadBalancer.servers = [ { url = service.url; } ]; }; in listToAttrs (map makeService cfg.extraServices); # Process extraRouters into router configurations extraRouterConfigs = let makeRouter = router: nameValuePair router.subdomain { entryPoints = router.entryPoints; rule = "Host(`${router.subdomain}.${domain}`)"; service = router.service; middlewares = router.middlewares ++ [ "crowdsec" "whitelist-geoblock" ]; tls.certResolver = "letsencrypt"; }; in listToAttrs (map makeRouter cfg.extraRouters); # Process reverseProxies into service and router configurations reverseProxyServiceConfigs = let makeService = reverseProxy: nameValuePair reverseProxy.service.name reverseProxy.service.config; in listToAttrs (map makeService cfg.reverseProxies); reverseProxyRouterConfigs = let makeRouter = reverseProxy: nameValuePair reverseProxy.router.subdomain reverseProxy.router.config; in listToAttrs (map makeRouter cfg.reverseProxies); domain = "mjallen.dev"; serverIp = "10.0.1.3"; # Forward services authUrl = "http://${serverIp}:9000/outpost.goauthentik.io"; authentikUrl = "http://${serverIp}:9000"; cacheUrl = "http://${serverIp}:9012"; cloudUrl = "http:/10.0.1.3:9200"; # cloudUrl = "http://${config.containers.nextcloud.localAddress}:80"; hassUrl = "http://10.0.1.4:8123"; immichUrl = "http://${serverIp}:${toString config.services.immich.port}"; jellyfinUrl = "http://${serverIp}:8096"; jellyseerrUrl = "http://10.0.1.3:${toString config.services.jellyseerr.port}"; lubeloggerUrl = "http://${serverIp}:6754"; # onlyofficeUrl = "http://${config.containers.nextcloud.localAddress}:${toString config.containers.nextcloud.config.services.onlyoffice.port}"; onlyofficeUrl = "http://10.0.1.3:9980"; openWebUIUrl = "http://${serverIp}:8888"; paperlessUrl = "http://${serverIp}:${toString config.services.paperless.port}"; # Plugins traefikPlugins = { bouncer = { moduleName = "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"; version = "v1.4.5"; }; geoblock = { moduleName = "github.com/PascalMinder/geoblock"; version = "v0.2.5"; }; }; # Ports httpPort = 80; httpsPort = 443; traefikPort = 8080; metricsPort = 8082; forwardPorts = [ httpPort httpsPort traefikPort metricsPort ]; # misc letsEncryptEmail = "jalle008@proton.me"; dataDir = "/media/nas/main/appdata/traefik"; authentikAddress = "http://${serverIp}:9000/outpost.goauthentik.io/auth/traefik"; in { imports = [ ./options.nix ]; config = mkIf cfg.enable { sops = { secrets = { "jallen-nas/traefik/crowdsec/lapi-key" = { sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml"); owner = config.users.users.traefik.name; group = config.users.users.traefik.group; restartUnits = [ "traefik.service" ]; }; "jallen-nas/traefik/crowdsec/capi-machine-id" = { sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml"); owner = config.users.users.traefik.name; group = config.users.users.traefik.group; restartUnits = [ "traefik.service" ]; }; "jallen-nas/traefik/crowdsec/capi-password" = { sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml"); owner = config.users.users.traefik.name; group = config.users.users.traefik.group; restartUnits = [ "traefik.service" ]; }; "jallen-nas/traefik/cloudflare-dns-api-token" = { sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml"); owner = config.users.users.traefik.name; group = config.users.users.traefik.group; restartUnits = [ "traefik.service" ]; }; "jallen-nas/traefik/cloudflare-zone-api-token" = { sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml"); owner = config.users.users.traefik.name; group = config.users.users.traefik.group; restartUnits = [ "traefik.service" ]; }; "jallen-nas/traefik/cloudflare-api-key" = { sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml"); owner = config.users.users.traefik.name; group = config.users.users.traefik.group; restartUnits = [ "traefik.service" ]; }; "jallen-nas/traefik/cloudflare-email" = { sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml"); owner = config.users.users.traefik.name; group = config.users.users.traefik.group; restartUnits = [ "traefik.service" ]; }; }; templates = { "traefik.env" = { content = '' CLOUDFLARE_DNS_API_TOKEN=${config.sops.placeholder."jallen-nas/traefik/cloudflare-dns-api-token"} CLOUDFLARE_ZONE_API_TOKEN=${config.sops.placeholder."jallen-nas/traefik/cloudflare-zone-api-token"} CLOUDFLARE_API_KEY=${config.sops.placeholder."jallen-nas/traefik/cloudflare-api-key"} CLOUDFLARE_EMAIL=${config.sops.placeholder."jallen-nas/traefik/cloudflare-email"} ''; owner = config.users.users.traefik.name; group = config.users.users.traefik.group; restartUnits = [ "traefik.service" ]; }; }; }; networking.firewall = { allowedTCPPorts = forwardPorts; allowedUDPPorts = forwardPorts; }; services.traefik = { enable = true; dataDir = dataDir; group = "jallen-nas"; # group; environmentFiles = [ config.sops.templates."traefik.env".path ]; staticConfigOptions = { entryPoints = { web = { address = ":${toString httpPort}"; asDefault = true; http.redirections.entrypoint = { to = "websecure"; scheme = "https"; }; }; websecure = { address = ":${toString httpsPort}"; asDefault = true; http.tls.certResolver = "letsencrypt"; }; metrics = { address = ":${toString metricsPort}"; # Port for metrics }; }; log = { level = "INFO"; }; metrics = { prometheus = { entryPoint = "metrics"; addEntryPointsLabels = true; addServicesLabels = true; buckets = [ 0.1 0.3 1.2 5.0 ]; # Response time buckets }; }; certificatesResolvers.letsencrypt.acme = { email = letsEncryptEmail; storage = "${config.services.traefik.dataDir}/acme.json"; dnsChallenge = { provider = "cloudflare"; resolvers = [ "1.1.1.1:53" "8.8.8.8:53" ]; }; }; api.dashboard = true; # Access the Traefik dashboard on :8080 of your server api.insecure = true; experimental = { plugins = traefikPlugins; }; }; dynamicConfigOptions = { # udp = { # services = { # wireguard.loadBalancer.servers = [ # { # url = "localhost:51820"; # } # ]; # }; # routers = { # wireguard = { # entryPoints = [ "websecure" ]; # service = "wireguard"; # }; # }; # }; http = { serversTransports = { internal-https = { insecureSkipVerify = true; }; attich1 = { serverName = "localhost"; disableHTTP2 = true; }; }; middlewares = { authentik = { forwardAuth = { tls.insecureSkipVerify = true; address = authentikAddress; trustForwardHeader = true; authResponseHeaders = [ "X-authentik-username" "X-authentik-groups" "X-authentik-email" "X-authentik-name" "X-authentik-uid" "X-authentik-jwt" "X-authentik-meta-jwks" "X-authentik-meta-outpost" "X-authentik-meta-provider" "X-authentik-meta-app" "X-authentik-meta-version" ]; }; }; crowdsec = { plugin = { bouncer = { enabled = true; crowdsecLapiKeyFile = config.sops.secrets."jallen-nas/traefik/crowdsec/lapi-key".path; crowdsecLapiScheme = "http"; crowdsecLapiHost = "localhost:8181"; crowdsecLapiPath = "/"; crowdsecLapiTLSInsecureVerify = false; crowdsecCapiMachineIdFile = config.sops.secrets."jallen-nas/traefik/crowdsec/capi-machine-id".path; crowdsecCapiPasswordFile = config.sops.secrets."jallen-nas/traefik/crowdsec/capi-password".path; crowdsecCapiScenarios = [ ]; }; }; }; whitelist-geoblock = { plugin = { geoblock = { silentStartUp = false; allowLocalRequests = true; logLocalRequests = false; logAllowedRequests = false; logApiRequests = false; api = "https://get.geojs.io/v1/ip/country/{ip}"; apiTimeoutMs = 500; cacheSize = 25; forceMonthlyUpdate = true; allowUnknownCountries = false; unknownCountryApiResponse = "nil"; blackListMode = false; countries = [ "CA" "US" ]; }; }; }; internal-ipallowlist = { ipAllowList = { sourceRange = [ "127.0.0.1/32" "10.0.1.0/24" ]; }; }; collabora-headers = { headers = { customRequestHeaders = { Upgrade = "websocket"; Connection = "Upgrade"; X-Forwarded-Proto = "https"; X-Forwarded-Host = "office.mjallen.dev"; }; customResponseHeaders = { X-Frame-Options = ""; Content-Security-Policy = "frame-ancestors https://cloud.mjallen.dev"; }; referrerPolicy = "no-referrer"; stsSeconds = "15552000"; stsPreload = "true"; stsIncludeSubdomains = "true"; forceSTSHeader = "true"; browserXssFilter = "true"; }; }; onlyoffice-headers = { headers = { customResponseHeaders = { X-Robots-Tag = "none"; Strict-Transport-Security = "max-age=63072000"; X-Forwarded-Proto = "https"; }; browserXssFilter = "true"; contentTypeNosniff = "true"; stsIncludeSubdomains = "true"; stsPreload = "true"; stsSeconds = "31536000"; forceSTSHeader = "true"; accessControlMaxAge = "15552000"; accesscontrolalloworiginlist = "*"; }; }; }; services = { auth.loadBalancer.servers = [ { url = authUrl; } ]; gitea.loadBalancer.servers = [ { url = "http://10.0.1.3:3000"; } ]; actual.loadBalancer.servers = [ { url = "http://10.0.1.3:3333"; } ]; matrix.loadBalancer.servers = [ { url = "http://10.0.1.3:8448"; } ]; authentik.loadBalancer.servers = [ { url = authentikUrl; } ]; cache.loadBalancer = { servers = [ { url = cacheUrl; } ]; serversTransport = "attich1"; }; chat.loadBalancer.servers = [ { url = openWebUIUrl; } ]; cloud.loadBalancer = { servers = [ { url = cloudUrl; } ]; }; hass.loadBalancer.servers = [ { url = hassUrl; } ]; immich.loadBalancer.servers = [ { url = immichUrl; } ]; jellyfin.loadBalancer.servers = [ { url = jellyfinUrl; } ]; jellyseerr.loadBalancer.servers = [ { url = jellyseerrUrl; } ]; lubelogger.loadBalancer.servers = [ { url = lubeloggerUrl; } ]; onlyoffice.loadBalancer = { servers = [ { url = onlyofficeUrl; } ]; passHostHeader = true; }; paperless.loadBalancer.servers = [ { url = paperlessUrl; } ]; } // extraServiceConfigs // reverseProxyServiceConfigs; routers = { auth = { entryPoints = [ "websecure" ]; rule = "HostRegexp(`{subdomain:[a-z]+}.mjallen.dev`) && PathPrefix(`/outpost.goauthentik.io/`)"; service = "auth"; middlewares = [ "crowdsec" "whitelist-geoblock" ]; priority = 15; tls.certResolver = "letsencrypt"; }; gitea = { entryPoints = [ "websecure" ]; rule = "Host(`gitea.${domain}`)"; service = "gitea"; middlewares = [ "crowdsec" "whitelist-geoblock" ]; tls.certResolver = "letsencrypt"; }; actual = { entryPoints = [ "websecure" ]; rule = "Host(`actual.${domain}`)"; service = "actual"; middlewares = [ "crowdsec" "whitelist-geoblock" ]; tls.certResolver = "letsencrypt"; }; matrix = { entryPoints = [ "websecure" ]; rule = "Host(`matrix.${domain}`)"; service = "matrix"; middlewares = [ "crowdsec" "whitelist-geoblock" ]; tls.certResolver = "letsencrypt"; }; authentik = { entryPoints = [ "websecure" ]; rule = "Host(`authentik.${domain}`)"; service = "authentik"; middlewares = [ "crowdsec" "whitelist-geoblock" ]; tls.certResolver = "letsencrypt"; }; cache = { entryPoints = [ "websecure" ]; rule = "Host(`cache.${domain}`)"; service = "cache"; middlewares = [ ]; priority = 10; tls.certResolver = "letsencrypt"; }; cloud = { entryPoints = [ "websecure" ]; rule = "Host(`cloud.${domain}`)"; service = "cloud"; middlewares = [ "crowdsec" "whitelist-geoblock" ]; tls.certResolver = "letsencrypt"; }; hass = { entryPoints = [ "websecure" ]; rule = "Host(`hass.${domain}`)"; service = "hass"; middlewares = [ "crowdsec" "whitelist-geoblock" "authentik" ]; priority = 10; tls.certResolver = "letsencrypt"; }; immich = { entryPoints = [ "websecure" ]; rule = "Host(`immich.${domain}`)"; service = "immich"; middlewares = [ "crowdsec" "whitelist-geoblock" ]; tls.certResolver = "letsencrypt"; }; jellyfin = { entryPoints = [ "websecure" ]; rule = "Host(`jellyfin.${domain}`)"; service = "jellyfin"; middlewares = [ "crowdsec" "whitelist-geoblock" ]; tls.certResolver = "letsencrypt"; }; jellyseerr = { entryPoints = [ "websecure" ]; rule = "Host(`jellyseerr.${domain}`)"; service = "jellyseerr"; middlewares = [ "crowdsec" "whitelist-geoblock" ]; tls.certResolver = "letsencrypt"; }; lubelogger = { entryPoints = [ "websecure" ]; rule = "Host(`lubelogger.${domain}`)"; service = "lubelogger"; middlewares = [ "crowdsec" "whitelist-geoblock" ]; tls.certResolver = "letsencrypt"; }; onlyoffice = { entryPoints = [ "websecure" ]; rule = "Host(`office.${domain}`)"; service = "onlyoffice"; middlewares = [ "crowdsec" "whitelist-geoblock" # "onlyoffice-headers" "collabora-headers" ]; tls.certResolver = "letsencrypt"; }; } // extraRouterConfigs // reverseProxyRouterConfigs; }; }; }; }; }