New tavolo-scopa package with a pure rules engine and the platform adapters for two-player scopa and four-player fixed-partnership scopa a quattro: three cards each and four on the table, capture by rank or sum, sweep for scope and score carte/denara/settebello/primiera. The optional asso pigliatutto creation option turns either variant into scopa d'assi (an ace sweeps the whole table and scores no scopa), reusing scopone's rule semantics. Wire both engines into the app and the Docker install, add the scopa table view to the SPA (duo and four-seat layouts, deck count, captured/scope stats, hand-summary and final screens, player names in the two-player summary), and cover the rules, the platform contract, the REST/WebSocket flows, match persistence and Elo.
115 lines
4.9 KiB
Docker
115 lines
4.9 KiB
Docker
# syntax=docker/dockerfile:1
|
|
# Multi-stage build for the tavolo stack (kaya backend + Sycamore/WASM
|
|
# frontend). The Docker build context is the REPOSITORY ROOT (see
|
|
# docker-compose.yml) so this single image assembles both parts:
|
|
#
|
|
# web-builder rust + trunk -> compiles web/ into web/dist
|
|
# builder alpine python -> python venv with the backend + deps
|
|
# runtime alpine python + the venv + the compiled frontend
|
|
#
|
|
# apk and pip both use BuildKit cache mounts (type=cache): the package
|
|
# caches persist in the builder's cache across builds instead of being
|
|
# re-downloaded, and never land in the image layers.
|
|
|
|
# --- Web builder -------------------------------------------------------------
|
|
FROM rust:1-slim AS web-builder
|
|
|
|
ARG TRUNK_VERSION=0.21.14
|
|
# The build environment blocks plain HTTP: force the apt mirrors to HTTPS.
|
|
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
|
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
|
sed -i 's|http://deb.debian.org|https://deb.debian.org|g' /etc/apt/sources.list.d/debian.sources \
|
|
&& apt-get update \
|
|
&& apt-get install -y --no-install-recommends curl ca-certificates \
|
|
&& curl -fsSL "https://github.com/trunk-rs/trunk/releases/download/v${TRUNK_VERSION}/trunk-x86_64-unknown-linux-gnu.tar.gz" \
|
|
| tar -xz -C /usr/local/bin \
|
|
&& rustup target add wasm32-unknown-unknown
|
|
|
|
WORKDIR /web
|
|
|
|
# Card images are committed in the repository (CC0 woodcut napoletane deck);
|
|
# web/fetch-cards.sh can regenerate them.
|
|
COPY web/Cargo.toml web/Cargo.lock web/index.html web/style.css web/theme.css web/Trunk.toml ./
|
|
COPY web/assets ./assets
|
|
# Vendored Bootstrap (MIT) loaded by index.html; kept out of the CDN so the
|
|
# image builds and runs without network access.
|
|
COPY web/vendor ./vendor
|
|
COPY web/src ./src
|
|
|
|
# --public-url makes trunk emit asset URLs under /static, the prefix Granian
|
|
# serves in the runtime image (see GRANIAN_STATIC_PATH_* below). Dev builds
|
|
# (trunk serve) keep the default "/" public URL.
|
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
|
--mount=type=cache,target=/web/target \
|
|
trunk build --release --public-url /static/
|
|
|
|
# --- Python builder ----------------------------------------------------------
|
|
FROM alpine:3.24 AS builder
|
|
|
|
RUN --mount=type=cache,target=/var/cache/apk \
|
|
apk add python3 py3-pip build-base python3-dev
|
|
|
|
WORKDIR /build
|
|
|
|
|
|
COPY server/requirements.txt ./
|
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
|
python3 -m venv /opt/venv \
|
|
&& /opt/venv/bin/pip install --upgrade pip \
|
|
&& /opt/venv/bin/pip install -r requirements.txt
|
|
|
|
# The five sibling distributions, installed from the local tree (PEP 420
|
|
# namespace packages sharing the `tavolo` namespace). --no-deps: every
|
|
# third-party dependency — including the tavolo-app `otel` extra — is
|
|
# already pinned in requirements.txt.
|
|
COPY server/packages/ ./packages/
|
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
|
/opt/venv/bin/pip install --no-deps ./packages/tavolo-platform ./packages/tavolo-scopone ./packages/tavolo-scopa ./packages/tavolo-briscola ./packages/tavolo-app
|
|
# server/pyproject.toml only carries the [tool.aerich] section (the
|
|
# db-migrate compose service runs `aerich upgrade` with working_dir=/app,
|
|
# next to ./migrations).
|
|
COPY server/pyproject.toml ./
|
|
# aerich migration files are a release artifact: the db-migrate compose
|
|
# service runs `aerich upgrade` from this image before the app starts.
|
|
COPY server/migrations/ ./migrations/
|
|
|
|
# --- Runtime ---------------------------------------------------------------
|
|
FROM alpine:3.24
|
|
|
|
RUN --mount=type=cache,target=/var/cache/apk \
|
|
apk add python3 ca-certificates tzdata \
|
|
&& addgroup -S app && adduser -S -G app app
|
|
|
|
COPY --from=builder /opt/venv /opt/venv
|
|
COPY --from=builder /build/migrations /app/migrations
|
|
# aerich reads [tool.aerich] from pyproject.toml (its default config file);
|
|
# the db-migrate compose service runs `aerich upgrade` with working_dir=/app.
|
|
COPY --from=builder /build/pyproject.toml /app/pyproject.toml
|
|
# The compiled single-page application. Granian serves the assets directly
|
|
# in Rust — hashed js/wasm/css under /static/* and the card images under
|
|
# /assets/* (see the GRANIAN_STATIC_PATH_* env vars below; click splits
|
|
# multi-value env vars on whitespace for routes and ':' for paths). The
|
|
# Python app only serves the SPA shell (index.html) at / and for
|
|
# client-side routes (STATIC_DIR).
|
|
COPY --from=web-builder /web/dist /app/web/dist
|
|
|
|
ENV PATH="/opt/venv/bin:$PATH" \
|
|
PYTHONUNBUFFERED=1 \
|
|
PYTHONDONTWRITEBYTECODE=1 \
|
|
GRANIAN_HOST=0.0.0.0 \
|
|
GRANIAN_PORT=8080 \
|
|
GRANIAN_INTERFACE=rsgi \
|
|
GRANIAN_STATIC_PATH_ROUTE="/static /assets" \
|
|
GRANIAN_STATIC_PATH_MOUNT="/app/web/dist:/app/web/dist/assets" \
|
|
GRANIAN_STATIC_PATH_DIR_TO_FILE=index.html \
|
|
STATIC_DIR=/app/web/dist
|
|
|
|
USER app
|
|
|
|
EXPOSE 8080
|
|
|
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
|
|
CMD wget -q -O- http://127.0.0.1:8080/api/health || exit 1
|
|
|
|
CMD ["granian", "tavolo.app:app"]
|