Ctrl K
ComunidadGuías…

docker/Dockerfile.test-client

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Dockerfile for containerized game test client
# Used by E2E tests to run multiple game client instances
#
# Build: make build-test-client
# Usage: started by the test broker via Testcontainers (see tests/JunimoServer.Tests/Containers/GameClientContainer.cs)
#
# NOTE: This Dockerfile duplicates the game-downloader and smapi-builder stages of the main
# Dockerfile: the game DLLs build the test-client mod, and the SMAPI build is what a client
# installs into the shared game volume when it boots before the server.

# Global build arguments
ARG SMAPI_VERSION=4.5.2

# ============================================================================
# Stage 1: Build steam-service downloader (same as main Dockerfile)
# ============================================================================
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS steam-service-builder
WORKDIR /src

# Repo-level build props + code-style rules apply inside the image build too
COPY ./Directory.Build.props ./CodeStyle.props ./.editorconfig ./
COPY ./tools/steam-service/*.csproj ./
RUN dotnet restore

COPY ./tools/steam-service/ .
RUN dotnet publish -c Release -o /app --no-restore

# ============================================================================
# Stage 2: Download game files (same as main Dockerfile)
# ============================================================================
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS game-downloader

COPY --from=steam-service-builder /app /steam-service
WORKDIR /steam-service

# Download game (skip SDK during build)
RUN --mount=type=secret,id=steam_username \
    --mount=type=secret,id=steam_password \
    --mount=type=secret,id=steam_refresh_token \
    if [ -f /run/secrets/steam_username ]; then export STEAM_USERNAME="$(cat /run/secrets/steam_username)"; fi && \
    if [ -f /run/secrets/steam_password ]; then export STEAM_PASSWORD="$(cat /run/secrets/steam_password)"; fi && \
    if [ -f /run/secrets/steam_refresh_token ]; then export STEAM_REFRESH_TOKEN="$(cat /run/secrets/steam_refresh_token)"; fi && \
    export GAME_DIR=/game && \
    dotnet SteamService.dll download --skip-sdk

# ============================================================================
# Stage 3: Build SMAPI from source with the patch series applied (same as main Dockerfile)
# ============================================================================
# The client shares the server's game volume, so it must install the same SMAPI build when it
# boots onto an empty volume (init_smapi in docker/rootfs-test-client/startapp.sh).
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS smapi-builder

ARG SMAPI_VERSION

# zip keeps the Unix permission bits inside install.dat (the SDK image has git but no zip)
RUN apt-get update && apt-get install -y --no-install-recommends zip && rm -rf /var/lib/apt/lists/*

COPY --from=game-downloader /game /game
RUN git clone --depth 1 --branch "${SMAPI_VERSION}" https://github.com/Pathoschild/SMAPI.git /smapi

# A patch that no longer applies to the pinned upstream fails the build here, not silently at runtime
COPY patches/smapi /smapi-patches
RUN for patch in /smapi-patches/*.patch; do \
        echo "Applying ${patch}" && git -C /smapi apply --check "${patch}" && git -C /smapi apply "${patch}"; \
    done

COPY --chmod=0755 docker/smapi/build-installer.sh /smapi-build.sh
RUN --mount=type=cache,target=/root/.nuget/packages \
    /smapi-build.sh /smapi /game /smapi-patches "${SMAPI_VERSION}" /out

# ============================================================================
# Stage 4: Build test-client mod using game DLLs
# ============================================================================
# SDK 10.0 (not 6.0): ModBuildConfig 4.4.0's analyzer targets Roslyn 4.9, which
# the 6.0 SDK's 4.3 compiler rejects (CS9057). The net6.0 TFM still builds fine.
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS mod-builder

ARG BUILD_CONFIGURATION=Release

# Mod compilation resolves StardewModdingAPI.dll from the game folder, so install our build there
COPY --from=game-downloader /game /game
COPY --from=smapi-builder /out /tmp/smapi
RUN /tmp/smapi/internal/linux/SMAPI.Installer --no-prompt --install --game-path /game && \
    rm -rf /tmp/smapi

# Copy project files first for better layer caching
# (.editorconfig: code-style rules enforced at build via EnforceCodeStyleInBuild)
COPY ./Directory.Build.props ./CodeStyle.props ./.editorconfig /src/
COPY ./tests/test-client/JunimoTestClient.csproj /src/tests/test-client/
COPY ./mod/JunimoServer.Shared/JunimoServer.Shared.csproj /src/mod/JunimoServer.Shared/

# Restore dependencies
RUN --mount=type=cache,target=/root/.nuget/packages \
    dotnet restore /src/tests/test-client/JunimoTestClient.csproj

# Copy source and build
COPY ./tests/test-client /src/tests/test-client
COPY ./mod/JunimoServer.Shared /src/mod/JunimoServer.Shared
RUN --mount=type=cache,target=/root/.nuget/packages \
    dotnet build /src/tests/test-client/JunimoTestClient.csproj \
    --configuration ${BUILD_CONFIGURATION} \
    /p:GamePath=/game \
    /p:EnableModDeploy=false \
    -o /output/JunimoTestClient \
    && cp /src/tests/test-client/manifest.json /output/JunimoTestClient/manifest.json

# ============================================================================
# Stage 5: Final runtime image
# ============================================================================
FROM jlesage/baseimage-gui:debian-13-v4.13 AS client

ENV APP_NAME="Stardew Test Client" \
    USER_ID=1000 \
    GROUP_ID=1000 \
    HOME=/config \
    LANG=en_US.UTF-8 \
    LANGUAGE=en_US:en \
    LC_ALL=en_US.UTF-8 \
    ACCEPT_EULA=Y \
    DEBIAN_FRONTEND=noninteractive \
    TERMINAL=xterm \
    DARK_MODE=1 \
    # Display
    DISPLAY=:0 \
    DISPLAY_HEIGHT=720 \
    DISPLAY_WIDTH=1280 \
    CLIENT_FPS=0 \
    # SMAPI
    SMAPI_USE_CURRENT_SHELL=true \
    SMAPI_MODS_PATH=/data/Mods \
    # Test client API port (can be overridden per container)
    JUNIMO_TEST_PORT=5123

# add-pkg (base image helper), not raw apt-get — see docker/Dockerfile for why: the base's
# /var/log symlink breaks postinst scripts, and recommends drag in systemd. Cleans apt itself.
RUN add-pkg \
    ca-certificates \
    curl \
    libicu76 \
    locales \
    procps \
    scrot \
    x11-utils \
    xauth \
    xz-utils \
    mesa-utils \
    && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \
    && locale-gen

# Install ffmpeg 8.1.2 (BtbN build of the stable 8.1 branch) rather than the distro package, whose
# x11grab timing bugs break the test-recording pipeline. Pin month-end autobuilds only: BtbN prunes
# daily builds after 14 days (month-end builds live two years), and the "latest" tag drifts.
# Must match the server image's ffmpeg pin so both ends of the recording pipeline behave identically.
RUN curl -fL -o /tmp/ffmpeg.tar.xz https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-06-30-13-34/ffmpeg-n8.1.2-21-gce3c09c101-linux64-gpl-8.1.tar.xz && \
    tar -xf /tmp/ffmpeg.tar.xz -C /tmp && \
    install -m 0755 /tmp/ffmpeg-n8.1.2-21-gce3c09c101-linux64-gpl-8.1/bin/ffmpeg /usr/local/bin/ffmpeg && \
    install -m 0755 /tmp/ffmpeg-n8.1.2-21-gce3c09c101-linux64-gpl-8.1/bin/ffprobe /usr/local/bin/ffprobe && \
    rm -rf /tmp/ffmpeg*

# System cleanup
RUN \
    # Fix stuck dbus statoverride
    rm -f /var/lib/dpkg/statoverride && \
    # Prevent unnecessary logs
    rm -f /etc/cont-init.d/89-info.sh && \
    # Prevent overriding OpenGL drivers
    rm -rf /etc/cont-env.d/LIBGL_DRIVERS_PATH && \
    # Pin the runtime HOME so Steam's ~/.steam lookup matches where startapp.sh links
    # steamclient.so (${HOME}/.steam/sdk64) — see docker/Dockerfile. /config is owned by USER_ID
    # at startup, so it stays writable for any configured non-root user; set-cont-env is used
    # because cont-env overrides container env, so HOME can't be supplied from compose.
    set-cont-env HOME /config

# Our SMAPI build, installed into the game volume by startapp.sh
COPY --from=smapi-builder /out /opt/smapi

# Copy SMAPI config (client-specific settings)
COPY docker/rootfs-test-client/data/smapi-config.json /data/smapi-config.json

# Declare the xrdb one-shot as expected-to-exit-immediately (0 = no floor, matching the
# app/min_running_time the base image ships — verified in the built image's /etc/services.d).
# cinit treats a service exiting before min_running_time
# (default 500ms) as a failed start and tears the whole container down (exit 1) — and xrdb
# (load X resources, exit) only outlives 500ms when it happens to spend that long waiting on
# a still-warming Xvnc, a timing race a one-shot must not have to win.
# --chmod=0644: cinit READS this value file; an exec bit makes cinit try to run it (fails 126).
COPY --chmod=0644 docker/rootfs-test-client/etc/services.d/xrdb/min_running_time /etc/services.d/xrdb/min_running_time

# Copy startup script
COPY docker/rootfs-test-client/startapp.sh /startapp.sh

# Root init hook: takes ownership of the volumes/paths the non-root app user writes, before the
# app service drops privileges (runs as root, alphabetically, ahead of services).
COPY docker/rootfs-test-client/etc/cont-init.d/50-client-init.sh /etc/cont-init.d/50-client-init.sh
RUN chmod +x /startapp.sh /etc/cont-init.d/50-client-init.sh

# Copy test-client mod from build stage
COPY --from=mod-builder /output/JunimoTestClient /data/Mods/JunimoTestClient

# Game files provided via volume mount (shared with server)
VOLUME [ "/data/game" ]

# Expose test client API port
EXPOSE 5123

# Health check - wait for test client API to respond
HEALTHCHECK --interval=2s --timeout=2s --start-period=60s --retries=60 \
    CMD curl -sf http://localhost:${JUNIMO_TEST_PORT}/ping || exit 1

WORKDIR /data