chore(ci): tidy CI/dev infra — drop local-ci, lift migration rule, scope by galaxy.stack label
Five connected cleanups across the dev/CI infrastructure:
1. Drop tools/local-ci/. The standalone Gitea + act_runner stack was
the legacy "offline workflow validator"; the per-stage CI gate now
runs on gitea.lan and the directory was only retained as a
fallback. Removing it leaves no operational dependency: backend,
gateway, and game code have no references; documentation that
pointed at it (CLAUDE.md, docs/ARCHITECTURE.md, ui/docs/testing.md,
tools/dev-deploy/README.md, tools/local-dev/README.md) is updated
in this same change. Historical "Verified on local-ci run N"
markers in ui/PLAN.md are preserved unchanged.
2. Lift the pre-production single-migration rule. The rule forced
every schema delta into 00001_init.sql and required a manual
make clean-data wipe on every backward-incompatible change in
tools/dev-deploy/. Future schema deltas now land as additive
sequence-numbered files (00002_*.sql, …) that goose applies
automatically on backend startup; 00001_init.sql becomes an
immutable baseline. Authoring conventions live in
backend/internal/postgres/migrations/README.md. The chain may be
squashed back into a fresh 00001 as a deliberate one-time
operation before the first production deployment.
3. Document the deployment cadence. The dev environment is
single-tenant: pushes to feature/* run the test workflows
(go-unit, ui-test, integration) only; dev-deploy.yaml fires on
push to development. A workflow_dispatch override on
dev-deploy.yaml lets a developer preview a feature branch on the
shared dev environment before merge; the next merge into
development overwrites the manual deploy idempotently.
4. Scope compose-managed resources by an explicit
galaxy.stack=<local-dev|dev-deploy> label. Both compose files
stamp the label on every service, network, and named volume.
Makefiles in tools/local-dev/ and tools/dev-deploy/ filter their
engine-cleanup operations by (stack-label AND engine OCI title)
so they never touch unrelated workloads on the same daemon.
dev-deploy.yaml gains a pre-`compose up` step that reaps stale
exited/dead containers under the dev-deploy stack label.
5. Backend now stamps the same galaxy.stack=<value> label on every
engine container it spawns, sourced from a new BACKEND_STACK_LABEL
env var (empty → label not applied; legacy-safe). Both compose
files set it to their stack name (local-dev / dev-deploy). The
contract is recorded in docs/ARCHITECTURE.md under
"Container labels". A package-level test in
backend/internal/runtime exercises both the label-present and
label-absent paths.
No tests intentionally regressed: go test ./backend/internal/{config,
runtime,dockerclient} is green, both compose files validate cleanly,
and the backend, gateway, and game modules all build.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
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`
|
||||
@@ -93,12 +94,14 @@ psql:
|
||||
|
||||
clean-data:
|
||||
@echo "Stopping containers and engines, then wiping volumes + game-state…"
|
||||
@ids=$$(docker ps -aq --filter label=$(ENGINE_LABEL)); \
|
||||
$(COMPOSE) down -v
|
||||
@ids=$$(docker ps -aq \
|
||||
--filter "label=$(STACK_LABEL)" \
|
||||
--filter "label=$(ENGINE_LABEL)"); \
|
||||
if [ -n "$$ids" ]; then \
|
||||
echo "stopping engine containers…"; \
|
||||
echo "stopping engine containers for $(STACK_LABEL)…"; \
|
||||
docker rm -f $$ids >/dev/null; \
|
||||
fi
|
||||
$(COMPOSE) down -v
|
||||
@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 \
|
||||
|
||||
@@ -135,17 +135,20 @@ exec galaxy-mailpit wget -qO- localhost:8025/messages` and similar.
|
||||
## Persistent state and schema changes
|
||||
|
||||
The dev Postgres volume `galaxy-dev-postgres-data` survives redeploys.
|
||||
Until the pre-production migration rule is lifted, every
|
||||
backward-incompatible change to `backend/internal/postgres/migrations/00001_init.sql`
|
||||
needs a manual wipe before the next deploy succeeds:
|
||||
Schema deltas land as additive, sequence-numbered migration files
|
||||
(`backend/internal/postgres/migrations/0000N_*.sql`) and `pressly/goose`
|
||||
applies them on backend startup without operator action.
|
||||
|
||||
Use `make -C tools/dev-deploy clean-data` only when you deliberately
|
||||
want a fresh database (debugging schema drift, exercising the
|
||||
bootstrap path from scratch, etc.):
|
||||
|
||||
```sh
|
||||
make -C tools/dev-deploy clean-data
|
||||
make -C tools/dev-deploy up
|
||||
```
|
||||
|
||||
This is the same caveat as `tools/local-dev/`, just with a different
|
||||
volume name.
|
||||
The same volume-persistence model applies to `tools/local-dev/`.
|
||||
|
||||
## Make targets
|
||||
|
||||
@@ -183,13 +186,30 @@ See [`KNOWN-ISSUES.md`](KNOWN-ISSUES.md) for symptoms that surface
|
||||
in the long-lived dev environment but are not yet fixed (currently:
|
||||
the sandbox game flipping to `cancelled` after a redispatch).
|
||||
|
||||
## Deployment cadence
|
||||
|
||||
This environment is single-tenant: one live deployment, redeployed by
|
||||
the `dev-deploy.yaml` workflow on every merge into `development`. PR
|
||||
branches do not auto-deploy here — pushes to `feature/*` only run the
|
||||
test workflows (`go-unit`, `ui-test`, `integration`).
|
||||
|
||||
To put a feature branch on the shared dev environment before its PR
|
||||
merges (e.g. to validate a UI flow against the real Caddy edge), run
|
||||
the workflow manually:
|
||||
|
||||
1. Push the branch (`git push gitea HEAD`).
|
||||
2. Gitea UI → **Actions → Deploy · Dev → Run workflow**, pick the
|
||||
feature ref.
|
||||
|
||||
The deploy is idempotent — when the PR later merges into
|
||||
`development`, the regular push trigger fires the same packaging and
|
||||
healthcheck steps, overwriting whatever the manual dispatch left
|
||||
behind. There is no separate state to clean up between the two paths.
|
||||
|
||||
## Relationship to other infrastructure
|
||||
|
||||
- `tools/local-dev/` — single-developer playground, host-port mapped,
|
||||
Vite dev server on the side. Recommended for active UI work.
|
||||
- `tools/local-ci/` — Gitea + act runner for **fallback** workflow
|
||||
testing without `gitea.lan`. Optional, not part of the per-stage CI
|
||||
gate anymore.
|
||||
- `.gitea/workflows/dev-deploy.yaml` — the CI side of this stack:
|
||||
builds images, seeds the UI volume, runs `docker compose up -d` on
|
||||
every merge into `development`. The Makefile in this directory is
|
||||
|
||||
@@ -22,6 +22,8 @@ services:
|
||||
image: postgres:16-alpine
|
||||
container_name: galaxy-dev-postgres
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
galaxy.stack: dev-deploy
|
||||
environment:
|
||||
POSTGRES_USER: galaxy
|
||||
POSTGRES_PASSWORD: galaxy
|
||||
@@ -41,6 +43,8 @@ services:
|
||||
image: redis:7-alpine
|
||||
container_name: galaxy-dev-redis
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
galaxy.stack: dev-deploy
|
||||
command:
|
||||
- redis-server
|
||||
- --requirepass
|
||||
@@ -62,6 +66,8 @@ services:
|
||||
image: axllent/mailpit:v1.21
|
||||
container_name: galaxy-dev-mailpit
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
galaxy.stack: dev-deploy
|
||||
networks:
|
||||
- galaxy-internal
|
||||
healthcheck:
|
||||
@@ -78,6 +84,8 @@ services:
|
||||
image: galaxy/backend:dev
|
||||
container_name: galaxy-dev-backend
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
galaxy.stack: dev-deploy
|
||||
user: "0:0"
|
||||
depends_on:
|
||||
galaxy-postgres:
|
||||
@@ -94,6 +102,7 @@ services:
|
||||
BACKEND_SMTP_FROM: "galaxy-backend@galaxy.lan"
|
||||
BACKEND_SMTP_TLS_MODE: none
|
||||
BACKEND_DOCKER_NETWORK: galaxy-dev-internal
|
||||
BACKEND_STACK_LABEL: dev-deploy
|
||||
BACKEND_GAME_STATE_ROOT: ${GALAXY_DEV_GAME_STATE_DIR}
|
||||
BACKEND_GEOIP_DB_PATH: /var/lib/galaxy/geoip.mmdb
|
||||
BACKEND_NOTIFICATION_ADMIN_EMAIL: admin@galaxy.lan
|
||||
@@ -152,6 +161,8 @@ services:
|
||||
image: galaxy/gateway:dev
|
||||
container_name: galaxy-dev-api
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
galaxy.stack: dev-deploy
|
||||
depends_on:
|
||||
galaxy-backend:
|
||||
condition: service_healthy
|
||||
@@ -209,6 +220,8 @@ services:
|
||||
image: caddy:2.11.2-alpine
|
||||
container_name: galaxy-dev-caddy
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
galaxy.stack: dev-deploy
|
||||
depends_on:
|
||||
galaxy-api:
|
||||
condition: service_healthy
|
||||
@@ -225,6 +238,8 @@ networks:
|
||||
name: galaxy-dev-internal
|
||||
driver: bridge
|
||||
internal: false
|
||||
labels:
|
||||
galaxy.stack: dev-deploy
|
||||
edge:
|
||||
name: ${GALAXY_EDGE_NETWORK:-edge}
|
||||
external: true
|
||||
@@ -232,7 +247,13 @@ networks:
|
||||
volumes:
|
||||
galaxy-dev-postgres-data:
|
||||
name: galaxy-dev-postgres-data
|
||||
labels:
|
||||
galaxy.stack: dev-deploy
|
||||
galaxy-dev-caddy-data:
|
||||
name: galaxy-dev-caddy-data
|
||||
labels:
|
||||
galaxy.stack: dev-deploy
|
||||
galaxy-dev-ui-dist:
|
||||
name: galaxy-dev-ui-dist
|
||||
labels:
|
||||
galaxy.stack: dev-deploy
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
.env
|
||||
@@ -1,42 +0,0 @@
|
||||
.PHONY: help up down logs status clean push
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
COMPOSE := docker compose
|
||||
GITEA_USER := galaxy
|
||||
GITEA_PASS := galaxy-dev
|
||||
REPO_NAME := galaxy
|
||||
REMOTE_NAME := local-gitea
|
||||
REPO_ROOT := $(realpath $(CURDIR)/../..)
|
||||
GIT := git -C $(REPO_ROOT)
|
||||
REMOTE_URL := http://$(GITEA_USER):$(GITEA_PASS)@localhost:3000/$(GITEA_USER)/$(REPO_NAME).git
|
||||
|
||||
help:
|
||||
@echo "Local Gitea CI for galaxy:"
|
||||
@echo " make up Bring up Gitea + runner (idempotent)"
|
||||
@echo " make down Stop both containers"
|
||||
@echo " make logs Tail logs"
|
||||
@echo " make status Show container status"
|
||||
@echo " make push Push current branch to local Gitea"
|
||||
@echo " make clean Stop and wipe all local state"
|
||||
|
||||
up:
|
||||
@./bootstrap.sh
|
||||
|
||||
down:
|
||||
$(COMPOSE) down
|
||||
|
||||
logs:
|
||||
$(COMPOSE) logs -f --tail=50
|
||||
|
||||
status:
|
||||
$(COMPOSE) ps
|
||||
|
||||
push:
|
||||
@$(GIT) remote get-url $(REMOTE_NAME) >/dev/null 2>&1 || \
|
||||
$(GIT) remote add $(REMOTE_NAME) $(REMOTE_URL)
|
||||
$(GIT) push $(REMOTE_NAME) HEAD
|
||||
|
||||
clean:
|
||||
$(COMPOSE) down -v
|
||||
rm -f .env
|
||||
@@ -1,106 +0,0 @@
|
||||
# Local Gitea CI (fallback)
|
||||
|
||||
> **Status:** fallback / opt-in. The primary CI target is now
|
||||
> `gitea.lan` with its host-mode `act_runner`. The per-stage CI gate
|
||||
> closes against `gitea.lan`, not against this stack. Use this
|
||||
> directory when you want to validate `.gitea/workflows/*` without
|
||||
> reaching `gitea.lan` — for example, when iterating on a workflow
|
||||
> file from a flight without LAN access — or when isolating a runner
|
||||
> issue from production-shaped infrastructure.
|
||||
|
||||
Self-contained Gitea + Actions runner for verifying
|
||||
`.gitea/workflows/*` honestly before pushing to `gitea.lan`. Runs
|
||||
natively on arm64 (Apple Silicon) — every image below has an arm64
|
||||
variant, so Docker pulls the right architecture and the runner
|
||||
executes workflow steps without QEMU emulation.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker (Colima or Docker Desktop)
|
||||
- `python3`, `curl`, `bash` — all built into macOS
|
||||
|
||||
## First time
|
||||
|
||||
```sh
|
||||
make -C tools/local-ci up
|
||||
```
|
||||
|
||||
This:
|
||||
|
||||
1. brings up the Gitea container;
|
||||
2. creates an admin user (`galaxy` / `galaxy-dev`);
|
||||
3. creates the `galaxy/galaxy` repo;
|
||||
4. fetches a runner registration token from the Gitea API;
|
||||
5. brings up the runner with that token (the runner persists its
|
||||
credentials in a Docker volume and ignores the token on subsequent
|
||||
restarts).
|
||||
|
||||
The script is idempotent — re-running it is safe.
|
||||
|
||||
## Pushing a branch
|
||||
|
||||
```sh
|
||||
make -C tools/local-ci push
|
||||
```
|
||||
|
||||
This adds a `local-gitea` remote on the first run and then pushes the
|
||||
current `HEAD`. Equivalent manual flow:
|
||||
|
||||
```sh
|
||||
git remote add local-gitea \
|
||||
http://galaxy:galaxy-dev@localhost:3000/galaxy/galaxy.git
|
||||
git push local-gitea HEAD
|
||||
```
|
||||
|
||||
The Tier 1 workflow fires on `push` to any branch and the Tier 2
|
||||
workflow fires on tags matching `v*`. Watch runs at:
|
||||
|
||||
<http://localhost:3000/galaxy/galaxy/actions>
|
||||
|
||||
## Operational targets
|
||||
|
||||
| Target | What it does |
|
||||
| ---------------- | -------------------------------------------- |
|
||||
| `make up` | Bring up Gitea + runner (idempotent) |
|
||||
| `make down` | Stop both containers (state preserved) |
|
||||
| `make logs` | Tail logs from both containers |
|
||||
| `make status` | Show container status |
|
||||
| `make push` | Push current `HEAD` to local Gitea |
|
||||
| `make clean` | Stop and wipe all local state (full reset) |
|
||||
|
||||
## What's in the box
|
||||
|
||||
| Component | Image | Role |
|
||||
| ---------- | ---------------------------------- | ------------------------------------------- |
|
||||
| Gitea | `gitea/gitea:1.23` | Server with SQLite backend |
|
||||
| act_runner | `gitea/act_runner:0.6.1` | Single-capacity runner registered on boot |
|
||||
| Workflow | `catthehacker/ubuntu:act-latest` | Image spawned per job (multi-arch) |
|
||||
|
||||
The runner mounts the host Docker socket and spawns workflow
|
||||
containers on the same Docker network as Gitea, so
|
||||
`actions/checkout` reaches the server at `http://gitea:3000` from
|
||||
inside spawned containers.
|
||||
|
||||
## Caveats
|
||||
|
||||
- Gitea's `ROOT_URL` is set to `http://gitea:3000/` so spawned
|
||||
workflow containers reach the server through the compose network.
|
||||
The web UI works at `http://localhost:3000` via port mapping, but
|
||||
copy-paste URLs in the UI may show `gitea:3000` instead of
|
||||
`localhost:3000`. Harmless for local dev; switch the host part by
|
||||
hand when copying.
|
||||
- The runner is single-capacity (`runner.capacity: 1` in
|
||||
`config.yaml`). Concurrent jobs queue. Bump if you need parallel
|
||||
jobs.
|
||||
- First push from a fresh checkout uploads the full repo history
|
||||
(~tens of MB). Subsequent pushes are deltas.
|
||||
- `actions/upload-artifact@v4` requires Gitea ≥ 1.21 — we pin
|
||||
`1.23` to stay above the cutoff.
|
||||
- Workflow steps run as `root` inside the spawned container; this
|
||||
matches the upstream catthehacker behaviour. Keep that in mind if
|
||||
you add steps that touch host-mounted directories.
|
||||
- On Apple Silicon the runner image and its catthehacker child run
|
||||
natively as arm64. Some pre-built tools that ship in the image are
|
||||
amd64-only and would fall back to QEMU; `setup-go`, `setup-node`,
|
||||
and `pnpm/action-setup` all download arm64 binaries themselves, so
|
||||
the workflow steps we care about stay native.
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Bring up Gitea, create the admin user and the galaxy/galaxy repo,
|
||||
# fetch a runner registration token, bring up the runner.
|
||||
# Idempotent — re-runnable.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
GITEA_USER=galaxy
|
||||
GITEA_PASS=galaxy-dev
|
||||
GITEA_EMAIL=galaxy@local
|
||||
REPO_NAME=galaxy
|
||||
GITEA_URL=http://localhost:3000
|
||||
|
||||
echo ">>> Bringing up Gitea..."
|
||||
docker compose up -d gitea
|
||||
|
||||
echo ">>> Waiting for Gitea API..."
|
||||
for _ in $(seq 1 120); do
|
||||
if curl -fsS "${GITEA_URL}/api/v1/version" >/dev/null 2>&1; then
|
||||
echo "Gitea is up."
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if ! curl -fsS "${GITEA_URL}/api/v1/version" >/dev/null 2>&1; then
|
||||
echo "Gitea did not come up within 120 seconds." >&2
|
||||
docker compose logs gitea | tail -30 >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ">>> Creating admin user (idempotent)..."
|
||||
docker compose exec -T gitea su git -c "
|
||||
gitea admin user create \
|
||||
--username ${GITEA_USER} \
|
||||
--password ${GITEA_PASS} \
|
||||
--email ${GITEA_EMAIL} \
|
||||
--admin \
|
||||
--must-change-password=false 2>&1 || true
|
||||
"
|
||||
|
||||
echo ">>> Creating repo (idempotent)..."
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' \
|
||||
-u "${GITEA_USER}:${GITEA_PASS}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"${REPO_NAME}\",\"private\":true,\"auto_init\":false}" \
|
||||
"${GITEA_URL}/api/v1/user/repos")
|
||||
case "${HTTP_CODE}" in
|
||||
201) echo "Repo created." ;;
|
||||
409) echo "Repo already exists." ;;
|
||||
*)
|
||||
echo "Unexpected response (${HTTP_CODE}) creating repo." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ">>> Fetching runner registration token..."
|
||||
RUNNER_TOKEN=$(curl -fsS \
|
||||
-u "${GITEA_USER}:${GITEA_PASS}" \
|
||||
"${GITEA_URL}/api/v1/admin/runners/registration-token" \
|
||||
| python3 -c "import json, sys; print(json.load(sys.stdin)['token'])")
|
||||
|
||||
# act_runner uses RUNNER_TOKEN only on the first boot. After registration
|
||||
# it persists credentials in the named runner-data volume (/data/.runner)
|
||||
# and ignores the env token on subsequent restarts. Writing a fresh token
|
||||
# every time is harmless.
|
||||
echo "RUNNER_TOKEN=${RUNNER_TOKEN}" > .env
|
||||
|
||||
echo ">>> Bringing up runner..."
|
||||
docker compose up -d runner
|
||||
|
||||
cat <<EOF
|
||||
|
||||
Done.
|
||||
|
||||
Push the current branch and watch a run:
|
||||
|
||||
cd $(cd ../.. && pwd)
|
||||
git remote add local-gitea http://${GITEA_USER}:${GITEA_PASS}@localhost:3000/${GITEA_USER}/${REPO_NAME}.git 2>/dev/null || true
|
||||
git push local-gitea HEAD
|
||||
|
||||
open http://localhost:3000/${GITEA_USER}/${REPO_NAME}/actions
|
||||
|
||||
Or use \`make push\` from this directory.
|
||||
EOF
|
||||
@@ -1,35 +0,0 @@
|
||||
# act_runner configuration.
|
||||
#
|
||||
# The `ubuntu-latest` label is mapped to catthehacker/ubuntu:act-latest,
|
||||
# which is multi-arch — Docker on Apple Silicon pulls the arm64 variant
|
||||
# and runs it natively (no QEMU). The same image is what `act` uses
|
||||
# locally, so workflows behave the same.
|
||||
|
||||
log:
|
||||
level: info
|
||||
|
||||
runner:
|
||||
file: /data/.runner
|
||||
capacity: 1
|
||||
fetch_timeout: 5s
|
||||
fetch_interval: 2s
|
||||
labels:
|
||||
- "ubuntu-latest:docker://catthehacker/ubuntu:act-latest"
|
||||
|
||||
cache:
|
||||
enabled: true
|
||||
dir: /data/cache
|
||||
|
||||
container:
|
||||
# Spawned workflow containers join the same network as Gitea so
|
||||
# actions/checkout and other steps can reach the server at
|
||||
# http://gitea:3000.
|
||||
network: galaxy-local-gitea-net
|
||||
privileged: false
|
||||
options: ""
|
||||
workdir_parent: ""
|
||||
valid_volumes: []
|
||||
force_pull: false
|
||||
|
||||
host:
|
||||
workdir_parent: ""
|
||||
@@ -1,16 +0,0 @@
|
||||
# Local-only override: this developer's host already runs another
|
||||
# Gitea instance bound to 0.0.0.0:3000 and 0.0.0.0:2222, so the
|
||||
# default port mappings in docker-compose.yml conflict. Remap the
|
||||
# local-ci Gitea to 13000 (HTTP) and 12222 (SSH) on the host. The
|
||||
# in-network ports stay 3000 / 22 — runners and workflow containers
|
||||
# keep reaching Gitea by hostname through the compose network.
|
||||
#
|
||||
# This file is intentionally NOT committed to the repo; it captures
|
||||
# per-host port allocation. Use `make -C tools/local-ci push` only
|
||||
# after pointing the `local-gitea` git remote at the override port.
|
||||
|
||||
services:
|
||||
gitea:
|
||||
ports: !override
|
||||
- "13000:3000"
|
||||
- "12222:22"
|
||||
@@ -1,78 +0,0 @@
|
||||
# Local Gitea + Actions runner for verifying .gitea/workflows/*.
|
||||
# Runs natively on arm64 (Apple Silicon) — every image below is multi-arch.
|
||||
#
|
||||
# Browser: http://localhost:3000
|
||||
# API: http://localhost:3000/api/v1
|
||||
# Push URL: http://galaxy:galaxy-dev@localhost:3000/galaxy/galaxy.git
|
||||
# Actions: http://localhost:3000/galaxy/galaxy/actions
|
||||
#
|
||||
# `bootstrap.sh` (or `make up`) brings everything up and registers the
|
||||
# runner. State persists in named Docker volumes; `make clean` wipes them.
|
||||
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:1.23
|
||||
container_name: galaxy-local-gitea
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
USER_UID: "1000"
|
||||
USER_GID: "1000"
|
||||
GITEA__database__DB_TYPE: sqlite3
|
||||
GITEA__database__PATH: /data/gitea/gitea.db
|
||||
# ROOT_URL uses the in-network hostname so the runner and spawned
|
||||
# workflow containers reach Gitea through the compose network.
|
||||
# The browser still works at http://localhost:3000 via the port
|
||||
# mapping below; UI-generated copy URLs may show "gitea:3000",
|
||||
# which is harmless for local dev.
|
||||
GITEA__server__ROOT_URL: http://gitea:3000/
|
||||
GITEA__server__SSH_PORT: "2222"
|
||||
GITEA__actions__ENABLED: "true"
|
||||
GITEA__security__INSTALL_LOCK: "true"
|
||||
GITEA__service__DISABLE_REGISTRATION: "true"
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "2222:22"
|
||||
volumes:
|
||||
- gitea-data:/data
|
||||
networks:
|
||||
- gitea-net
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD-SHELL
|
||||
- wget -q -O- http://localhost:3000/api/v1/version >/dev/null || exit 1
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 30
|
||||
start_period: 5s
|
||||
|
||||
runner:
|
||||
image: gitea/act_runner:0.6.1
|
||||
container_name: galaxy-local-runner
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
gitea:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
CONFIG_FILE: /config/config.yaml
|
||||
GITEA_INSTANCE_URL: http://gitea:3000
|
||||
# Provided by bootstrap.sh in the .env file. After the first
|
||||
# successful registration, act_runner persists credentials in
|
||||
# /data/.runner and ignores this token on subsequent restarts.
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN:-}
|
||||
GITEA_RUNNER_NAME: galaxy-local
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- runner-data:/data
|
||||
- ./config.yaml:/config/config.yaml:ro
|
||||
networks:
|
||||
- gitea-net
|
||||
|
||||
networks:
|
||||
gitea-net:
|
||||
name: galaxy-local-gitea-net
|
||||
|
||||
volumes:
|
||||
gitea-data:
|
||||
name: galaxy-local-gitea-data
|
||||
runner-data:
|
||||
name: galaxy-local-runner-data
|
||||
@@ -5,9 +5,16 @@
|
||||
COMPOSE := docker compose
|
||||
REPO_ROOT := $(realpath $(CURDIR)/../..)
|
||||
ENGINE_IMAGE := galaxy-engine:local-dev
|
||||
# Label set by the engine `Dockerfile` runtime stage; used to find
|
||||
# engine containers spawned by backend's runtime that fall outside
|
||||
# `docker compose down`'s scope.
|
||||
# Engine containers spawned by backend's runtime fall outside the
|
||||
# compose project. We identify them by two labels:
|
||||
# STACK_LABEL — backend stamps this on every engine it spawns from
|
||||
# this stack (see BACKEND_STACK_LABEL env in the
|
||||
# compose file);
|
||||
# ENGINE_LABEL — image-level OCI title baked into the engine
|
||||
# Dockerfile.
|
||||
# Both filters together select exactly this stack's engine containers
|
||||
# and never compose-managed services or unrelated workloads.
|
||||
STACK_LABEL := galaxy.stack=local-dev
|
||||
ENGINE_LABEL := org.opencontainers.image.title=galaxy-game-engine
|
||||
|
||||
help:
|
||||
@@ -65,9 +72,11 @@ clean: stop-engines
|
||||
# cascade the game to `cancelled`. We only remove them as part of
|
||||
# `clean`, where the whole DB is wiped anyway.
|
||||
stop-engines:
|
||||
@ids=$$(docker ps -aq --filter label=$(ENGINE_LABEL)); \
|
||||
@ids=$$(docker ps -aq \
|
||||
--filter "label=$(STACK_LABEL)" \
|
||||
--filter "label=$(ENGINE_LABEL)"); \
|
||||
if [ -n "$$ids" ]; then \
|
||||
echo "stopping engine containers…"; \
|
||||
echo "stopping engine containers for $(STACK_LABEL)…"; \
|
||||
docker rm -f $$ids >/dev/null; \
|
||||
fi
|
||||
|
||||
@@ -87,7 +96,9 @@ stop-engines:
|
||||
# cycles.
|
||||
prune-broken-engines:
|
||||
@ids=""; \
|
||||
for cid in $$(docker ps -aq --filter label=$(ENGINE_LABEL) 2>/dev/null); do \
|
||||
for cid in $$(docker ps -aq \
|
||||
--filter "label=$(STACK_LABEL)" \
|
||||
--filter "label=$(ENGINE_LABEL)" 2>/dev/null); do \
|
||||
state=$$(docker inspect -f '{{.State.Status}}' $$cid 2>/dev/null); \
|
||||
case "$$state" in \
|
||||
running|restarting) ;; \
|
||||
|
||||
+13
-12
@@ -15,10 +15,10 @@ This stack is **not** a CI gate (the per-stage CI gate now lives on
|
||||
the **long-lived dev environment** at
|
||||
[`tools/dev-deploy/`](../dev-deploy/README.md), which is redeployed on
|
||||
every merge into `development` and is reachable as
|
||||
`https://www.galaxy.lan` / `https://api.galaxy.lan`. The three stacks
|
||||
(`tools/local-dev/`, `tools/dev-deploy/`, and the fallback
|
||||
`tools/local-ci/`) coexist on the same host because every name —
|
||||
compose project, container, network, volume — is distinct.
|
||||
`https://www.galaxy.lan` / `https://api.galaxy.lan`. The two stacks
|
||||
(`tools/local-dev/` and `tools/dev-deploy/`) coexist on the same host
|
||||
because every name — compose project, container, network, volume — is
|
||||
distinct.
|
||||
|
||||
## Bring it up
|
||||
|
||||
@@ -203,8 +203,8 @@ make status docker compose ps
|
||||
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.
|
||||
- `Makefile` — wrapper over `docker compose` with thin targets for the
|
||||
most common dev cycles.
|
||||
- `.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
|
||||
@@ -290,12 +290,13 @@ make status docker compose ps
|
||||
|
||||
## 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.
|
||||
- `tools/dev-deploy/` — long-lived dev environment redeployed on every
|
||||
merge into `development`; reachable at `https://www.galaxy.lan` /
|
||||
`https://api.galaxy.lan`. Distinct compose project, container names,
|
||||
network and volumes.
|
||||
- `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
|
||||
`make -C integration integration`. Uses the canonical
|
||||
`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.
|
||||
|
||||
@@ -19,11 +19,15 @@
|
||||
# can log in without touching Mailpit. Real codes still arrive in
|
||||
# Mailpit; both paths coexist.
|
||||
|
||||
name: galaxy-local-dev
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: galaxy-local-dev-postgres
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
galaxy.stack: local-dev
|
||||
environment:
|
||||
POSTGRES_USER: galaxy
|
||||
POSTGRES_PASSWORD: galaxy
|
||||
@@ -45,6 +49,8 @@ services:
|
||||
image: redis:7-alpine
|
||||
container_name: galaxy-local-dev-redis
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
galaxy.stack: local-dev
|
||||
command:
|
||||
- redis-server
|
||||
- --requirepass
|
||||
@@ -68,6 +74,8 @@ services:
|
||||
image: axllent/mailpit:v1.21
|
||||
container_name: galaxy-local-dev-mailpit
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
galaxy.stack: local-dev
|
||||
ports:
|
||||
- "${LOCAL_DEV_MAILPIT_PORT:-8025}:8025"
|
||||
networks:
|
||||
@@ -86,6 +94,8 @@ services:
|
||||
image: galaxy/backend:local-dev
|
||||
container_name: galaxy-local-dev-backend
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
galaxy.stack: local-dev
|
||||
user: "0:0"
|
||||
depends_on:
|
||||
postgres:
|
||||
@@ -102,6 +112,7 @@ services:
|
||||
BACKEND_SMTP_FROM: "galaxy-backend@galaxy.local"
|
||||
BACKEND_SMTP_TLS_MODE: none
|
||||
BACKEND_DOCKER_NETWORK: galaxy-local-dev-net
|
||||
BACKEND_STACK_LABEL: local-dev
|
||||
BACKEND_GAME_STATE_ROOT: /tmp/galaxy-game-state
|
||||
BACKEND_GEOIP_DB_PATH: /var/lib/galaxy/geoip.mmdb
|
||||
BACKEND_NOTIFICATION_ADMIN_EMAIL: admin@galaxy.local
|
||||
@@ -144,6 +155,8 @@ services:
|
||||
image: galaxy/gateway:local-dev
|
||||
container_name: galaxy-local-dev-gateway
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
galaxy.stack: local-dev
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
@@ -205,7 +218,11 @@ services:
|
||||
networks:
|
||||
galaxy-net:
|
||||
name: galaxy-local-dev-net
|
||||
labels:
|
||||
galaxy.stack: local-dev
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
name: galaxy-local-dev-postgres-data
|
||||
labels:
|
||||
galaxy.stack: local-dev
|
||||
|
||||
Reference in New Issue
Block a user