magicciv/tools/docker/Dockerfile.mc-ai

128 lines
5.4 KiB
Text
Raw Normal View History

# syntax=docker/dockerfile:1.6
#
# Dockerfile.mc-ai — Linux container for mc-ai autoplay batches on apricot.
#
# Purpose: provide a single docker-cgroup boundary around the entire autoplay
# batch (build artifact + Godot processes) so a runaway sim cannot wedge the
# host the way bare `systemd-run --user` did. Mirrors the freeze-proofing
# pattern from Dockerfile.godot/godot-docker.sh, applied to the simulator
# half of the loop.
#
# Build context = the per-run scratch worktree (the same path apricot-run.sh
# launcher.sh creates via `git worktree add`). The image bakes the
# libmagic_civ_physics_gdext.so artifact for that exact SHA so cargo never
# runs at container start. Tag the image with the short SHA:
#
# docker build --tag mc-ai:$(git rev-parse --short HEAD) \
# --file tools/docker/Dockerfile.mc-ai $SCRATCH
#
# At runtime, scripts/mc-ai-docker.sh bind-mounts the worktree at /work and
# the entrypoint installs the baked .so into the worktree's addons dir before
# exec'ing the requested command.
#
# Base-image digest refresh:
# docker buildx imagetools inspect debian:bookworm-slim # → index digest
# docker buildx imagetools inspect rust:1-bookworm # → index digest
#
# ── stage 1: build the GDExtension shared object ────────────────────────────
FROM rust@sha256:6258907abe69656e41cd992e0b705cdcfabcbbe3db374f92ed2d47121282d4a1 AS builder
# build-essential + pkg-config + libssl-dev cover the native deps wgpu /
# tokio / openssl-sys transitively pull in. Keep this list short — the
# simulator workspace is intentionally light on C deps.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
pkg-config \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy only what build-gdext.sh actually needs to keep cargo's input set
# minimal — full game/ tree (.tscn/.gd/.import etc.) is not part of the
# Rust build.
WORKDIR /build
COPY src/simulator ./src/simulator
# Several mc-* crates `include_str!("../../../../../public/...")` at compile
# time (mc-comms/config.rs, mc-trade/rules.rs, mc-score/lib.rs, mc-ecology
# generation.rs, etc.) — the JSON game pack + resources tree must be present
# during the cargo build, not just at runtime.
COPY public ./public
# BuildKit cache mounts persist cargo registry + target dir across image
# rebuilds, so the second build of any SHA reuses everything that didn't
# change. CARGO_TARGET_DIR is set so build-gdext.sh's find_build helper
# locates the .so under /build-target/.
RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \
--mount=type=cache,target=/usr/local/cargo/git,sharing=locked \
--mount=type=cache,target=/build-target,sharing=locked \
set -eux; \
cd src/simulator; \
CARGO_TARGET_DIR=/build-target \
bash build-gdext.sh x86_64-unknown-linux-gnu; \
mkdir -p /out; \
cp /build-target/x86_64-unknown-linux-gnu/release/libmagic_civ_physics_gdext.so \
/out/libmagic_civ_physics.x86_64.so
# ── stage 2: runtime — Godot headless + the baked .so ───────────────────────
FROM debian@sha256:0104b334637a5f19aa9c983a91b54c89887c0984081f2068983107a6f6c21eeb AS runtime
ARG GODOT_VERSION=4.6.2
ARG GODOT_RELEASE=stable
ENV DEBIAN_FRONTEND=noninteractive
# Mirrors Dockerfile.godot's runtime deps. coreutils + util-linux cover the
# nice/ionice calls autoplay-batch.sh wraps each seed in (its systemd-run
# branch is auto-skipped inside the container since systemd-run is absent).
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
wget \
unzip \
coreutils \
util-linux \
libgl1-mesa-glx \
libfontconfig1 \
libxi6 \
libxrandr2 \
libxcursor1 \
libxinerama1 \
libasound2 \
libpulse0 \
&& rm -rf /var/lib/apt/lists/*
RUN set -eux; \
arch="$(dpkg --print-architecture)"; \
case "$arch" in \
arm64) godot_arch="linux.arm64" ;; \
amd64) godot_arch="linux.x86_64" ;; \
*) echo "unsupported arch: $arch" >&2; exit 1 ;; \
esac; \
url="https://github.com/godotengine/godot-builds/releases/download/${GODOT_VERSION}-${GODOT_RELEASE}/Godot_v${GODOT_VERSION}-${GODOT_RELEASE}_${godot_arch}.zip"; \
wget -q -O /tmp/godot.zip "$url"; \
unzip -q /tmp/godot.zip -d /opt; \
mv /opt/Godot_v${GODOT_VERSION}-${GODOT_RELEASE}_${godot_arch} /usr/local/bin/godot; \
chmod +x /usr/local/bin/godot; \
rm /tmp/godot.zip
# uid/gid 1000 matches the apricot operator account so bind-mount writes
# (RESULTS dir, addons/ install) land with host-friendly ownership.
RUN useradd --create-home --uid 1000 --shell /bin/bash mcai
# Baked simulator artifact — entrypoint installs it into the bind-mounted
# worktree at container start.
COPY --from=builder /out/libmagic_civ_physics.x86_64.so /opt/mc-ai/libmagic_civ_physics.x86_64.so
RUN chown -R mcai:mcai /opt/mc-ai
# Entrypoint copies the baked .so into the worktree's addons dir so Godot
# loads the image's exact SHA-matched artifact (overriding anything the
# bind-mounted worktree might carry). Exec form preserves signal handling.
COPY tools/docker/mc-ai-entrypoint.sh /usr/local/bin/mc-ai-entrypoint
RUN chmod 0755 /usr/local/bin/mc-ai-entrypoint
USER mcai
WORKDIR /work
ENTRYPOINT ["/usr/local/bin/mc-ai-entrypoint"]
CMD ["godot", "--version"]