# Caddy configuration for DChain prod.
#
# What this does:
#   1. Auto-HTTPS via Let's Encrypt (requires the DOMAIN envvar and
#      a DNS A-record pointing at this host).
#   2. Round-robins HTTP /api/* across the three node backends. GETs are
#      idempotent so round-robin is safe; POST /api/tx is accepted by any
#      validator and gossiped to the rest — no stickiness needed.
#   3. Routes /api/ws (WebSocket upgrade) through with header
#      preservation. Uses ip_hash (lb_policy client_ip) so one client
#      sticks to one node — avoids re-doing the auth handshake on every
#      subscribe.
#   4. Serves /metrics ONLY from localhost IPs so the Prometheus inside
#      the stack can scrape it; public scrapers are refused.
#
# To use:
#   - Set environment var DOMAIN before `docker compose up`:
#       DOMAIN=dchain.example.com docker compose up -d
#   - DNS must resolve DOMAIN → this host's public IP.
#   - Port 80 must be reachable for ACME HTTP-01 challenge.

{
    # Global options. `auto_https` is on by default — leave it alone.
    email   {$ACME_EMAIL:admin@example.com}
    servers {
        # Enable HTTP/3 for mobile clients.
        protocols h1 h2 h3
    }
}

# ── Public endpoint ────────────────────────────────────────────────────────
{$DOMAIN:localhost} {
    # Compression for JSON / HTML responses.
    encode zstd gzip

    # ── WebSocket ──────────────────────────────────────────────────────
    # Client-IP stickiness so reconnects land on the same node. This keeps
    # per-subscription state local and avoids replaying every auth+subscribe
    # to a cold node.
    @ws path /api/ws
    handle @ws {
        reverse_proxy node1:8080 node2:8080 node3:8080 {
            lb_policy ip_hash
            # Health-check filters dead nodes out of the pool automatically.
            health_uri    /api/netstats
            health_interval 15s
            # Upgrade headers preserved by Caddy by default for WS path; no
            # extra config needed.
        }
    }

    # ── REST API ──────────────────────────────────────────────────────
    handle /api/* {
        reverse_proxy node1:8080 node2:8080 node3:8080 {
            lb_policy       least_conn
            health_uri      /api/netstats
            health_interval 15s
            # Soft fail open: if no node is healthy, return a clear 503.
            fail_duration   30s
        }
    }

    # ── /metrics — internal only ──────────────────────────────────────
    # Refuse external scraping of Prometheus metrics. Inside the Docker
    # network Prometheus hits node1:8080/metrics directly, bypassing Caddy.
    @metricsPublic {
        path /metrics
        not remote_ip 127.0.0.1 ::1 172.16.0.0/12 192.168.0.0/16 10.0.0.0/8
    }
    handle @metricsPublic {
        respond "forbidden" 403
    }

    # ── Everything else → explorer HTML ───────────────────────────────
    handle {
        reverse_proxy node1:8080 {
            health_uri      /api/netstats
            health_interval 15s
        }
    }

    # Server-side logging; write JSON for easy log aggregation.
    log {
        output  stdout
        format  json
        level   INFO
    }
}
