# Application-routing Caddy for the long-lived dev environment. # Single-origin, path-based: the project site, the game UI, and both # gateway surfaces live behind one host. TLS termination and the real # `:80`/`:443` listeners belong to the host Caddy in front of us. # # / -> project site (galaxy-dev-site-dist -> /srv/galaxy-site) # /game/* -> game UI (galaxy-dev-ui-dist -> /srv/galaxy-ui) # /api/*, /healthz -> gateway public REST (galaxy-api:8080) # /rpc/* -> gateway Connect/gRPC-web (galaxy-api:9090) # # The same artifact is domain-agnostic: nothing here names a host, so an # identical bundle serves galaxy.lan, galaxy.iliadenisov.ru, or any # other domain the host Caddy terminates. { auto_https off } :80 { # Authenticated Connect-Web edge. The browser calls # `/rpc/edge.v1.Gateway/`; strip the `/rpc` prefix so the # gateway sees the proto-derived service path on its :9090 listener. handle_path /rpc/* { reverse_proxy galaxy-api:9090 } # Gateway public REST (auth) and the health probe on :8080. @api path /api/* /healthz handle @api { reverse_proxy galaxy-api:8080 } # Operator console + observability behind one Basic Auth gate. The gate # credential equals the admin-console account (dev: gm / gm-dev-password), # so Caddy forwards the same Authorization header to the backend `/_gm` # surface (its own Basic Auth) and to Grafana/Mailpit — one prompt covers # all three. The gateway applies the admin anti-abuse class to the console. @gm path /_gm /_gm/* handle @gm { basic_auth { gm "$2a$14$xVh1TLaZxh8fazlKrI9Mx.NQMQlMarYWtr3FRELmZIXuac/DeeTRO" } # Grafana under /_gm/grafana/ (sub-path mode; anonymous Admin, so the # /_gm gate is the only barrier — GF_AUTH_BASIC_ENABLED=false makes it # ignore the forwarded Authorization header). handle /_gm/grafana/* { reverse_proxy galaxy-grafana:3000 } # Mailpit captured-mail UI under /_gm/mailpit/ (MP_WEBROOT). Shows # every message the backend sent, relayed or not. handle /_gm/mailpit/* { reverse_proxy galaxy-mailpit:8025 } # The operator console itself (gateway -> backend /_gm surface). handle { reverse_proxy galaxy-api:8080 } } # Bare `/game` (no trailing slash) -> `/game/` so the SPA root # resolves before the site catch-all can claim it. handle /game { redir * /game/ 308 } # Game UI under `/game/`. The bundle is built with base=/game, so it # references `/game/_app/...`; strip the prefix to serve the build # whose files sit at the volume root. SPA fallback to index.html. handle_path /game/* { root * /srv/galaxy-ui # Hash-named, content-addressed chunks: cache forever. @immutable path /_app/immutable/* header @immutable Cache-Control "public, max-age=31536000, immutable" # index.html, env.js, version.json, core.wasm, wasm_exec.js, # favicon, manifest, service-worker.js must revalidate so a # fresh deploy lands without a manual cache clear. @dynamic not path /_app/immutable/* header @dynamic Cache-Control "no-cache, must-revalidate" try_files {path} /index.html file_server encode zstd gzip } # Project site at the root (VitePress static output). handle { root * /srv/galaxy-site # VitePress emits hash-named assets under `/assets/`. @immutable path /assets/* header @immutable Cache-Control "public, max-age=31536000, immutable" @dynamic not path /assets/* header @dynamic Cache-Control "no-cache, must-revalidate" try_files {path} {path}.html {path}/index.html /404.html file_server encode zstd gzip } }