# Caddy reverse proxy for blumeops services # Managed by ansible - do not edit manually # # All *.{{ caddy_domain }} requests are proxied to backend services. # TLS certificates are obtained via ACME DNS-01 challenge using Gandi. { # Global options admin off {% if caddy_tcp_services %} # Layer 4 (TCP) routing layer4 { {% for tcp_svc in caddy_tcp_services %} :{{ tcp_svc.port }} { route { proxy {{ tcp_svc.backend }} } } {% endfor %} } {% endif %} } # Wildcard certificate for all services *.{{ caddy_domain }}:{{ caddy_https_port }} { tls { dns gandi {env.GANDI_BEARER_TOKEN} } {% for service in caddy_services %} @{{ service.name }} host {{ service.host }} handle @{{ service.name }} { {% if service.kind | default('proxy') == 'static' %} root * {{ service.root }} encode gzip # Long-cache fingerprinted assets; everything else stays default. @{{ service.name }}_assets path_regexp \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ header @{{ service.name }}_assets Cache-Control "public, max-age=31536000, immutable" {% for dl in service.download_paths | default([]) %} @{{ service.name }}_dl{{ loop.index }} path {{ dl.path }} header @{{ service.name }}_dl{{ loop.index }} Content-Disposition `attachment; filename="{{ dl.filename }}"` {% endfor %} {% if service.try_html | default(false) %} # Quartz clean URLs: path → path/ → path.html → /404.html (200). # Caddy's handle_errors is a top-level directive and can't live in # this nested handle, so the 404 page rides as the final try_files # candidate (served with 200 — acceptable for a human-facing 404). try_files {path} {path}/ {path}.html /404.html {% endif %} file_server {% else %} {% if service.cache_policy | default('') == 'spa' %} # SPA cache policy: hashed static assets are immutable, HTML must revalidate. # Prevents stale HTML from referencing chunk hashes that no longer exist. @{{ service.name }}_static path /static/dist/* header @{{ service.name }}_static Cache-Control "public, max-age=31536000, immutable" @{{ service.name }}_html path /if/* header @{{ service.name }}_html Cache-Control "no-cache" {% endif %} {% if service.backend.startswith('https://') %} reverse_proxy {{ service.backend }} { # Caddy v2.11+ rewrites Host to upstream for HTTPS backends. # Preserve the original Host so services see *.ops.eblu.me. header_up Host {http.request.host} } {% else %} reverse_proxy {{ service.backend }} {% endif %} {% endif %} } {% endfor %} # Fallback for unknown hosts handle { respond "Unknown service" 404 } } # Base domain (ops.eblu.me) {{ caddy_domain }}:{{ caddy_https_port }} { tls { dns gandi {env.GANDI_BEARER_TOKEN} } respond "blumeops services - use a subdomain (e.g., forge.{{ caddy_domain }})" }