A community based topic aggregation platform built on atproto
1# Coves Production Caddyfile
2# Handles HTTPS for both coves.social (AppView) and coves.me (PDS)
3#
4# Domain architecture:
5# - coves.social: AppView (API, web app)
6# - *.coves.social: Community handles (route atproto-did to PDS)
7# - pds.coves.me: PDS canonical hostname (for relay registration)
8# - coves.me: PDS legacy hostname (kept for compatibility)
9
10# Community handle subdomains (e.g., gaming.coves.social)
11# These need to route /.well-known/atproto-did to PDS for handle resolution
12#
13# NOTE: Wildcard certs require DNS challenge. For Cloudflare:
14# 1. Create API token with Zone:DNS:Edit permissions
15# 2. Set CLOUDFLARE_API_TOKEN environment variable
16# 3. Use caddy-dns/cloudflare plugin (see docker-compose.prod.yml)
17*.coves.social {
18 tls {
19 dns cloudflare {env.CLOUDFLARE_API_TOKEN}
20 }
21 # Handle resolution - proxy to PDS
22 handle /.well-known/atproto-did {
23 reverse_proxy pds:3000
24 }
25
26 # OAuth well-known endpoints - proxy to PDS
27 handle /.well-known/oauth-protected-resource {
28 reverse_proxy pds:3000
29 }
30
31 handle /.well-known/oauth-authorization-server {
32 reverse_proxy pds:3000
33 }
34
35 # All other requests return 404 (subdomains only exist for handle resolution)
36 handle {
37 respond "Not Found" 404
38 }
39
40 # Security headers
41 header {
42 Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
43 X-Content-Type-Options "nosniff"
44 -Server
45 }
46}
47
48# AppView Domain (root)
49coves.social {
50 # Serve .well-known files for DID verification
51 handle /.well-known/* {
52 header Access-Control-Allow-Origin "*"
53 root * /srv
54 file_server
55 }
56
57 # Serve OAuth client metadata
58 handle /client-metadata.json {
59 root * /srv
60 file_server
61 }
62
63 # Proxy all requests to AppView
64 handle {
65 reverse_proxy appview:8080 {
66 # Health check
67 health_uri /xrpc/_health
68 health_interval 30s
69 health_timeout 5s
70
71 # Headers for proper DPoP verification
72 # Host headers are critical for DPoP htu (HTTP URI) matching
73 header_up Host {host}
74 header_up X-Real-IP {remote_host}
75 header_up X-Forwarded-For {remote_host}
76 header_up X-Forwarded-Proto {scheme}
77 header_up X-Forwarded-Host {host}
78 }
79 }
80
81 # Logging (Docker captures stdout/stderr)
82 log {
83 output stdout
84 format json
85 }
86
87 # Security headers
88 header {
89 Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
90 X-Content-Type-Options "nosniff"
91 X-Frame-Options "DENY"
92 Referrer-Policy "strict-origin-when-cross-origin"
93 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"
94 # Remove Server header
95 -Server
96 }
97
98 # Enable compression
99 encode gzip zstd
100}
101
102# PDS Domain (both hostnames point to same PDS)
103# pds.coves.me is the canonical hostname for relay registration
104pds.coves.me, coves.me {
105 reverse_proxy pds:3000 {
106 # Health check
107 health_uri /xrpc/_health
108 health_interval 30s
109 health_timeout 5s
110
111 # Headers for proper client IP handling
112 header_up Host {host}
113 header_up X-Real-IP {remote_host}
114 header_up X-Forwarded-For {remote_host}
115 header_up X-Forwarded-Proto {scheme}
116
117 # Note: Caddy v2 handles WebSocket upgrades automatically
118 # No need for explicit Connection/Upgrade headers
119 }
120
121 # Logging (Docker captures stdout/stderr)
122 log {
123 output stdout
124 format json
125 }
126
127 # Security headers
128 header {
129 Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
130 X-Content-Type-Options "nosniff"
131 X-Frame-Options "DENY"
132 Referrer-Policy "strict-origin-when-cross-origin"
133 -Server
134 }
135
136 # Enable compression
137 encode gzip zstd
138}