# `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 for the UI and for Mailpit. ## 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 , 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). ## Network map ``` host compose network "galaxy-local-dev-net" ┌────────────────────────────┐ ┌──────────────────────────────┐ │ pnpm dev localhost:5173 │──HMR──▶│ host (Vite) │ │ browser localhost:8080 │──REST/Connect─▶│ gateway:8080 │ │ browser localhost:8025 │─────▶│ mailpit:8025 (web UI) │ │ psql localhost:5433 │─────▶│ postgres:5432 │ │ redis-cli localhost:6380 │─────▶│ redis:6379 │ └────────────────────────────┘ │ ↳ backend:8080 (HTTP) │ │ ↳ backend:8081 (gRPC push) │ │ ↳ mailpit:1025 (SMTP in) │ └──────────────────────────────┘ ``` Only the gateway public port (8080) and the mailpit web UI (8025) are needed for normal UI work. Postgres (5433) and Redis (6380) are exposed for direct inspection (`make psql`, `redis-cli -h localhost -p 6380 -a galaxy-dev`). ## Make targets ```text make up Bring up the stack (build if needed) and wait for health make rebuild Rebuild the backend / gateway images (ignores cache) 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 - **`make up` reports a build error mentioning `pkg/cronutil`** — upstream module list drifted; copy any new `pkg//` 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.