Files
galaxy-game/tools/dev-deploy/Makefile
T
Ilia Denisov f70258849f fix(dev-deploy): seed geoip onto a named volume
`docker restart galaxy-dev-backend` failed with "not a directory"
after every dev-deploy workflow run. Root cause: the compose file
bind-mounted the geoip database via a relative path
(`../../pkg/geoip/test-data/test-data/GeoIP2-Country-Test.mmdb`).
When the Gitea runner invoked `docker compose up`, the path
resolved against the runner's ephemeral workspace under
`/home/runner/.cache/act/<hash>/hostexecutor/...`. The bind source
baked into the running container therefore pointed at that
ephemeral path; the runner deleted the workspace once the workflow
finished, and any later `docker restart` could not remount.

Replace the bind with a named volume `galaxy-dev-geoip-data`,
seeded at deploy time:

- `tools/dev-deploy/docker-compose.yml`: mount
  `galaxy-dev-geoip-data:/var/lib/galaxy:ro` instead of a relative
  bind. Declare the volume in the top-level `volumes:` block.

- `.gitea/workflows/dev-deploy.yaml`: new `Seed geoip volume` step
  (placed right after the existing UI-volume seed) copies the
  fixture from `pkg/geoip/test-data/test-data/` into the named
  volume via an ephemeral alpine container, the same pattern UI
  seeding already uses.

- `tools/dev-deploy/Makefile`: new `seed-geoip` target performs
  the same copy from the persistent checkout. `up` and `rebuild`
  now depend on it, so a hand-run `make -C tools/dev-deploy up`
  populates the volume without operator action.

- `tools/dev-deploy/README.md`: updated the make-targets table to
  list `seed-geoip`.

- `tools/dev-deploy/KNOWN-ISSUES.md`: the entry for the restart
  failure is downgraded to a "fixed" postmortem; the symptom,
  cause, and where the fix lives are kept for future reference.

Verification on the dev host (this branch checked out):

  $ make -C tools/dev-deploy up                # populates the volume, brings stack healthy
  $ docker restart galaxy-dev-backend          # used to error "not a directory"
  $ until [ "$(docker inspect -f '{{.State.Health.Status}}' galaxy-dev-backend)" = "healthy" ]; do sleep 2; done
  $ echo "ok"                                   # backend up 6s, healthy

The pre-existing sandbox engine `galaxy-game-80f3ce86-...` survived
both `make up` and `docker restart` untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 01:59:38 +02:00

124 lines
5.1 KiB
Makefile

.PHONY: help up down rebuild logs status clean-data health psql build-engine seed-ui seed-geoip
.DEFAULT_GOAL := help
REPO_ROOT := $(realpath $(CURDIR)/../..)
ENGINE_IMAGE := galaxy-engine:dev
STACK_LABEL := galaxy.stack=dev-deploy
ENGINE_LABEL := org.opencontainers.image.title=galaxy-game-engine
# Game-state root lives under the invoking user's home by default so
# `make up` works without sudo. Override `GALAXY_DEV_GAME_STATE_DIR`
# in the environment or `.env` to relocate (e.g. /var/lib/galaxy-dev/
# game-state in a production-shaped host). The value flows through to
# both the compose bind-mount and the backend's
# `BACKEND_GAME_STATE_ROOT`.
export GALAXY_DEV_GAME_STATE_DIR ?= $(HOME)/.galaxy-dev/game-state
COMPOSE := docker compose
help:
@echo "Long-lived Galaxy dev environment (https://*.galaxy.lan):"
@echo " make up Build images, ensure engine image, seed geoip, bring stack up"
@echo " make rebuild Force rebuild of backend / gateway images and bring up"
@echo " make build-engine Build $(ENGINE_IMAGE) from game/Dockerfile (no-op if present)"
@echo " make seed-ui Build ui/frontend and load into galaxy-dev-ui-dist volume"
@echo " make seed-geoip Copy GeoIP fixture into galaxy-dev-geoip-data volume"
@echo " make down Stop containers, keep named volumes"
@echo " make logs Tail all logs"
@echo " make status docker compose ps"
@echo " make health Probe the stack through the host Caddy"
@echo " make psql Open a psql shell as galaxy@galaxy_backend"
@echo " make clean-data Stop everything and wipe named volumes + game-state"
@echo ""
@echo "Requires:"
@echo " - external Docker network '$${GALAXY_EDGE_NETWORK:-edge}'"
@echo " (docker network create edge)"
@echo " - host Caddy proxying *.galaxy.lan into that network"
@echo " - game-state dir: $(GALAXY_DEV_GAME_STATE_DIR) (auto-created)"
up: build-engine seed-geoip
mkdir -p "$(GALAXY_DEV_GAME_STATE_DIR)"
$(COMPOSE) up -d --wait
rebuild: build-engine seed-geoip
$(COMPOSE) build --no-cache galaxy-backend galaxy-api
mkdir -p "$(GALAXY_DEV_GAME_STATE_DIR)"
$(COMPOSE) up -d --wait
build-engine:
@if docker image inspect $(ENGINE_IMAGE) >/dev/null 2>&1; then \
echo "$(ENGINE_IMAGE) already built; skipping (use 'docker rmi $(ENGINE_IMAGE)' to force a rebuild)."; \
else \
echo "building $(ENGINE_IMAGE)"; \
docker build -t $(ENGINE_IMAGE) -f $(REPO_ROOT)/game/Dockerfile $(REPO_ROOT); \
fi
# Copy the GeoIP fixture into a named volume the backend mounts as
# /var/lib/galaxy. Using a volume avoids a bind-mount that would
# resolve against an ephemeral workspace path when compose is driven
# from the Gitea runner (see tools/dev-deploy/KNOWN-ISSUES.md for the
# breakage that bind-mounts caused on `docker restart`).
seed-geoip:
@echo "seeding GeoIP fixture into galaxy-dev-geoip-data…"
docker volume create galaxy-dev-geoip-data >/dev/null
docker run --rm \
-v galaxy-dev-geoip-data:/dst \
-v $(REPO_ROOT)/pkg/geoip/test-data/test-data:/src:ro \
alpine sh -c 'cp /src/GeoIP2-Country-Test.mmdb /dst/geoip.mmdb'
# Build the UI frontend and load the resulting build/ directory into
# the named volume Caddy serves from. Used by the dev-deploy workflow
# and by anyone bringing the stack up by hand.
seed-ui:
@if [ ! -d $(REPO_ROOT)/ui/frontend/node_modules ]; then \
echo "installing UI dependencies…"; \
(cd $(REPO_ROOT)/ui && pnpm install --frozen-lockfile); \
fi
@echo "building UI (vite build)…"
(cd $(REPO_ROOT)/ui/frontend && \
VITE_GATEWAY_BASE_URL=https://api.galaxy.lan \
VITE_GALAXY_DEV_AFFORDANCES=true \
VITE_GATEWAY_RESPONSE_PUBLIC_KEY=$$(cat $(REPO_ROOT)/ui/frontend/.env.development \
| sed -n 's/^VITE_GATEWAY_RESPONSE_PUBLIC_KEY=//p') \
pnpm build)
@echo "loading build/ into galaxy-dev-ui-dist volume…"
docker volume create galaxy-dev-ui-dist >/dev/null
docker run --rm \
-v galaxy-dev-ui-dist:/dst \
-v $(REPO_ROOT)/ui/frontend/build:/src:ro \
alpine sh -c 'rm -rf /dst/* /dst/.??* 2>/dev/null; cp -a /src/. /dst/'
down:
$(COMPOSE) down
logs:
$(COMPOSE) logs -f --tail=100
status:
$(COMPOSE) ps
health:
@echo "Frontend (https://www.galaxy.lan):"
@curl -sS -o /dev/null -w " HTTP %{http_code}\n" https://www.galaxy.lan/ || echo " unreachable"
@echo "API healthz (https://api.galaxy.lan/healthz):"
@curl -sS -o /dev/null -w " HTTP %{http_code}\n" https://api.galaxy.lan/healthz || echo " unreachable"
psql:
$(COMPOSE) exec galaxy-postgres psql -U galaxy -d galaxy_backend
clean-data:
@echo "Stopping containers and engines, then wiping volumes + game-state…"
$(COMPOSE) down -v
@ids=$$(docker ps -aq \
--filter "label=$(STACK_LABEL)" \
--filter "label=$(ENGINE_LABEL)"); \
if [ -n "$$ids" ]; then \
echo "stopping engine containers for $(STACK_LABEL)"; \
docker rm -f $$ids >/dev/null; \
fi
@if [ -d "$(GALAXY_DEV_GAME_STATE_DIR)" ]; then \
echo "wiping $(GALAXY_DEV_GAME_STATE_DIR)"; \
docker run --rm -v "$(GALAXY_DEV_GAME_STATE_DIR):/state" alpine sh -c 'rm -rf /state/*' 2>/dev/null \
|| rm -rf "$(GALAXY_DEV_GAME_STATE_DIR)"/* 2>/dev/null || true; \
fi