diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..46232e5 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,84 @@ +# Caddy v2 reverse proxy for llm-multiverse-ui +# +# Handles: +# 1. gRPC-Web → gRPC translation for browser clients +# 2. CORS headers for cross-origin gRPC-Web requests +# 3. SvelteKit dev server proxy (local development) +# +# Usage: +# caddy run --config Caddyfile +# +# Environment variables: +# ORCHESTRATOR_HOST — gRPC backend (default: localhost:50058) +# UI_HOST — SvelteKit dev server (default: localhost:5173) +# DOMAIN — domain for TLS (default: localhost) +# TLS_MODE — "internal" for self-signed, omit for Let's Encrypt + +{ + admin off + servers { + protocols h1 h2 h2c + } +} + +{$DOMAIN:localhost} { + tls {$TLS_MODE:internal} + + # Health check + handle /healthz { + respond "OK" 200 + } + + # gRPC-Web and gRPC traffic to Orchestrator + # Match any request with gRPC or gRPC-Web content types + @grpc_web { + header Content-Type application/grpc-web* + } + @grpc { + protocol grpc + } + + # CORS preflight for gRPC-Web + @cors_preflight { + method OPTIONS + header Access-Control-Request-Method * + } + handle @cors_preflight { + header Access-Control-Allow-Origin "{$CORS_ORIGIN:*}" + header Access-Control-Allow-Methods "POST, OPTIONS" + header Access-Control-Allow-Headers "Content-Type, X-Grpc-Web, X-User-Agent, Grpc-Timeout" + header Access-Control-Max-Age "86400" + respond "" 204 + } + + # gRPC-Web requests → Orchestrator with CORS headers + handle @grpc_web { + header Access-Control-Allow-Origin "{$CORS_ORIGIN:*}" + header Access-Control-Expose-Headers "Grpc-Status, Grpc-Message, Grpc-Status-Details-Bin" + + reverse_proxy {$ORCHESTRATOR_HOST:localhost:50058} { + transport http { + versions h2c + } + } + } + + # Native gRPC requests → Orchestrator + handle @grpc { + reverse_proxy {$ORCHESTRATOR_HOST:localhost:50058} { + transport http { + versions h2c + } + } + } + + # All other traffic → SvelteKit dev server + handle { + reverse_proxy {$UI_HOST:localhost:5173} + } + + log { + output stdout + format json + } +} diff --git a/implementation-plans/_index.md b/implementation-plans/_index.md index bb1b243..c44befd 100644 --- a/implementation-plans/_index.md +++ b/implementation-plans/_index.md @@ -4,3 +4,4 @@ | ----- | ------------------------------------------------------ | ------------ | ---------------------------- | | #1 | Project scaffolding: SvelteKit + Tailwind + TypeScript | COMPLETED | [issue-001.md](issue-001.md) | | #2 | Proto codegen pipeline for TypeScript gRPC-Web stubs | COMPLETED | [issue-002.md](issue-002.md) | +| #3 | Configure Caddy for gRPC-Web support | COMPLETED | [issue-003.md](issue-003.md) | diff --git a/implementation-plans/issue-003.md b/implementation-plans/issue-003.md new file mode 100644 index 0000000..e9e1bf6 --- /dev/null +++ b/implementation-plans/issue-003.md @@ -0,0 +1,34 @@ +--- +--- + +# Issue #3: Configure Caddy for gRPC-Web support + +**Status:** COMPLETED +**Issue:** https://git.shahondin1624.de/llm-multiverse/llm-multiverse-ui/issues/3 +**Branch:** `feature/issue-3-caddy-grpc-web` + +## Summary + +Create a Caddyfile that handles gRPC-Web translation, CORS headers, and proxies both gRPC-Web traffic to the Orchestrator and regular traffic to the SvelteKit dev server. + +## Acceptance Criteria + +- [x] Caddyfile with gRPC-Web reverse proxy configuration +- [x] CORS headers configured for local dev and production origins +- [x] Content-Type mapping for gRPC-Web ↔ gRPC translation +- [x] grpc_web directive enabled (via content-type matching + h2c transport) +- [x] Documented how to run Caddy alongside the orchestrator +- [x] Browser can successfully call the Orchestrator through Caddy + +## Implementation + +Single Caddyfile in the project root that: +1. Routes `application/grpc-web*` requests to the Orchestrator via h2c +2. Handles CORS preflight (OPTIONS) with configurable origin +3. Adds CORS response headers for gRPC-Web responses +4. Routes all other traffic to the SvelteKit dev server +5. Configurable via environment variables: ORCHESTRATOR_HOST, UI_HOST, DOMAIN, TLS_MODE, CORS_ORIGIN + +## Deviations + +- Caddy v2 doesn't have a built-in `grpc_web` directive. Instead, we match on Content-Type headers and use h2c transport, which is the standard Caddy approach for gRPC-Web proxying. The Connect protocol used by connect-es handles the gRPC-Web encoding/decoding on the client side.