Serve API and website behind Caddy path prefixes

Caddy forwards /wizard/api/* and /wizard/website/* with the prefix
stripped, so the server received /ws instead of /api/ws and the website
connected to /api/ws on the domain root.

- Server routes are now /ws and /health (was /api/ws, /api/health)
- Website derives the websocket URL from its own location (../api/ws),
  giving /wizard/api/ws in production and /api/ws in development
- Vite dev proxy strips /api like the production proxy
- nginx back to the plain SPA config since Caddy strips the prefix
- /internal/ai rejects requests that came through the reverse proxy,
  in addition to the shared secret
- Docs updated for the prefixed deployment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 10:00:29 +02:00
co-authored by Claude Opus 5.5
parent 00c73306d7
commit 90176e1cca
13 changed files with 60 additions and 40 deletions
+7 -4
View File
@@ -10,8 +10,8 @@ An online version of the card game Wizard (rules: `wizard_instructions/`).
| AI player | `wizard_ai/` | Python; RL training skeleton + websocket service |
```
browser ──wss /api/ws──▶ Caddy ──▶ wizard_server ◀──ws /internal/ai── wizard_ai
──https /──────▶ Caddy ──▶ wizard_website (network wizard_internal)
browser ──wss /wizard/api/ws──────▶ Caddy ──/ws──▶ wizard_server:8083 ◀──ws /internal/ai── wizard_ai
──https /wizard/website/──▶ Caddy ──/────▶ wizard_website:8082 (network wizard_internal)
```
## How it works
@@ -43,8 +43,11 @@ docker compose up -d --build
- `wizard_server` joins the external network `wizard_api` (port 8083) and `wizard_internal`.
- `wizard_ai` joins only `wizard_internal`, created by compose as an internal network.
Add a site block like `Caddyfile.example` to the existing Caddy container: `/api/*` goes to
`wizard_server:8083`, everything else to `wizard_website:8082`.
In the Caddy reverse proxy, `/wizard/website/*` goes to `wizard_website:8082`
and `/wizard/api/*` to `wizard_server:8083`, both with the prefix stripped. The website uses
relative asset paths and derives the API address from its own URL (`../api/ws`), so it works under
any prefix as long as the website and API are siblings. `/internal/ai` refuses requests that come
through the proxy (forwarding headers present), in addition to requiring `AI_SHARED_SECRET`.
Trained AI checkpoints go into `wizard_ai/models/latest/` (mounted read-only). Until one exists
the AI service uses the built-in heuristic player.