# Traefik at the edge of the services host. Every stack that wants to be
# reachable joins the external `traefik` network and opts in with labels
# (exposedbydefault=false). Secrets and hostnames come from .env (see
# env.example next to this file). The `traefik` network is created once,
# outside compose: see network.sh.
name: traefik

networks:
  traefik:
    external: true

services:
  traefik:
    image: traefik:v3.7.5
    container_name: traefik
    restart: unless-stopped
    mem_limit: 256m
    cpus: 0.5
    logging:
      driver: json-file
      options:
        max-file: 5
        max-size: 10m
    dns:
      - 10.0.15.254                      # internal resolver (split-horizon DNS)
    environment:
      - "CLOUDFLARE_DNS_API_TOKEN=${CLOUDFLARE_DNS_API_TOKEN}"   # ACME DNS-01
    command:
      - "--global.sendAnonymousUsage=false"
      - "--api=true"
      - "--api.dashboard=true"

      # Docker provider: containers opt in with traefik.enable=true and
      # are reached over the `traefik` network only.
      - "--providers.docker=true"
      - "--providers.docker.endpoint=unix:///var/run/docker.sock"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=traefik"

      # File provider: shared middlewares (robots-tag-header, redirect,
      # forward-auth, ldap-auth) live in ./data/dynamic and hot-reload.
      - "--providers.file.directory=/etc/traefik/dynamic"
      - "--providers.file.watch=true"

      # Entrypoints. web/websecure are what this post uses; the TCP ones
      # (ssh on :26, OpenVPN, QuakeJS) belong to other services on the host
      # and are kept only because they are in the real file.
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.ssh.address=:26"
      - "--entrypoints.openvpn.address=:1194/tcp"
      - "--entrypoints.quakejs.address=:27960/tcp"

      # There is a front proxy (nginx, 10.0.12.30) between the internet and
      # this Traefik. Trust X-Forwarded-* only from it; from anyone else the
      # headers are dropped. This is what makes ipallowlist + depth=1 honest.
      - "--entryPoints.web.forwardedHeaders.trustedIPs=10.0.12.30/32"
      - "--entryPoints.websecure.forwardedHeaders.trustedIPs=10.0.12.30/32"

      # Prometheus metrics on a separate entrypoint (scraped over the LAN).
      - "--metrics.prometheus=true"
      - "--metrics.prometheus.addEntryPointsLabels=true"
      - "--entryPoints.metrics.address=:8082"
      - "--metrics.prometheus.entryPoint=metrics"

      - "--log.level=INFO"
      - "--accesslog=true"
      - "--accesslog.fields.defaultmode=keep"
      - "--accesslog.fields.headers.defaultmode=keep"

      # Plugin used by the ldap-auth middleware (dashboard login).
      - "--experimental.plugins.ldapAuth.modulename=github.com/wiltonsr/ldapAuth"
      - "--experimental.plugins.ldapAuth.version=v0.1.11"

      # Certificates: Let's Encrypt via Cloudflare DNS-01 (no port 80 needed,
      # wildcard-capable) plus an HTTP-01 resolver as a fallback.
      - "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
      - "--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme-dnschallenge.json"
      - "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
      - "--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=8.8.8.8:53,1.1.1.1:53"
      - "--certificatesresolvers.letsencrypt-web.acme.email=${ACME_EMAIL}"
      - "--certificatesresolvers.letsencrypt-web.acme.storage=/etc/traefik/acme/acme-httpchallenge.json"
      - "--certificatesresolvers.letsencrypt-web.acme.httpchallenge.entrypoint=web"
    networks:
      traefik:
        ipv4_address: 10.0.4.2           # fixed address inside the traefik network
    # Published in host mode on the host's LAN address only, so a client on
    # the LAN that talks to Traefik directly arrives with its real source IP.
    ports:
      - { target: 80,    published: 80,    mode: host, host_ip: 10.0.12.50 }
      - { target: 443,   published: 443,   mode: host, host_ip: 10.0.12.50 }
      - { target: 26,    published: 26,    mode: host, host_ip: 10.0.12.50 }
      - { target: 1194,  published: 1194,  mode: host, host_ip: 10.0.12.50 }
      - { target: 27960, published: 27960, mode: host, host_ip: 10.0.12.50 }
    volumes:
      - "./data/acme:/etc/traefik/acme"
      - "./data/dynamic:/etc/traefik/dynamic"
      - "./data/logs:/logs"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    labels:
      # The dashboard itself: TLS, LDAP login, no indexing.
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`${TRAEFIK_DASHBOARD_DOMAIN}`)"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.tls=true"
      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
      - "traefik.http.routers.dashboard.middlewares=robots-tag-header@file,ldap-auth@file"
      - "traefik.http.routers.dashboard.service=api@internal"
