From bb0e3e17e5ff34ff6981753f0470568dd72785e9 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Mon, 22 Jun 2026 14:45:52 +0200 Subject: [PATCH 1/2] chore(deploy): pin dictionary seed to v1.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump DICT_VERSION v1.2.1 -> v1.3.0 across the seed surface: .env.example, the compose build-arg default, both backend Dockerfile stages, the loadtest Dockerfile, the CI dawg-download version, and the deploy/backend docs. v1.3.0 drops the abbreviation class from the Russian word list (scrabble-dictionary #6). This only seeds a FRESH volume; a live contour/prod volume is unaffected (the .seed_version marker wins — seed-drift guard) and moves to v1.3.0 through the admin console (ARCHITECTURE §5). Per-contour deploy still overrides via TEST_/PROD_DICT_VERSION. --- .gitea/workflows/ci.yaml | 2 +- backend/Dockerfile | 4 ++-- backend/README.md | 2 +- deploy/.env.example | 2 +- deploy/README.md | 2 +- deploy/docker-compose.yml | 2 +- loadtest/Dockerfile | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index fe5a438..d11622f 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -31,7 +31,7 @@ on: # unit/integration jobs inherit it. The deploy job overrides it per contour with # vars.TEST_DICT_VERSION (the seed for a fresh volume), see deploy/README.md. env: - DICT_VERSION: v1.2.1 + DICT_VERSION: v1.3.0 jobs: # changes detects which areas a PR/push touched, so the test jobs can skip when diff --git a/backend/Dockerfile b/backend/Dockerfile index a95c7e8..bc7bb97 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -12,7 +12,7 @@ # --- dictionary artifact ----------------------------------------------------- FROM alpine:3.20 AS dawg -ARG DICT_VERSION=v1.2.1 +ARG DICT_VERSION=v1.3.0 RUN apk add --no-cache curl tar RUN mkdir -p /dawg \ && curl -fsSL -o /tmp/dawg.tar.gz \ @@ -42,7 +42,7 @@ FROM gcr.io/distroless/static-debian12:nonroot # Re-declare the build arg in this stage so it labels the seed dictionary. One # DICT_VERSION drives both the artifact the dawg stage downloads and the version # label the binary pins, so the resident version equals the release tag. -ARG DICT_VERSION=v1.2.1 +ARG DICT_VERSION=v1.3.0 COPY --from=build /out/backend /usr/local/bin/backend # Own the seed dictionary as the nonroot runtime user (UID 65532): a named volume # mounted at /opt/dawg inherits this ownership on first use, so the admin console diff --git a/backend/README.md b/backend/README.md index a833500..5a6230b 100644 --- a/backend/README.md +++ b/backend/README.md @@ -228,7 +228,7 @@ internal/banview/ # gateway active-ban mirror: the console's Active IP bans p ```sh docker run -d --name scrabble-pg -e POSTGRES_PASSWORD=dev -p 5432:5432 postgres:17-alpine # DAWGs: extract the dictionary release artifact (or point at a local scrabble-solver/dawg): -mkdir -p /tmp/dawg && curl -fsSL https://gitea.iliadenisov.ru/developer/scrabble-dictionary/releases/download/v1.2.1/scrabble-dawg-v1.2.1.tar.gz | tar xz -C /tmp/dawg +mkdir -p /tmp/dawg && curl -fsSL https://gitea.iliadenisov.ru/developer/scrabble-dictionary/releases/download/v1.3.0/scrabble-dawg-v1.3.0.tar.gz | tar xz -C /tmp/dawg BACKEND_POSTGRES_DSN='postgres://postgres:dev@localhost:5432/postgres?search_path=backend&sslmode=disable' \ BACKEND_DICT_DIR=/tmp/dawg \ GOPRIVATE='gitea.iliadenisov.ru/*' \ diff --git a/deploy/.env.example b/deploy/.env.example index 584b9af..027b307 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -16,7 +16,7 @@ POSTGRES_PASSWORD=change-me # required # the active version lives in the DB. On a live volume a changed value is ignored (the # recorded .seed_version marker wins — the seed-drift guard); change a running # contour's dictionary through /_gm/dictionary (ARCHITECTURE.md §5). -DICT_VERSION=v1.2.1 +DICT_VERSION=v1.3.0 # --- Logging ---------------------------------------------------------------- LOG_LEVEL=info diff --git a/deploy/README.md b/deploy/README.md index 7007af3..24cc2cf 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -80,7 +80,7 @@ without it Docker's resolver handles `otelcol`, `gateway` and `api.telegram.org` | --- | --- | --- | --- | | `POSTGRES_DB` | variable | `scrabble` | Database name. | | `POSTGRES_USER` | variable | `scrabble` | Database user. | -| `DICT_VERSION` | variable | `v1.2.1` | `scrabble-dictionary` release tag baked into the backend image as the **seed for a fresh volume** (build-arg). A live contour changes dictionary through the admin console, not this; on a seeded volume a changed value is ignored (the recorded `.seed_version` marker wins — the seed-drift guard, ARCHITECTURE.md §5). Set per contour as `TEST_`/`PROD_DICT_VERSION`. | +| `DICT_VERSION` | variable | `v1.3.0` | `scrabble-dictionary` release tag baked into the backend image as the **seed for a fresh volume** (build-arg). A live contour changes dictionary through the admin console, not this; on a seeded volume a changed value is ignored (the recorded `.seed_version` marker wins — the seed-drift guard, ARCHITECTURE.md §5). Set per contour as `TEST_`/`PROD_DICT_VERSION`. | | `LOG_LEVEL` | variable | `info` | Shared log level for backend / gateway / validator / bot (`debug\|info\|warn\|error`). | | `CADDY_SITE_ADDRESS` | variable | `:80` | Caddy site address. Test: `:80` (host caddy terminates TLS). Prod: a domain, so caddy does its own ACME. | | `GM_BASICAUTH_USER` | variable | `gm` | Username for the `/_gm` Basic-Auth. | diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 12f7cb4..143eade 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -70,7 +70,7 @@ services: args: # Seed dictionary for a FRESH volume; the per-contour value comes from the # deploy env (Gitea TEST_/PROD_DICT_VERSION). See the volume note below. - DICT_VERSION: ${DICT_VERSION:-v1.2.1} + DICT_VERSION: ${DICT_VERSION:-v1.3.0} # Build version stamped into the binary (git tag; see pkg/version). VERSION: ${APP_VERSION:-dev} restart: unless-stopped diff --git a/loadtest/Dockerfile b/loadtest/Dockerfile index 410bd34..1103051 100644 --- a/loadtest/Dockerfile +++ b/loadtest/Dockerfile @@ -12,7 +12,7 @@ # --- dictionary artifact ----------------------------------------------------- FROM alpine:3.20 AS dawg -ARG DICT_VERSION=v1.2.1 +ARG DICT_VERSION=v1.3.0 RUN apk add --no-cache curl tar RUN mkdir -p /dawg \ && curl -fsSL -o /tmp/dawg.tar.gz \ -- 2.52.0 From 1ba52dd0b4ba65774c10c8a080ff9a26602f5c33 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Mon, 22 Jun 2026 15:03:07 +0200 Subject: [PATCH 2/2] refactor(deploy): make DICT_VERSION a required build arg (single-sourced) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the literal version default from the build files (backend Dockerfile both stages, loadtest Dockerfile, the compose build-arg) so the release tag is not duplicated as a stale-prone default a newcomer can't tell from the real source. DICT_VERSION is now required: compose uses ${DICT_VERSION:?…} and the Dockerfiles have no ARG default, so a missing value fails loudly instead of baking a stale tag. The tag lives only in its genuine sources — ci.yaml env (CI tests), the Gitea TEST_/PROD_DICT_VERSION variables (deploy seed) and deploy/.env.example (local). Adds a "Bumping the dictionary version" section to deploy/README and fixes the bare docker-build examples (CLAUDE.md, README.md, loadtest/README) to pass --build-arg. --- CLAUDE.md | 2 +- README.md | 2 +- backend/Dockerfile | 10 ++++++---- deploy/README.md | 22 ++++++++++++++++++++++ deploy/docker-compose.yml | 8 +++++--- loadtest/Dockerfile | 3 ++- loadtest/README.md | 4 ++-- 7 files changed, 39 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 11f5b73..75321f4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -144,7 +144,7 @@ go run ./backend/cmd/backend # /healthz, /readyz on :8080 cd ui && pnpm install && pnpm check && pnpm test:unit && pnpm build # the UI pnpm start # UI mock mode: lobby -> game, no backend -docker build -f backend/Dockerfile -t scrabble-backend . # images; gateway embeds the SPA +docker build --build-arg DICT_VERSION=v1.3.0 -f backend/Dockerfile -t scrabble-backend . # DICT_VERSION required (no default); gateway embeds the SPA docker build -f gateway/Dockerfile --target gateway -t scrabble-gateway . docker build -f gateway/Dockerfile --target landing -t scrabble-landing . # static landing docker compose -f deploy/docker-compose.yml config # validate the full contour diff --git a/README.md b/README.md index 5bab9a1..354adce 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ observability stack (OTel Collector → Prometheus + Tempo → Grafana) + a fron services build from multi-stage distroless `*/Dockerfile`. ```sh -docker build -f backend/Dockerfile -t scrabble-backend . # pulls the DAWG release artifact +docker build --build-arg DICT_VERSION=v1.3.0 -f backend/Dockerfile -t scrabble-backend . # DICT_VERSION required; pulls that DAWG release artifact docker build -f gateway/Dockerfile -t scrabble-gateway . # node stage builds + embeds the UI docker compose -f deploy/docker-compose.yml config # validate (needs the TEST_/PROD_ env) ``` diff --git a/backend/Dockerfile b/backend/Dockerfile index bc7bb97..b593d33 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -7,12 +7,14 @@ # (GOPRIVATE), so the build stage needs git and network. # # Build from the repository root so go.work, go.work.sum, pkg/ and backend/ are all -# in the Docker context: -# docker build -f backend/Dockerfile -t scrabble-backend . +# in the Docker context. DICT_VERSION has no default — the caller supplies the +# scrabble-dictionary release tag (compose/CI pass it; see deploy/README.md +# "Bumping the dictionary version"): +# docker build --build-arg DICT_VERSION=v1.3.0 -f backend/Dockerfile -t scrabble-backend . # --- dictionary artifact ----------------------------------------------------- FROM alpine:3.20 AS dawg -ARG DICT_VERSION=v1.3.0 +ARG DICT_VERSION RUN apk add --no-cache curl tar RUN mkdir -p /dawg \ && curl -fsSL -o /tmp/dawg.tar.gz \ @@ -42,7 +44,7 @@ FROM gcr.io/distroless/static-debian12:nonroot # Re-declare the build arg in this stage so it labels the seed dictionary. One # DICT_VERSION drives both the artifact the dawg stage downloads and the version # label the binary pins, so the resident version equals the release tag. -ARG DICT_VERSION=v1.3.0 +ARG DICT_VERSION COPY --from=build /out/backend /usr/local/bin/backend # Own the seed dictionary as the nonroot runtime user (UID 65532): a named volume # mounted at /opt/dawg inherits this ownership on first use, so the admin console diff --git a/deploy/README.md b/deploy/README.md index 24cc2cf..89fa882 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -117,6 +117,28 @@ collector's / gateway's internal IP is fine (connected route), but its `AWG_CONF which resolves `otelcol`, `gateway` and `api.telegram.org`. `GATEWAY_ADMIN_*` is intentionally **unset** — caddy owns `/_gm` in the contour. +## Bumping the dictionary version + +The dictionary ships as a versioned **release artifact** (`scrabble-dawg-vX.Y.Z.tar.gz`) from +[`scrabble-dictionary`](https://gitea.iliadenisov.ru/developer/scrabble-dictionary). The tag is +a build-time input with **no default** in the images, so it is set in exactly two places to +move the whole stack — change both to a new release: + +1. **CI tests** — `.gitea/workflows/ci.yaml` `env.DICT_VERSION` (the unit/integration jobs + download that dawg). +2. **Deploy seed** — the Gitea repo variables `TEST_DICT_VERSION` / `PROD_DICT_VERSION` (the tag + the deploy bakes into a **fresh** volume's image; the deploy job feeds it to `compose` as + `DICT_VERSION`). + +For local builds set `DICT_VERSION` in `deploy/.env` (template: `.env.example`); a bare +`docker build` needs `--build-arg DICT_VERSION=vX.Y.Z`. The Dockerfiles and `compose` carry no +default — a missing value fails loudly instead of baking a stale tag. + +Bumping the seed is a **no-op on a live volume** (the `.seed_version` marker wins — the +seed-drift guard). A running contour/prod moves to a new release **through the admin console** +`/_gm/dictionary` (upload the tarball, preview the per-variant diff, confirm); in-flight games +keep their pinned version, new games use the new one (ARCHITECTURE.md §5). + ## Production rollout Prod runs on **two hosts** (main = full stack + ACME on the domain; tg = the bot only, diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 143eade..369aa18 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -68,9 +68,11 @@ services: context: .. dockerfile: backend/Dockerfile args: - # Seed dictionary for a FRESH volume; the per-contour value comes from the - # deploy env (Gitea TEST_/PROD_DICT_VERSION). See the volume note below. - DICT_VERSION: ${DICT_VERSION:-v1.3.0} + # Seed dictionary for a FRESH volume; required (no default) so the release tag is + # set in exactly one place per context — the deploy env (Gitea TEST_/PROD_DICT_VERSION) + # or .env for local builds. See the volume note below + deploy/README.md "Bumping the + # dictionary version". + DICT_VERSION: ${DICT_VERSION:?set DICT_VERSION — the scrabble-dictionary release tag, e.g. in deploy/.env} # Build version stamped into the binary (git tag; see pkg/version). VERSION: ${APP_VERSION:-dev} restart: unless-stopped diff --git a/loadtest/Dockerfile b/loadtest/Dockerfile index 1103051..e9c21a8 100644 --- a/loadtest/Dockerfile +++ b/loadtest/Dockerfile @@ -12,7 +12,8 @@ # --- dictionary artifact ----------------------------------------------------- FROM alpine:3.20 AS dawg -ARG DICT_VERSION=v1.3.0 +# Required, no default: the build caller supplies the scrabble-dictionary release tag. +ARG DICT_VERSION RUN apk add --no-cache curl tar RUN mkdir -p /dawg \ && curl -fsSL -o /tmp/dawg.tar.gz \ diff --git a/loadtest/README.md b/loadtest/README.md index 1af9c04..4a34223 100644 --- a/loadtest/README.md +++ b/loadtest/README.md @@ -35,8 +35,8 @@ The harness reaches Postgres and the gateway directly, so run it as a one-shot container on the contour's docker network (this bypasses the host→gateway hairpin): ```sh -# from the repo root -docker build -f loadtest/Dockerfile -t scrabble-loadtest . +# from the repo root (DICT_VERSION has no default — pass the scrabble-dictionary release tag) +docker build --build-arg DICT_VERSION=v1.3.0 -f loadtest/Dockerfile -t scrabble-loadtest . docker run --rm --cpus=3 --name scrabble-loadtest --network scrabble-internal \ -e POSTGRES_PASSWORD="$TEST_POSTGRES_PASSWORD" \ -- 2.52.0