# Caddyfile for ui.suaveplan.com — @suaveplan/react-ui registry + Storybook # # Serves two static trees off /srv/registry: # /r/* shadcn registry-item JSON manifests # /storybook/* hosted Storybook static build # # Auto-TLS via Let's Encrypt. TLS 1.2 minimum, HSTS preload-eligible, # security headers on every response. { # Global options. ACME contact email; auto-HTTPS is on by default. email ops@suaveplan.com servers { protocols h1 h2 } } ui.suaveplan.com { root * /srv/registry # TLS pinned to modern protocols only. TLS 1.0/1.1 are rejected. tls { protocols tls1.2 tls1.3 } # Compression. encode zstd gzip # Security headers applied to every response. # HSTS includes preload directive — domain is preload-list eligible # after a 90-day soak of the live deployment. 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" Permissions-Policy "interest-cohort=()" # Remove server fingerprinting. -Server } # Health probe — always 200, no filesystem touch, no logs noise. handle /health { respond "ok" 200 { close } } # Block any non-ACME .well-known traffic. Caddy's internal ACME # handler binds /.well-known/acme-challenge/* before this block runs; # everything else under .well-known is denied. @wellKnownNotAcme { path /.well-known/* not path /.well-known/acme-challenge/* } respond @wellKnownNotAcme 404 # Registry JSON tree. Public, CORS-open (manifests are static JSON # fetched by `npx shadcn` from any origin). Short TTL so updated # manifests propagate quickly to consumers; SWR keeps perceived # latency low. handle_path /r/* { root * /srv/registry/r header { Access-Control-Allow-Origin "*" Access-Control-Allow-Methods "GET, HEAD, OPTIONS" Access-Control-Allow-Headers "Content-Type" Cache-Control "public, max-age=300, stale-while-revalidate=60" Content-Type "application/json; charset=utf-8" } file_server { precompressed br gzip } } # Storybook static build. Restrictive CSP — Storybook needs inline # styles and inline scripts for its own runtime, but third-party # script sources are blocked. handle_path /storybook/* { root * /srv/registry/storybook header { Cache-Control "public, max-age=3600, stale-while-revalidate=600" Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'" } try_files {path} {path}/ /index.html file_server } # Catch-all 404 for anything outside /r, /storybook, /health. handle { respond "Not Found" 404 } # Logs to stdout — `docker logs` captures. log { output stdout format json level INFO } }