og-edopro-server-ts/Dockerfile
Diango Gavidia 46e353c588
feat(room): join-command overhaul — pairing joins, room identity, edison bots (#319)
* chore(dependencies): update various package versions in package-lock.json

* feat(windbot): playable edison bots and format-scoped random pools

Edison bots join the botlist under short names that fit the utf16[20]
pass field. Botlist entries gain a format tag; random bot selection
resolves a pool from the join tokens (mirroring the room's rule-tier
precedence) so a format room can no longer roll a bot with an illegal
deck. Bot names are validated against the pass-field budget at boot,
and the bot-request failure path delivers its JOINERROR before the
canonical room teardown destroys the sockets.

* feat(ban-list): deterministic alias resolution with load-time format aliases

Format banlists carry an explicit alias captured from their
formats/<dir> path at load time. Alias resolution tries the alias
field, then the exact normalized name, and only then the substring
scan — now tie-broken by shortest normalized name instead of load
order, with a memoized warning on ambiguity. Adds getFirstOCGIndex
so OCG-list tokens stop hardcoding index 0.

* fix(room): rule-mapping clamps, state coherence, and boot-order hardening

The lp clamp compared a NaN (parseInt over the whole token) so lp0
started duels at 0 LP; clamps now act on the extracted number. The
edison rule set is shared between its two tokens. The room's DuelState
label and its state object now always transition together: waiting()
sets both and resets isStart/ready flags, and setDuelFinished disposes
the OCGCore before delegating — the ocgcore-error rewind no longer
leaks the core, lies to the room list, or leaves a stale TRY_START
armed. Missing banlist aliases warn once instead of silently
mislabeling rooms. Windbot bootstrap moved before socket init so a bad
botlist aborts pre-listen, and the never-initialized registry fallback
composes the full base chain.

* feat(room): exact-string pairing joins and (name,password) room identity

Empty-password commands made purely of recognized tokens become
pairing joins: they route to a waiting same-command room with a free
seat, an empty password, and a compatible league — or create a fresh
room. They never land in a dueling or full room. A disconnected
pairing player who re-sends the bare command is first matched as a
legitimate reconnector (same predicate as findReconnectingPlayer)
before the pairing scan runs.

Non-pairing joins now identify a room by the exact (name, password)
pair: a mismatched pair creates its own room instead of rejecting, so
a passwordless pairing room no longer blocks passworded rooms that
share its name. Spectating a room mid-duel with the correct password
is unchanged. docs/join-commands.md is the reference for the whole
command system.
2026-08-10 18:12:12 -04:00

102 lines
4 KiB
Docker

# syntax=docker/dockerfile:1
# Stage 1: Clone repositories and assemble resources
FROM public.ecr.aws/docker/library/node:24.11.0-bullseye-slim AS resources-builder
RUN apt-get update -y && \
apt-get install -y --no-install-recommends wget git ca-certificates jq && \
rm -rf /var/lib/apt/lists/*
WORKDIR /build
# Resource layout is owned by scripts/clone_repositories.sh + scripts/setup_resources.sh — the
# single source of truth, shared with local dev (README) and the runtime refresh
# loop (entrypoint). This produces /build/resources/releases/<id> and a current symlink.
COPY scripts/ ./scripts/
# resources.manifest.json = public base (+ the shipped example). The private override is
# NOT part of the build — it is provided at runtime, so the seed is public-only.
COPY resources.manifest*.json ./
# Assemble the PUBLIC resource seed so the server boots immediately. Private sources are
# fetched at runtime by the entrypoint's updater (mounted private override + a token from
# the container env); no token ever touches the build.
RUN bash scripts/clone_repositories.sh && bash scripts/setup_resources.sh
# Stage 2: Build CoreIntegrator (C++)
FROM public.ecr.aws/docker/library/node:24.11.0-bullseye-slim AS core-builder
RUN apt-get update -y && \
apt-get install -y --no-install-recommends \
g++ make cmake pkg-config \
libboost-system-dev \
libsqlite3-dev \
libjsoncpp-dev \
nlohmann-json3-dev \
libcurl4-openssl-dev && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY ./core .
RUN cmake -B build -S . -DCMAKE_BUILD_TYPE=Release && \
cmake --build build
# Stage 3: Build Node.js server
FROM public.ecr.aws/docker/library/node:24.11.0-bullseye AS server-builder
WORKDIR /server
COPY package.json package-lock.json ./
RUN npm ci
RUN git clone --depth 1 https://github.com/diangogav/evolution-types.git ./src/evolution-types
COPY . .
RUN npm run build && \
npm prune --production
# Stage 4: Final image
FROM public.ecr.aws/docker/library/node:24.11.0-slim
RUN apt-get update && \
apt-get install -y --no-install-recommends curl wget git ca-certificates jq liblua5.3-dev libsqlite3-dev libevent-dev dumb-init && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Server
COPY --from=server-builder /server/dist ./
COPY --from=server-builder /server/package.json ./package.json
COPY --from=server-builder /server/node_modules ./node_modules
# WindBot botlist (read at boot by FileBotlistRepository when ENABLE_WINDBOT=true).
# tsc only emits dist/, so config/ must be copied explicitly or the server crashes
# at boot with ENOENT when windbot is enabled. Replace botlist.example.json with a
# curated botlist whose deck names match the WindBot image's bots.json.
# IMPORTANT: every entry MUST also carry a "format" tag ("tcg" / "jtp" / "edison")
# matching resolveBotPool's pools. Format-scoped random join commands (e.g.
# "pre,ai", "ed,ai", "jtp,ai") call pickRandom(format) — a curated botlist without
# "format" tags makes that lookup return null for every user of those commands
# (JOINERROR). See config/botlist.example.json for the reference shape.
COPY --from=server-builder /server/config ./config
# CoreIntegrator binaries
COPY --from=core-builder /app/libocgcore.so ./core/libocgcore.so
COPY --from=core-builder /app/CoreIntegrator ./core/CoreIntegrator
# All resources (assembled in Stage 1): releases/<id> + current symlink — the
# baked seed so the server boots immediately. The entrypoint's background loop
# then refreshes resources/current in place and the in-memory reload picks it up.
COPY --from=resources-builder /build/resources ./resources
# Provisioning scripts (scripts/) + the PUBLIC manifest — reused by the runtime updater loop.
# The private override is not baked in: mount it at runtime (-v .../resources.manifest.private.json
# :/app/resources.manifest.private.json) and pass a read-only token via the container env
# (--env-file); the entrypoint sets up git-credentials before the loop clones private sources.
COPY scripts/ ./scripts/
COPY resources.manifest*.json ./
CMD ["dumb-init", "bash", "scripts/entrypoint.sh"]