# Coves Production Caddyfile # Handles HTTPS for both coves.social (AppView) and coves.me (PDS) # # Domain architecture: # - coves.social: AppView (API, web app) # - *.coves.social: Community handles (route atproto-did to PDS) # - pds.coves.me: PDS canonical hostname (for relay registration) # - coves.me: PDS legacy hostname (kept for compatibility) # Community handle subdomains (e.g., gaming.coves.social) # These need to route /.well-known/atproto-did to PDS for handle resolution # # NOTE: Wildcard certs require DNS challenge. For Cloudflare: # 1. Create API token with Zone:DNS:Edit permissions # 2. Set CLOUDFLARE_API_TOKEN environment variable # 3. Use caddy-dns/cloudflare plugin (see docker-compose.prod.yml) *.coves.social { tls { dns cloudflare {env.CLOUDFLARE_API_TOKEN} } # Handle resolution - proxy to PDS handle /.well-known/atproto-did { reverse_proxy pds:3000 } # OAuth well-known endpoints - proxy to PDS handle /.well-known/oauth-protected-resource { reverse_proxy pds:3000 } handle /.well-known/oauth-authorization-server { reverse_proxy pds:3000 } # All other requests return 404 (subdomains only exist for handle resolution) handle { respond "Not Found" 404 } # Security headers header { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" X-Content-Type-Options "nosniff" -Server } } # AppView Domain (root) coves.social { # Serve .well-known files for DID verification handle /.well-known/* { header Access-Control-Allow-Origin "*" root * /srv file_server } # Serve OAuth client metadata handle /client-metadata.json { root * /srv file_server } # Proxy all requests to AppView handle { reverse_proxy appview:8080 { # Health check health_uri /xrpc/_health health_interval 30s health_timeout 5s # Headers for proper DPoP verification # Host headers are critical for DPoP htu (HTTP URI) matching header_up Host {host} header_up X-Real-IP {remote_host} header_up X-Forwarded-For {remote_host} header_up X-Forwarded-Proto {scheme} header_up X-Forwarded-Host {host} } } # Logging (Docker captures stdout/stderr) log { output stdout format json } # Security headers header { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" X-Content-Type-Options "nosniff" X-Frame-Options "DENY" Referrer-Policy "strict-origin-when-cross-origin" Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' https://*.bsky.network wss://*.bsky.network" # Remove Server header -Server } # Enable compression encode gzip zstd } # PDS Domain (both hostnames point to same PDS) # pds.coves.me is the canonical hostname for relay registration pds.coves.me, coves.me { reverse_proxy pds:3000 { # Health check health_uri /xrpc/_health health_interval 30s health_timeout 5s # Headers for proper client IP handling header_up Host {host} header_up X-Real-IP {remote_host} header_up X-Forwarded-For {remote_host} header_up X-Forwarded-Proto {scheme} # Note: Caddy v2 handles WebSocket upgrades automatically # No need for explicit Connection/Upgrade headers } # Logging (Docker captures stdout/stderr) log { output stdout format json } # Security headers header { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" X-Content-Type-Options "nosniff" X-Frame-Options "DENY" Referrer-Policy "strict-origin-when-cross-origin" -Server } # Enable compression encode gzip zstd }