tools/local-dev: docker-compose stack for UI development
Adds tools/local-dev/ with postgres + redis + mailpit + backend + gateway plus a Make wrapper, so `make -C tools/local-dev up` brings the full authenticated stack online and `pnpm -C ui/frontend dev` talks to it directly. The committed `.env.development` already points at the stack and pins the matching gateway response public key from the dev keypair under tools/local-dev/keys/. The backend ships a new opt-in env, BACKEND_AUTH_DEV_FIXED_CODE (`tools/local-dev/.env` defaults it to 123456). When set, ConfirmEmailCode accepts that literal in addition to the real bcrypt-verified code; SendEmailCode still queues a real email so Mailpit captures the issued code at http://localhost:8025/, and both paths coexist. The override is rejected as non-six-digit by config validation and emits a loud warning at backend startup. The local-dev Dockerfiles mirror backend/Dockerfile and gateway/Dockerfile but switch the runtime stage to alpine so docker-compose healthchecks can wget /healthz; the gateway Dockerfile additionally copies ui/core/ into the build context because gateway/go.mod's `replace galaxy/core => ../ui/core` is required to compile the gateway main. Smoke tested: - `make -C tools/local-dev up` boots all five services to healthy. - send-email-code + confirm-email-code with code=123456 returns a device_session_id; a real code in Mailpit also redeems successfully. - `pnpm test` 14/14, `pnpm exec playwright test` 44/44. - `go test ./backend/internal/config/...` green. Docs: tools/local-dev/README.md, tools/local-dev/keys/README.md, new "Local development stack" section in ui/docs/testing.md, and a short pointer in ui/README.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# Default environment for `make -C tools/local-dev up`. The compose
|
||||
# file reads these via ${VAR:-} expansions; override per-developer by
|
||||
# editing this file (it is committed only with the project defaults).
|
||||
|
||||
# Six-digit decimal accepted by ConfirmEmailCode in addition to the
|
||||
# real bcrypt-verified code. Leave the value blank to disable the
|
||||
# override and force every login through Mailpit.
|
||||
BACKEND_AUTH_DEV_FIXED_CODE=123456
|
||||
@@ -0,0 +1,53 @@
|
||||
.PHONY: help up down logs status rebuild clean psql logs-backend logs-gateway logs-mail wait
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
COMPOSE := docker compose
|
||||
|
||||
help:
|
||||
@echo "Local development stack for the Galaxy UI:"
|
||||
@echo " make up Build (if needed) and bring up the stack, wait until healthy"
|
||||
@echo " make down Stop containers, keep volumes"
|
||||
@echo " make rebuild Force rebuild of backend / gateway images and bring up"
|
||||
@echo " make clean Stop and wipe volumes (postgres data, game state)"
|
||||
@echo " make logs Tail all logs"
|
||||
@echo " make logs-backend Tail only the backend logs"
|
||||
@echo " make logs-gateway Tail only the gateway logs"
|
||||
@echo " make logs-mail Tail only the mailpit logs"
|
||||
@echo " make status docker compose ps"
|
||||
@echo " make psql Open a psql shell as galaxy@galaxy_backend"
|
||||
@echo ""
|
||||
@echo "After 'make up', point the UI at the stack with:"
|
||||
@echo " pnpm -C ui/frontend dev"
|
||||
@echo "and open http://localhost:5173 (UI) plus http://localhost:8025 (Mailpit)."
|
||||
|
||||
up:
|
||||
$(COMPOSE) up -d --wait
|
||||
|
||||
rebuild:
|
||||
$(COMPOSE) build --no-cache backend gateway
|
||||
$(COMPOSE) up -d --wait
|
||||
|
||||
down:
|
||||
$(COMPOSE) down
|
||||
|
||||
clean:
|
||||
$(COMPOSE) down -v
|
||||
|
||||
logs:
|
||||
$(COMPOSE) logs -f --tail=100
|
||||
|
||||
logs-backend:
|
||||
$(COMPOSE) logs -f --tail=200 backend
|
||||
|
||||
logs-gateway:
|
||||
$(COMPOSE) logs -f --tail=200 gateway
|
||||
|
||||
logs-mail:
|
||||
$(COMPOSE) logs -f --tail=200 mailpit
|
||||
|
||||
status:
|
||||
$(COMPOSE) ps
|
||||
|
||||
psql:
|
||||
$(COMPOSE) exec postgres psql -U galaxy -d galaxy_backend
|
||||
@@ -0,0 +1,161 @@
|
||||
# `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.
|
||||
|
||||
## 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).
|
||||
|
||||
## 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/<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.
|
||||
@@ -0,0 +1,68 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
#
|
||||
# Local-dev image for the backend service. Mirrors the structure of
|
||||
# `backend/Dockerfile` (the integration/production image) but switches
|
||||
# the runtime stage to alpine so docker-compose healthchecks can shell
|
||||
# out to `wget` and the container can run as root for Docker-socket
|
||||
# access without needing the production-grade nonroot guarantees.
|
||||
#
|
||||
# Build via the local-dev compose: `make -C tools/local-dev up`. The
|
||||
# build context is the repository root.
|
||||
|
||||
FROM golang:1.26.2-alpine AS builder
|
||||
WORKDIR /src
|
||||
ENV CGO_ENABLED=0 GOFLAGS=-trimpath
|
||||
|
||||
COPY pkg/cronutil/ ./pkg/cronutil/
|
||||
COPY pkg/error/ ./pkg/error/
|
||||
COPY pkg/geoip/ ./pkg/geoip/
|
||||
COPY pkg/model/ ./pkg/model/
|
||||
COPY pkg/postgres/ ./pkg/postgres/
|
||||
COPY pkg/schema/ ./pkg/schema/
|
||||
COPY pkg/transcoder/ ./pkg/transcoder/
|
||||
COPY pkg/util/ ./pkg/util/
|
||||
COPY backend/ ./backend/
|
||||
|
||||
RUN <<'EOF' cat > go.work
|
||||
go 1.26.2
|
||||
|
||||
use (
|
||||
./backend
|
||||
./pkg/cronutil
|
||||
./pkg/error
|
||||
./pkg/geoip
|
||||
./pkg/model
|
||||
./pkg/postgres
|
||||
./pkg/schema
|
||||
./pkg/transcoder
|
||||
./pkg/util
|
||||
)
|
||||
|
||||
replace (
|
||||
galaxy/cronutil v0.0.0 => ./pkg/cronutil
|
||||
galaxy/error v0.0.0 => ./pkg/error
|
||||
galaxy/geoip v0.0.0 => ./pkg/geoip
|
||||
galaxy/model v0.0.0 => ./pkg/model
|
||||
galaxy/postgres v0.0.0 => ./pkg/postgres
|
||||
galaxy/schema v0.0.0 => ./pkg/schema
|
||||
galaxy/transcoder v0.0.0 => ./pkg/transcoder
|
||||
galaxy/util v0.0.0 => ./pkg/util
|
||||
)
|
||||
EOF
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
go build -o /out/backend ./backend/cmd/backend
|
||||
|
||||
FROM alpine:3.20 AS runtime
|
||||
|
||||
LABEL org.opencontainers.image.title="galaxy-backend-local-dev"
|
||||
|
||||
RUN apk add --no-cache wget ca-certificates
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8081
|
||||
|
||||
COPY --from=builder /out/backend /usr/local/bin/backend
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/backend"]
|
||||
@@ -0,0 +1,186 @@
|
||||
# Local development stack for the Galaxy UI.
|
||||
#
|
||||
# Brings up postgres + redis + mailpit + backend + gateway so the UI
|
||||
# Vite dev server (run on the host with `pnpm -C ui/frontend dev`) can
|
||||
# talk to a real authenticated stack without any cloud dependency.
|
||||
#
|
||||
# Browser: http://localhost:8080 (gateway public REST + Connect-Web)
|
||||
# Mailpit UI: http://localhost:8025
|
||||
# Postgres: localhost:5433 (host-mapped)
|
||||
# Redis: localhost:6380 (host-mapped)
|
||||
#
|
||||
# Bring up: make -C tools/local-dev up
|
||||
# Tear down: make -C tools/local-dev down
|
||||
# Wipe state: make -C tools/local-dev clean
|
||||
#
|
||||
# The backend reads `BACKEND_AUTH_DEV_FIXED_CODE=123456` from the
|
||||
# `.env` file alongside this compose; ConfirmEmailCode accepts that
|
||||
# literal in addition to the real bcrypt-verified code, so a developer
|
||||
# can log in without touching Mailpit. Real codes still arrive in
|
||||
# Mailpit; both paths coexist.
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: galaxy-local-dev-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: galaxy
|
||||
POSTGRES_PASSWORD: galaxy
|
||||
POSTGRES_DB: galaxy_backend
|
||||
ports:
|
||||
- "5433:5432"
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- galaxy-net
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U galaxy -d galaxy_backend"]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 30
|
||||
start_period: 5s
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: galaxy-local-dev-redis
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- redis-server
|
||||
- --requirepass
|
||||
- galaxy-dev
|
||||
- --appendonly
|
||||
- "no"
|
||||
- --save
|
||||
- ""
|
||||
ports:
|
||||
- "6380:6379"
|
||||
networks:
|
||||
- galaxy-net
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "galaxy-dev", "PING"]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 30
|
||||
start_period: 3s
|
||||
|
||||
mailpit:
|
||||
image: axllent/mailpit:v1.21
|
||||
container_name: galaxy-local-dev-mailpit
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8025:8025"
|
||||
networks:
|
||||
- galaxy-net
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O-", "http://localhost:8025/livez"]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 30
|
||||
start_period: 3s
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: tools/local-dev/backend.Dockerfile
|
||||
image: galaxy/backend:local-dev
|
||||
container_name: galaxy-local-dev-backend
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
mailpit:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
BACKEND_LOGGING_LEVEL: debug
|
||||
BACKEND_HTTP_LISTEN_ADDR: ":8080"
|
||||
BACKEND_GRPC_PUSH_LISTEN_ADDR: ":8081"
|
||||
BACKEND_POSTGRES_DSN: "postgres://galaxy:galaxy@postgres:5432/galaxy_backend?search_path=backend&sslmode=disable"
|
||||
BACKEND_SMTP_HOST: mailpit
|
||||
BACKEND_SMTP_PORT: "1025"
|
||||
BACKEND_SMTP_FROM: "galaxy-backend@galaxy.local"
|
||||
BACKEND_SMTP_TLS_MODE: none
|
||||
BACKEND_DOCKER_NETWORK: galaxy-local-dev-net
|
||||
BACKEND_GAME_STATE_ROOT: /var/lib/galaxy/game-state
|
||||
BACKEND_GEOIP_DB_PATH: /var/lib/galaxy/geoip.mmdb
|
||||
BACKEND_NOTIFICATION_ADMIN_EMAIL: admin@galaxy.local
|
||||
BACKEND_AUTH_CHALLENGE_THROTTLE_MAX: "100"
|
||||
BACKEND_MAIL_WORKER_INTERVAL: 500ms
|
||||
BACKEND_NOTIFICATION_WORKER_INTERVAL: 500ms
|
||||
BACKEND_OTEL_TRACES_EXPORTER: none
|
||||
BACKEND_OTEL_METRICS_EXPORTER: none
|
||||
BACKEND_AUTH_DEV_FIXED_CODE: ${BACKEND_AUTH_DEV_FIXED_CODE:-}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- game-state:/var/lib/galaxy/game-state
|
||||
- ../../pkg/geoip/test-data/test-data/GeoIP2-Country-Test.mmdb:/var/lib/galaxy/geoip.mmdb:ro
|
||||
networks:
|
||||
- galaxy-net
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O-", "http://localhost:8080/healthz"]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 60
|
||||
start_period: 10s
|
||||
|
||||
gateway:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: tools/local-dev/gateway.Dockerfile
|
||||
image: galaxy/gateway:local-dev
|
||||
container_name: galaxy-local-dev-gateway
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
GATEWAY_LOG_LEVEL: debug
|
||||
GATEWAY_PUBLIC_HTTP_ADDR: ":8080"
|
||||
GATEWAY_AUTHENTICATED_GRPC_ADDR: ":9090"
|
||||
GATEWAY_BACKEND_HTTP_URL: "http://backend:8080"
|
||||
GATEWAY_BACKEND_GRPC_PUSH_URL: "backend:8081"
|
||||
GATEWAY_BACKEND_GATEWAY_CLIENT_ID: local-dev-gateway-1
|
||||
GATEWAY_RESPONSE_SIGNER_PRIVATE_KEY_PEM_PATH: /run/secrets/gateway-response.pem
|
||||
GATEWAY_REDIS_MASTER_ADDR: "redis:6379"
|
||||
GATEWAY_REDIS_PASSWORD: galaxy-dev
|
||||
# Loosen anti-abuse so a developer hammering the form does not
|
||||
# rate-limit themselves between cycles.
|
||||
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_AUTH_RATE_LIMIT_REQUESTS: "10000"
|
||||
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_AUTH_RATE_LIMIT_BURST: "1000"
|
||||
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_SEND_EMAIL_CODE_IDENTITY_RATE_LIMIT_REQUESTS: "10000"
|
||||
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_SEND_EMAIL_CODE_IDENTITY_RATE_LIMIT_BURST: "1000"
|
||||
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_CONFIRM_EMAIL_CODE_IDENTITY_RATE_LIMIT_REQUESTS: "10000"
|
||||
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_CONFIRM_EMAIL_CODE_IDENTITY_RATE_LIMIT_BURST: "1000"
|
||||
GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_IP_RATE_LIMIT_REQUESTS: "10000"
|
||||
GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_IP_RATE_LIMIT_BURST: "1000"
|
||||
GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_SESSION_RATE_LIMIT_REQUESTS: "10000"
|
||||
GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_SESSION_RATE_LIMIT_BURST: "1000"
|
||||
GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_USER_RATE_LIMIT_REQUESTS: "10000"
|
||||
GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_USER_RATE_LIMIT_BURST: "1000"
|
||||
GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_MESSAGE_CLASS_RATE_LIMIT_REQUESTS: "10000"
|
||||
GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_MESSAGE_CLASS_RATE_LIMIT_BURST: "1000"
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./keys/gateway-response.pem:/run/secrets/gateway-response.pem:ro
|
||||
networks:
|
||||
- galaxy-net
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O-", "http://localhost:8080/healthz"]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 30
|
||||
start_period: 5s
|
||||
|
||||
networks:
|
||||
galaxy-net:
|
||||
name: galaxy-local-dev-net
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
name: galaxy-local-dev-postgres-data
|
||||
game-state:
|
||||
name: galaxy-local-dev-game-state
|
||||
@@ -0,0 +1,74 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
#
|
||||
# Local-dev image for the gateway service. Mirrors `gateway/Dockerfile`
|
||||
# (the integration/production image) but switches the runtime stage to
|
||||
# alpine so docker-compose healthchecks can shell out to `wget`.
|
||||
#
|
||||
# Build via the local-dev compose: `make -C tools/local-dev up`. The
|
||||
# build context is the repository root.
|
||||
|
||||
FROM golang:1.26.2-alpine AS builder
|
||||
WORKDIR /src
|
||||
ENV CGO_ENABLED=0 GOFLAGS=-trimpath
|
||||
|
||||
COPY pkg/cronutil/ ./pkg/cronutil/
|
||||
COPY pkg/error/ ./pkg/error/
|
||||
COPY pkg/geoip/ ./pkg/geoip/
|
||||
COPY pkg/model/ ./pkg/model/
|
||||
COPY pkg/postgres/ ./pkg/postgres/
|
||||
COPY pkg/redisconn/ ./pkg/redisconn/
|
||||
COPY pkg/schema/ ./pkg/schema/
|
||||
COPY pkg/transcoder/ ./pkg/transcoder/
|
||||
COPY pkg/util/ ./pkg/util/
|
||||
COPY ui/core/ ./ui/core/
|
||||
COPY backend/ ./backend/
|
||||
COPY gateway/ ./gateway/
|
||||
|
||||
RUN <<'EOF' cat > go.work
|
||||
go 1.26.2
|
||||
|
||||
use (
|
||||
./backend
|
||||
./gateway
|
||||
./pkg/cronutil
|
||||
./pkg/error
|
||||
./pkg/geoip
|
||||
./pkg/model
|
||||
./pkg/postgres
|
||||
./pkg/redisconn
|
||||
./pkg/schema
|
||||
./pkg/transcoder
|
||||
./pkg/util
|
||||
./ui/core
|
||||
)
|
||||
|
||||
replace (
|
||||
galaxy/cronutil v0.0.0 => ./pkg/cronutil
|
||||
galaxy/error v0.0.0 => ./pkg/error
|
||||
galaxy/geoip v0.0.0 => ./pkg/geoip
|
||||
galaxy/model v0.0.0 => ./pkg/model
|
||||
galaxy/postgres v0.0.0 => ./pkg/postgres
|
||||
galaxy/redisconn v0.0.0 => ./pkg/redisconn
|
||||
galaxy/schema v0.0.0 => ./pkg/schema
|
||||
galaxy/transcoder v0.0.0 => ./pkg/transcoder
|
||||
galaxy/util v0.0.0 => ./pkg/util
|
||||
galaxy/core v0.0.0 => ./ui/core
|
||||
)
|
||||
EOF
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
go build -o /out/gateway ./gateway/cmd/gateway
|
||||
|
||||
FROM alpine:3.20 AS runtime
|
||||
|
||||
LABEL org.opencontainers.image.title="galaxy-gateway-local-dev"
|
||||
|
||||
RUN apk add --no-cache wget ca-certificates
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 9090
|
||||
|
||||
COPY --from=builder /out/gateway /usr/local/bin/gateway
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/gateway"]
|
||||
@@ -0,0 +1,34 @@
|
||||
# `tools/local-dev/keys/`
|
||||
|
||||
DEV-ONLY cryptographic material used by the `tools/local-dev/` stack.
|
||||
|
||||
**Never use any key in this directory in a non-local environment.**
|
||||
|
||||
## Files
|
||||
|
||||
- `gateway-response.pem` — gateway response-signing private key, PKCS#8
|
||||
PEM, Ed25519. Mounted into the gateway container at
|
||||
`/run/secrets/gateway-response.pem` and pointed to via
|
||||
`GATEWAY_RESPONSE_SIGNER_PRIVATE_KEY_PEM_PATH`.
|
||||
- `gateway-response.pub` — matching raw 32-byte public key, standard
|
||||
base64. Copied verbatim into `ui/frontend/.env.development` as
|
||||
`VITE_GATEWAY_RESPONSE_PUBLIC_KEY`.
|
||||
|
||||
## Regenerating
|
||||
|
||||
The keypair is committed because it must be deterministic across
|
||||
developer checkouts (the UI's `.env.development` ships the exact
|
||||
base64 of the public half). Rotate only when a leak is suspected; the
|
||||
keys never reach a non-local environment in normal operation.
|
||||
|
||||
To regenerate from a Go one-shot:
|
||||
|
||||
```sh
|
||||
cd tools/local-dev/keys
|
||||
go run ./regenerate.go
|
||||
```
|
||||
|
||||
The helper writes a fresh PEM, prints the matching public-key base64,
|
||||
and updates `gateway-response.pub`. After regeneration, copy the new
|
||||
`VITE_GATEWAY_RESPONSE_PUBLIC_KEY` value from `gateway-response.pub`
|
||||
into `ui/frontend/.env.development` and commit both changes together.
|
||||
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIHqW94EpSePdiujbP1Wh1GIz+vuDnFU8HDeFfaNwcovi
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,4 @@
|
||||
# DEV-ONLY gateway response-signing public key (raw 32-byte Ed25519,
|
||||
# standard non-URL-safe base64). Pairs with `gateway-response.pem`.
|
||||
# Never use in any non-local environment.
|
||||
nIG54tCuNiIKrazt8Hh7YxmmU/BhpseGhIIgj164Chw=
|
||||
@@ -0,0 +1,47 @@
|
||||
// Regenerate `gateway-response.pem` and `gateway-response.pub`.
|
||||
//
|
||||
// Run from this directory: `go run ./regenerate.go`. The keys are
|
||||
// committed and used only by the `tools/local-dev/` stack; rotate by
|
||||
// re-running and committing both files together with the matching
|
||||
// `VITE_GATEWAY_RESPONSE_PUBLIC_KEY` update in
|
||||
// `ui/frontend/.env.development`.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "generate:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pkcs8, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "marshal:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8})
|
||||
if err := os.WriteFile("gateway-response.pem", pemBytes, 0o600); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "write pem:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
pubB64 := base64.StdEncoding.EncodeToString(pub)
|
||||
pubBlock := fmt.Sprintf("# DEV-ONLY gateway response-signing public key (raw 32-byte Ed25519,\n# standard non-URL-safe base64). Pairs with `gateway-response.pem`.\n# Never use in any non-local environment.\n%s\n", pubB64)
|
||||
if err := os.WriteFile("gateway-response.pub", []byte(pubBlock), 0o644); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "write pub:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("VITE_GATEWAY_RESPONSE_PUBLIC_KEY=%s\n", pubB64)
|
||||
}
|
||||
Reference in New Issue
Block a user