{ config, pkgs, lib, ... }: with lib; let # Version tagging traefikVersion = "v3.7.4"; # Helper helper = import ./lib.nix { inherit config pkgs lib; }; cfg = config.numbus.services.traefik; # Container config name = "traefik"; in helper.mkPodmanService { inherit name; description = "Traefik reverse proxy, one to rule them all"; pod = "false"; dataDirEnabled = false; dependencies = [ "network.target" ]; startDelay = 10; generatedSecrets = { CLOUDFLARE_DNS_API_TOKEN = "cat ${config.sops.secrets."cloudflareDnsApiToken".path}"; }; dirPermissions = [ "100999:100 ${cfg.configDir}" "100999:100 ${cfg.configDir}/rules" "100999:100 ${cfg.configDir}/certs" ]; reverseProxied = false; # Compose file good composeText = '' services: traefik: image: docker.io/library/traefik:${traefikVersion} container_name: traefik hostname: traefik user: '1000:1000' network_mode: pasta ports: - "80:80/tcp" - "443:443/tcp" volumes: - ${cfg.configDir}/traefik.yaml:/etc/traefik/traefik.yaml:ro - ${cfg.configDir}/rules:/etc/traefik/rules:ro - ${cfg.configDir}/certs:/var/traefik/certs:rw environment: - CF_DNS_API_TOKEN=$CLOUDFLARE_DNS_API_TOKEN cap_add: - NET_BIND_SERVICE security_opt: - no-new-privileges:true restart: unless-stopped ''; extraConfig = { environment.etc."traefik/traefik.yaml".text = '' global: checkNewVersion: false sendAnonymousUsage: false log: level: ${cfg.logLevel} accesslog: {} api: dashboard: false insecure: false entryPoints: web: address: :80 http: redirections: entryPoint: to: websecure scheme: https websecure: address: :443 forwardedHeaders: trustedIPs: - "127.0.0.1/32" - "10.0.0.0/8" - "192.168.0.0/16" - "172.16.0.0/12" certificatesResolvers: cloudflare: acme: email: ${config.numbus.mail.adminAddress} storage: /var/traefik/certs/cloudflare-acme.json caServer: "https://acme-v02.api.letsencrypt.org/directory" dnsChallenge: provider: cloudflare resolvers: - "1.1.1.1:53" - "9.9.9.9:53" serversTransport: insecureSkipVerify: true providers: file: directory: "/etc/traefik/rules" watch: true ''; environment.etc."traefik/rules/secureHeaders.yaml".text = '' http: middlewares: secureHeaders: headers: FrameDeny: true AccessControlAllowMethods: 'GET,OPTIONS,PUT' AccessControlAllowOriginList: - origin-list-or-null AccessControlMaxAge: 100 AddVaryHeader: true BrowserXssFilter: true ContentTypeNosniff: true ForceSTSHeader: true STSIncludeSubdomains: true STSPreload: true ContentSecurityPolicy: default-src 'self' 'unsafe-inline' CustomFrameOptionsValue: SAMEORIGIN ReferrerPolicy: same-origin PermissionsPolicy: vibrate 'self' STSSeconds: 315360000 ''; environment.etc."traefik/rules/secureTLS.yaml".text = '' tls: options: secureTLS: minVersion: VersionTLS12 sniStrict: true curvePreferences: - CurveP521 - CurveP384 cipherSuites: - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 ''; systemd.services."${name}-quirk" = { description = "Podman container quirk : ${name}"; wantedBy = [ "multi-user.target" ]; after = [ "${name}.service" "${name}-secrets.service" ]; onFailure = [ "service-failure-notify@%n.service" ]; startLimitBurst = 5; startLimitIntervalSec = 600; path = [ pkgs.coreutils ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; }; script = '' cp -Lurv /etc/traefik/* ${cfg.configDir} ''; }; }; extraOptions = { enable.default = true; logLevel = mkOption { type = types.enum [ "TRACE" "DEBUG" "INFO" "WARN" "ERROR" "FATAL" ]; default = "ERROR"; example = "ERROR"; description = "The level of detail Traefik should print in the logs."; }; }; }