Files
galaxy-game/tools/local-dev/README.md
T
Ilia Denisov 82c4f70156 local-dev: stop spawned engine containers in down/clean
Backend's runtime spawns the engine container outside the compose
project, so `docker compose down` left a `galaxy-game-…` container
running. Add a `stop-engines` target that finds them by their OCI
image-title label (set in game/Dockerfile) and remove forcibly;
make `down` and `clean` depend on it. `clean` additionally wipes
the per-game state directory under /tmp/galaxy-game-state.

Add a troubleshooting note for the related symptom: when the
browser holds a keypair from a previous DB and `make clean`
recreates everything, the lobby renders "no games yet" until the
user clears site data or opens an incognito window. The dev user
keeps the same email but receives a fresh user_id, which the old
keypair cannot authenticate against.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 19:04:05 +02:00

229 lines
11 KiB
Markdown

# `tools/local-dev/` — Galaxy local development stack
A docker-compose stack that brings up postgres + redis + mailpit +
backend + gateway so the UI Vite dev server (run on the host) can
talk to a real authenticated stack without any cloud dependency.
The stack is the recommended baseline for UI work that goes beyond
the mocked Playwright fixtures: every payload exercises the real
FlatBuffers wire, every authenticated call verifies the response
signature against the dev keypair, and every email passes through
Mailpit's web UI for inspection.
This stack is **not** a CI gate (that role belongs to
[`tools/local-ci/`](../local-ci/README.md), which boots a Gitea +
Actions runner and replays workflow files). The two stacks are
independent and can coexist on the same machine; they bind different
ports and use different networks.
## Bring it up
```sh
make -C tools/local-dev up
```
`up` builds the local-dev backend and gateway images on first run
(pulls postgres, redis, mailpit), waits for every service to report
healthy, and returns. Subsequent invocations reuse the built images.
After the stack is healthy:
```sh
pnpm -C ui/frontend dev
```
Open <http://localhost:5173> for the UI and
<http://localhost:8025> for Mailpit.
The first `make up` builds the engine image (`galaxy-engine:local-dev`)
from `game/Dockerfile`. Subsequent invocations skip the build when the
image already exists; force a rebuild with `docker rmi galaxy-engine:local-dev`
followed by `make build-engine`.
## Daily flow
```sh
make -C tools/local-dev up # bring up (idempotent, fast on warm cache)
pnpm -C ui/frontend dev # in another terminal
# ...edit UI, browse, repeat...
make -C tools/local-dev down # stop containers, keep state
```
State persists in named Docker volumes between `up`/`down` cycles, so
games created on Tuesday survive into Wednesday. Wipe with
`make clean` when you want a fresh database.
## Logging in
Two paths coexist by default:
1. **Fixed dev code (fast).** `tools/local-dev/.env` ships
`BACKEND_AUTH_DEV_FIXED_CODE=123456`. After requesting a code in
the UI, type `123456``ConfirmEmailCode` accepts that literal
in addition to the real bcrypt-verified code stored on the
challenge row. The override emits a loud warning at backend boot
and is rejected by the production env loader (`BACKEND_ENV` guard
in `backend/internal/config`).
2. **Real Mailpit code.** Open <http://localhost:8025>, find the
most recent message, copy the six-digit code, paste it into the
UI. This exercises the full mail outbox path, including SMTP
handoff and gomail TLS-mode handling.
To force the second path (no fast-bypass), edit
`tools/local-dev/.env` and clear `BACKEND_AUTH_DEV_FIXED_CODE`, then
`make rebuild` (or simply `docker compose up -d backend` to recreate
the backend with the new env).
## Auto-provisioned dev sandbox
`make up` provisions a private game called **Dev Sandbox** owned by
the dev user (default `dev@local.test`). The flow is implemented in
`backend/internal/devsandbox` and runs on every backend boot when
`BACKEND_DEV_SANDBOX_EMAIL` is non-empty in `tools/local-dev/.env`.
Bootstrap is idempotent — re-running `make up` after a `make down`
finds the existing user, dummy participants, game, and memberships
without creating duplicates. If a previous boot crashed mid-way
(game stuck in `enrollment_open` or `ready_to_start`), the next boot
resumes the lifecycle.
To log in straight into the sandbox:
1. `make -C tools/local-dev up`
2. `pnpm -C ui/frontend dev` (in another terminal)
3. Open <http://localhost:5173/login>, enter `dev@local.test`, then
the dev code `123456`.
4. The lobby shows **Dev Sandbox** in *My Games*; click in.
To disable the bootstrap, clear `BACKEND_DEV_SANDBOX_EMAIL` in
`tools/local-dev/.env` and `docker compose up -d backend` (or
`make rebuild`). Existing users / games are not removed.
The bootstrap requires:
- `galaxy-engine:local-dev` Docker image (`make build-engine`).
- `BACKEND_DEV_SANDBOX_ENGINE_VERSION` parses as plain semver
(`MAJOR.MINOR.PATCH`); the default `0.1.0` is what the bootstrap
registers in the `engine_versions` row that points at the image.
- `BACKEND_DEV_SANDBOX_PLAYER_COUNT` ≥ 20 (the engine's minimum;
19 deterministic dummies fill the slots so the single real user
can start the game).
- A frozen turn schedule (`0 0 1 1 *` — once a year) so the visible
game state stays at turn 1 until you explicitly progress it.
## Network map
```
host compose network "galaxy-local-dev-net"
┌────────────────────────────────┐ ┌──────────────────────────────┐
│ browser localhost:5173 │── pnpm dev (Vite, host) ──┐ │
│ ↳ /api/* proxied ───┼──────────────────────────▶│ gateway:8080 │
│ ↳ /galaxy.gateway... ┼──────────────────────────▶│ │
│ browser localhost:8025 │─────────────────────────▶│ mailpit:8025 │
│ psql localhost:5433 │─────────────────────────▶│ postgres:5432 │
│ redis-cli localhost:6380 │─────────────────────────▶│ redis:6379 │
└────────────────────────────────┘ │ ↳ backend:8080 (HTTP) │
│ ↳ backend:8081 (gRPC push) │
│ ↳ mailpit:1025 (SMTP in) │
└────────────────────────────────┘
```
Vite's dev server proxies `/api` and `/galaxy.gateway.v1.EdgeGateway`
to the gateway, so every browser request stays same-origin (no CORS
preflight). The gateway is therefore reachable only through Vite at
<http://localhost:5173>, not at <http://localhost:8080> from the
browser tab. Direct curl/wget against <http://localhost:8080> still
works for diagnostic probes — only the browser-side requests are
proxied.
Mailpit (8025), postgres (5433), and redis (6380) remain directly
reachable for diagnostics (`make psql`, `redis-cli -h localhost -p
6380 -a galaxy-dev`).
To point the proxy at a non-local gateway, run
`VITE_DEV_PROXY_TARGET=http://gateway.host:8080 pnpm -C ui/frontend dev`
— no compose changes needed.
## Make targets
```text
make up Bring up the stack (build engine + compose images if needed) and wait for health
make rebuild Rebuild the backend / gateway images (ignores cache)
make build-engine Build galaxy-engine:local-dev from game/Dockerfile (no-op if image already present)
make down Stop containers, keep volumes
make clean Stop and wipe volumes (postgres + game-state)
make logs Tail every service's logs
make logs-backend Tail backend only
make logs-gateway Tail gateway only
make logs-mail Tail mailpit only
make psql Open a psql shell as galaxy@galaxy_backend
make status docker compose ps
```
## Files
- `docker-compose.yml` — five services: postgres, redis, mailpit,
backend, gateway, plus shared network and volumes.
- `backend.Dockerfile`, `gateway.Dockerfile` — local-dev runtime
images built on alpine (so `wget` is available for the compose
healthchecks). The build stage mirrors `backend/Dockerfile` and
`gateway/Dockerfile` exactly.
- `Makefile` — wrapper over `docker compose` that keeps the muscle
memory close to `tools/local-ci/`'s Makefile.
- `.env` — committed defaults for the compose `${VAR:-}`
expansions. Edit per-developer or override via your shell.
- `keys/gateway-response.pem`, `keys/gateway-response.pub` — dev-only
Ed25519 keypair used by the gateway for response signing. Pairs
with the `VITE_GATEWAY_RESPONSE_PUBLIC_KEY` value in
`ui/frontend/.env.development`. See `keys/README.md` before
rotating.
- `keys/regenerate.go` — one-shot Go helper that regenerates the
pair and prints the new base64 public key.
## Troubleshooting
- **Lobby shows "no games yet" after `make clean && make up`** —
the browser still holds a keypair + device session bound to the
user_id from the previous DB. The new user has the same email
(`dev@local.test`) but a fresh user_id, so the old keypair
authenticates against a session row that no longer exists or
points at the wrong account. Open the page in an incognito
window, or wipe site data for `localhost:5173` (DevTools →
Application → Storage → Clear site data) and log in again.
- **`make down` leaves a `galaxy-game-…` container behind** — fixed
in this Makefile: `make down` and `make clean` now stop spawned
engine containers via the `org.opencontainers.image.title=
galaxy-game-engine` label. To stop them by hand without touching
the rest of the stack, `make stop-engines`.
- **`make up` reports a build error mentioning `pkg/cronutil`** —
upstream module list drifted; copy any new `pkg/<name>/` line into
the local-dev `backend.Dockerfile` / `gateway.Dockerfile` to match
`backend/Dockerfile` / `gateway/Dockerfile`.
- **Gateway exits at boot with "redis: …"** — the redis container is
still bootstrapping. `make up --wait` waits for healthchecks; if
it times out, increase `start_period` in the gateway service or
inspect `make logs-redis`.
- **Login form rejects every code** — confirm
`BACKEND_AUTH_DEV_FIXED_CODE` is set in `tools/local-dev/.env` and
the backend has been recreated since the last edit
(`docker compose up -d backend`). Real Mailpit codes work
regardless.
- **UI talks to old gateway**: Vite caches `import.meta.env` at boot.
Restart `pnpm dev` after editing
`ui/frontend/.env.development.local`.
- **Port 8080 already in use** — stop the conflicting service or
edit the host-side mapping in `docker-compose.yml` (gateway's
`ports:` entry) plus the matching `VITE_GATEWAY_BASE_URL` in
`ui/frontend/.env.development.local`.
## Relationship to other infrastructure
- `tools/local-ci/` — Gitea + Actions runner, replays
`.gitea/workflows/*` against a pushed branch. Different stack,
different purpose; coexists with local-dev on the same machine.
- `integration/testenv/` — testcontainers harness used by
`make -C integration integration`. Uses the same images
(`backend/Dockerfile`, `gateway/Dockerfile`) at production
defaults; do not confuse with this local-dev stack, which carries
alpine-runtime images for ergonomics and the dev-mode auth
override.