# Traefik dynamic routing config — generated by Ansible # Do not edit manually; re-run ansible-playbook deploy.yml # ── Wildcard TLS certificate via Cloudflare DNS-01 ──────────────────────────── # One *.csrx.ru cert covers all subdomains. New services = zero cert wait. tls: stores: default: defaultGeneratedCert: resolver: letsencrypt domain: main: "*.{{ domain_base }}" sans: - "{{ domain_base }}" http: routers: traefik-dashboard: rule: "Host(`{{ domain_traefik }}`)" entrypoints: [websecure] tls: certresolver: letsencrypt service: api@internal middlewares: [traefik-auth, rate-limit-strict] forgejo: rule: "Host(`{{ domain_git }}`)" entrypoints: [websecure] tls: certresolver: letsencrypt service: forgejo middlewares: [rate-limit-default] plane-api: rule: "Host(`{{ domain_plane }}`) && (PathPrefix(`/api/`) || PathPrefix(`/auth/`))" entrypoints: [websecure] tls: certresolver: letsencrypt service: plane-api middlewares: [rate-limit-api] plane: rule: "Host(`{{ domain_plane }}`)" entrypoints: [websecure] tls: certresolver: letsencrypt service: plane-web middlewares: [rate-limit-default] plane-godmode: rule: "Host(`{{ domain_plane }}`) && PathPrefix(`/god-mode/`)" entrypoints: [websecure] tls: certresolver: letsencrypt service: plane-admin middlewares: [rate-limit-strict] priority: 10 plane-spaces: rule: "Host(`{{ domain_plane }}`) && PathPrefix(`/spaces/`)" entrypoints: [websecure] tls: certresolver: letsencrypt service: plane-space middlewares: [rate-limit-default] priority: 10 grafana: rule: "Host(`{{ domain_dashboard }}`)" entrypoints: [websecure] tls: certresolver: letsencrypt service: grafana middlewares: [rate-limit-default] uptime-kuma: rule: "Host(`{{ domain_status }}`)" entrypoints: [websecure] tls: certresolver: letsencrypt service: uptime-kuma middlewares: [rate-limit-default] walava-landing: rule: "Host(`{{ domain_landing }}`)" entrypoints: [websecure] tls: certresolver: letsencrypt service: walava-landing middlewares: [rate-limit-default] # ── Cross-server: tools ({{ ip_tools }}) ───────────────────────────────── wiki: rule: "Host(`{{ domain_wiki }}`)" entrypoints: [websecure] tls: certresolver: letsencrypt service: wiki middlewares: [rate-limit-default] n8n: rule: "Host(`{{ domain_n8n }}`)" entrypoints: [websecure] tls: certresolver: letsencrypt service: n8n middlewares: [rate-limit-strict] services: forgejo: loadBalancer: servers: - url: "http://forgejo:3000" plane-api: loadBalancer: servers: - url: "http://plane-api:8000" plane-web: loadBalancer: servers: - url: "http://plane-web:3000" plane-admin: loadBalancer: servers: - url: "http://plane-admin:80" plane-space: loadBalancer: servers: - url: "http://plane-space:3000" grafana: loadBalancer: servers: - url: "http://grafana:3000" uptime-kuma: loadBalancer: servers: - url: "http://uptime-kuma:3001" walava-landing: loadBalancer: servers: - url: "http://walava-web:80" # ── Cross-server services ───────────────────────────────────────────────── wiki: loadBalancer: servers: - url: "http://{{ ip_tools }}:3000" n8n: loadBalancer: servers: - url: "http://{{ ip_tools }}:5678" middlewares: # ── Security Headers (applied globally via entrypoint) ───────────────── security-headers: headers: stsSeconds: 31536000 stsIncludeSubdomains: true stsPreload: true forceSTSHeader: true frameDeny: true contentTypeNosniff: true browserXssFilter: true referrerPolicy: "strict-origin-when-cross-origin" permissionsPolicy: "camera=(), microphone=(), geolocation=(), payment=()" customResponseHeaders: X-Robots-Tag: "noindex, nofollow" Server: "" # ── Rate Limiting ────────────────────────────────────────────────────── # Default: 100 req/s burst 50 — general web traffic rate-limit-default: rateLimit: average: 100 burst: 50 period: 1s # API: 30 req/s burst 20 — API endpoints rate-limit-api: rateLimit: average: 30 burst: 20 period: 1s # Strict: 10 req/s burst 5 — admin/login interfaces rate-limit-strict: rateLimit: average: 10 burst: 5 period: 1s # ── Auth (legacy basic auth kept for direct fallback) ───────────────── traefik-auth: basicAuth: users: - "{{ traefik_dashboard_htpasswd }}"