Initial Commit
This commit is contained in:
99
ops/registry/Caddyfile
Normal file
99
ops/registry/Caddyfile
Normal file
@@ -0,0 +1,99 @@
|
||||
# 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user