Something went wrong. Try again.
Backend environment for match hosting for lance.blue
Something went wrong. Try again.
arena Dockerfile
27 kB · 526 lines
Dockerfile
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527# syntax=docker/dockerfile:1## One image, one match.## Downloads use BuildKit cache mounts, all sharing /tmp/deps. A cache mount is# not a layer, so each archive is fetched into the cache, checksummed, and# extracted in the same RUN; only the extracted tree becomes part of the image.# The cache survives between builds and between branches, so the ~1.1GB of# third-party artifacts is fetched once on a machine rather than once per build.## apt uses the same mechanism: /var/cache/apt and /var/lib/apt are cache mounts,# and docker-clean is removed so apt keeps its .debs instead of deleting them.# Because neither directory is a layer, there is no need to rm the lists# afterwards - doing so would only empty the cache.## sharing=locked because the stages below run in parallel and draw from the same# pool. Clear either with:# docker builder prune -f --filter type=exec.cachemount (downloads + apt)# docker builder prune -af (all build cache)## Stages are per component - jre, megamek, suramadu, arena - so a failure names# the component, and the three that do not depend on each other build in# parallel.## Requires BuildKit, which is the default in Docker 23+. ./scripts/build.sh sets# DOCKER_BUILDKIT=1 regardless.## Licensing: this image redistributes MegaMek (GPL-3.0 code, CC BY-NC-SA 4.0# data), Suramadu (AGPL-3.0) and a Temurin-derived runtime (GPLv2+CPE). Our# patches ship as source in /opt/arena/patches, and the licence texts travel with# the bundle. See LICENSING.md.
ARG JDK_VERSIONARG JDK_URL_TAGARG JDK_ARCHIVEARG JDK_SHA256ARG JDK_DIRARG JRE_MODULESARG MEGAMEK_VERSIONARG MEGAMEK_RELEASE_DIRARG MEGAMEK_SRC_DIRARG SURAMADU_VERSIONARG SURAMADU_WAR# Defaults, not blanks: scripts/build.sh does not pass these yet, and an empty# ARG would fetch a URL ending in a slash. The same four values live in# versions.env, where every other pin does; tests/shell/test-profiling.sh fails# if they drift apart.ARG ASYNC_PROFILER_VERSION=4.5ARG ASYNC_PROFILER_ARCHIVE=async-profiler-4.5-linux-x64.tar.gzARG ASYNC_PROFILER_SHA256=89546fbb9ee0fc5496c7edd4099b0709489bc78b0d8057ccbb4b801f6b032b62ARG ASYNC_PROFILER_DIR=async-profiler-4.5-linux-x64
# ---------------------------------------------------------------------------# base: the tools every build stage needs# ---------------------------------------------------------------------------FROM debian:bookworm-slim AS baseRUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ rm -f /etc/apt/apt.conf.d/docker-clean \ && apt-get update && apt-get install -y --no-install-recommends \ ca-certificates curl tar unzip patch coreutilsSHELL ["/bin/bash", "-o", "pipefail", "-c"]
# ---------------------------------------------------------------------------# jre: Temurin JDK, then a jlink runtime# ---------------------------------------------------------------------------FROM base AS jreARG JDK_URL_TAGARG JDK_ARCHIVEARG JDK_SHA256ARG JDK_DIRARG JRE_MODULES
# Downloaded to .part and only renamed into place once it verifies, so an# interrupted curl can never leave a file that later builds treat as cached. A# cached file that fails its check is deleted and refetched rather than failing# forever - the cache has to be able to heal itself.RUN --mount=type=cache,target=/tmp/deps,sharing=locked \ set -eu; \ f="/tmp/deps/${JDK_ARCHIVE}"; \ if [ -f "$f" ] && ! echo "${JDK_SHA256} $f" | sha256sum -c - >/dev/null 2>&1; then \ echo "cached ${JDK_ARCHIVE} failed its checksum; refetching"; rm -f "$f"; \ fi; \ if [ ! -f "$f" ]; then \ echo "fetching ${JDK_ARCHIVE}"; \ curl -fsSL --retry 3 --retry-delay 2 -o "$f.part" \ "https://github.com/adoptium/temurin21-binaries/releases/download/${JDK_URL_TAG}/${JDK_ARCHIVE}"; \ echo "${JDK_SHA256} $f.part" | sha256sum -c -; \ mv "$f.part" "$f"; \ else echo "using cached ${JDK_ARCHIVE}"; fi; \ mkdir -p /opt && tar xzf "$f" -C /opt; \ mv "/opt/${JDK_DIR}" /opt/jdk
# --strip-debug shells out to objcopy to strip native debug symbols from the# JDK's .so files, and bookworm-slim has no binutils. Installed here rather than# in base because only this stage needs it, and it never reaches the runtime# image - only /opt/jre is copied forward.RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ rm -f /etc/apt/apt.conf.d/docker-clean \ && apt-get update && apt-get install -y --no-install-recommends \ binutils
# 59MB against 346MB for the full JDK, which is most of the image. If something# dies with NoClassDefFoundError on a java.* or jdk.* class, add the module to# JRE_MODULES in versions.env rather than shipping the whole JDK.## The rm is not paranoia: jlink refuses to write into an existing directory and# reports it with the same exit code as an unresolvable module, so without it the# two failures look identical.RUN set -eu; \ : "${JRE_MODULES:?empty - pass it as a build arg, or use ./scripts/build.sh}"; \ echo "jlink modules: ${JRE_MODULES}"; \ rm -rf /opt/jre; \ /opt/jdk/bin/jlink \ --add-modules "${JRE_MODULES}" \ --strip-debug --no-man-pages --no-header-files --compress=zip-6 \ --output /opt/jre; \ /opt/jre/bin/java -version
# ---------------------------------------------------------------------------# megamek: release + source, patched jar, primed units.cache# ---------------------------------------------------------------------------FROM base AS megamekARG MEGAMEK_VERSIONARG MEGAMEK_RELEASE_DIRARG MEGAMEK_SRC_DIRARG MEGAMEK_RELEASE_SHA256ARG MEGAMEK_SRC_SHA256
COPY --from=jre /opt/jdk /opt/jdkENV JAVA_HOME=/opt/jdkENV MM_HOME=/opt/megamekENV MM_SRC=/opt/megamek-src
# Both tarballs: the release supplies the jar and data/, the source is what the# patchset applies against.## Upstream publishes no checksum for either, so the digests in versions.env# come from a verified fetch, and are enforced here the way the JDK's is: a# cached file that fails is deleted and refetched, a fresh one that fails# fails the build naming the mismatch.RUN --mount=type=cache,target=/tmp/deps,sharing=locked \ set -eu; \ fetch_tgz() { \ f="$1"; url="$2"; sha="$3"; \ if [ -f "$f" ] && ! echo "$sha $f" | sha256sum -c - >/dev/null 2>&1; then \ echo "cached $(basename "$f") failed its checksum; refetching"; rm -f "$f"; \ fi; \ if [ ! -f "$f" ]; then \ echo "fetching $(basename "$f")"; \ curl -fsSL --retry 3 --retry-delay 2 -o "$f.part" "$url"; \ echo "$sha $f.part" | sha256sum -c -; \ mv "$f.part" "$f"; \ else echo "using cached $(basename "$f")"; fi; \ }; \ rel="/tmp/deps/MegaMek-${MEGAMEK_VERSION}.tar.gz"; \ src="/tmp/deps/megamek-src-${MEGAMEK_VERSION}.tar.gz"; \ fetch_tgz "$rel" "https://github.com/MegaMek/megamek/releases/download/v${MEGAMEK_VERSION}/MegaMek-${MEGAMEK_VERSION}.tar.gz" "${MEGAMEK_RELEASE_SHA256}"; \ fetch_tgz "$src" "https://github.com/MegaMek/megamek/archive/refs/tags/v${MEGAMEK_VERSION}.tar.gz" "${MEGAMEK_SRC_SHA256}"; \ mkdir -p /opt/extract; \ tar xzf "$rel" -C /opt/extract; \ tar xzf "$src" -C /opt/extract; \ mv "/opt/extract/${MEGAMEK_RELEASE_DIR}" "$MM_HOME"; \ mv "/opt/extract/${MEGAMEK_SRC_DIR}" "$MM_SRC"; \ rm -rf /opt/extract
COPY megamek/ /src/megamek/
# A patch that no longer applies fails the build here rather than at runtime,# which is the signal that upstream moved under us. verify-patches.sh then# confirms the jar differs from stock in exactly those classes.RUN OUT_JAR=/opt/MegaMek-patched.jar /src/megamek/apply-patches.sh \ && PATCHED_JAR=/opt/MegaMek-patched.jar /src/megamek/verify-patches.sh
RUN /src/megamek/prime-units-cache.sh
# The lance.blue skin: the committed mmconf/skins/lanceBlueSkin.xml (derived# once from 0.51.0's "BW - Default.xml", the skin GUIPreferences defaults to)# plus its two generated backgrounds, and the window icons become the# lance.blue insignia. The script verifies every image the skin references# exists in the assembled tree, so a MegaMek bump that moves the Bloodwolf# border art fails the build here, naming the missing paths.# clientsettings.xml.template selects the skin.RUN /src/megamek/skin/install-skin.sh
# Data no match can reach, removed here rather than in the runtime stage: this# tree is what `COPY --from=megamek` writes into the image, so what goes now is# never written into a layer. A delete in a later layer is a whiteout over bytes# that are still there and still pulled. See megamek/prune-data.sh - it is one# documented line per path, and it fails the build if a MegaMek bump moves one.# It runs after install-skin.sh so the skin's reference check sees the full tree.# The render benchmarks. Not scenarios anyone plays: they hold everything still# except unit count and board area, so two measurement runs differ only in the# thing being measured. Installed under their own directory so a MegaMek bump# cannot collide with them, and so `ls data/scenarios` still reads as upstream's# library plus ours.RUN mkdir -p "$MM_HOME/data/scenarios/lance-blue" \ && cp /src/megamek/scenarios/*.mms "$MM_HOME/data/scenarios/lance-blue/"
RUN /src/megamek/prune-data.sh
# The source tree and the platform launchers are build-time only.RUN rm -rf "$MM_SRC" \ "$MM_HOME"/logs/* "$MM_HOME"/savegames/* \ "$MM_HOME"/MegaMek.exe "$MM_HOME"/MegaMek.l4j.ini
# ---------------------------------------------------------------------------# suramadu: the AGPL-3.0 fork by manticore-projects of Webswing v20.2.5# ---------------------------------------------------------------------------FROM base AS suramaduARG SURAMADU_VERSIONARG SURAMADU_SHA256
# The distribution ships no LICENSE file. We redistribute it, so the AGPL text# has to travel with it, and a failure to fetch that text fails the build.# The zip is pinned like every other archive; see versions.env.RUN --mount=type=cache,target=/tmp/deps,sharing=locked \ set -eu; \ zip="/tmp/deps/suramadu-${SURAMADU_VERSION}.zip"; \ if [ -f "$zip" ] && ! echo "${SURAMADU_SHA256} $zip" | sha256sum -c - >/dev/null 2>&1; then \ echo "cached suramadu zip failed its checksum; refetching"; rm -f "$zip"; \ fi; \ if [ ! -f "$zip" ]; then \ echo "fetching suramadu-${SURAMADU_VERSION}.zip"; \ curl -fsSL --retry 3 --retry-delay 2 -o "$zip.part" \ "https://github.com/manticore-projects/suramadu/releases/download/${SURAMADU_VERSION}/suramadu-${SURAMADU_VERSION}.zip"; \ echo "${SURAMADU_SHA256} $zip.part" | sha256sum -c -; \ mv "$zip.part" "$zip"; \ else echo "using cached suramadu-${SURAMADU_VERSION}.zip"; fi; \ mkdir -p /opt/suramadu; \ unzip -q "$zip" -d /opt/suramadu; \ curl -fsSL -o /opt/suramadu/LICENSE.txt https://www.gnu.org/licenses/agpl-3.0.txt; \ rm -rf /opt/suramadu/logs/* /opt/suramadu/tmp/*
# web/index.html is a copy of the one in the war. Checked here, where the war# actually is, so a Suramadu bump that renames a script or tightens the CSP# fails the build instead of serving a page that loads nothing. The whole# directory is copied because the check also wants the files ours adds on top# of upstream's - theme.css and perf.js - to be next to it.ARG SURAMADU_WARCOPY suramadu/web/ /src/suramadu/web/COPY suramadu/verify-web.sh /src/suramadu/verify-web.shRUN WAR="/opt/suramadu/${SURAMADU_WAR}" /src/suramadu/verify-web.sh
# zip is needed to write the patched bundle back into the war, brotli to# regenerate the precompressed copy the browser actually gets. Installed here# rather than in base because only this stage needs them, and they never reach# the runtime image.RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ rm -f /etc/apt/apt.conf.d/docker-clean \ && apt-get update && apt-get install -y --no-install-recommends \ zip brotli
# The stock frontend parks keydown events behind a 100ms timer and only flushes# the input queue (including coalesced mousemove/wheel) on a 100ms interval, so# keyboard input lags by up to ~200ms and hover feedback by up to ~100ms.# patch-war.sh applies suramadu/patches/ to the bundle# inside the war - a patch that stops applying fails the build here, the same# signal as the MegaMek patchset - and verify-embed.sh then proves the served# bundle (and its precompressed copies) actually carry the change.COPY suramadu/patches/ /src/suramadu/patches/COPY suramadu/patch-war.sh suramadu/verify-embed.sh /src/suramadu/RUN WAR="/opt/suramadu/${SURAMADU_WAR}" /src/suramadu/patch-war.sh \ && WAR="/opt/suramadu/${SURAMADU_WAR}" /src/suramadu/verify-embed.sh
# The war's bundle gets its .br and .gz back above; our webFolder never had# any, and Suramadu's resource handler serves a precompressed sibling only when# one is already next to the file - it compresses nothing on the fly. Done in# this stage because gzip and brotli are already here for patch-war.sh and# neither belongs in the shipped image.COPY suramadu/precompress-web.sh /src/suramadu/RUN DIR=/src/suramadu/web /src/suramadu/precompress-web.sh
# ---------------------------------------------------------------------------# profiler: async-profiler, staged but never loaded unless ARENA_PROFILE is set# ---------------------------------------------------------------------------# Same .part-then-verify dance as the JDK above, for the same reason. Upstream# publishes a sha256 per release asset, so this one is pinned properly.## Only two files are kept. libasyncProfiler.so is the agent the JVMs load at# startup through -agentpath, which is why nothing here has to attach: attaching# wants a writable /tmp and a JVM whose cwd it can drop a handshake file into,# and both JVMs run out of the read-only image tree. asprof is the launcher for# the case where someone does want to attach to a match already in flight; it# costs 75KB. jfrconv (141KB) and include/ are dropped - we dump collapsed# stacks, never JFR, and nothing here compiles against the C API.FROM base AS profilerARG ASYNC_PROFILER_VERSIONARG ASYNC_PROFILER_ARCHIVEARG ASYNC_PROFILER_SHA256ARG ASYNC_PROFILER_DIR
RUN --mount=type=cache,target=/tmp/deps,sharing=locked \ set -eu; \ f="/tmp/deps/${ASYNC_PROFILER_ARCHIVE}"; \ if [ -f "$f" ] && ! echo "${ASYNC_PROFILER_SHA256} $f" | sha256sum -c - >/dev/null 2>&1; then \ echo "cached ${ASYNC_PROFILER_ARCHIVE} failed its checksum; refetching"; rm -f "$f"; \ fi; \ if [ ! -f "$f" ]; then \ echo "fetching ${ASYNC_PROFILER_ARCHIVE}"; \ curl -fsSL --retry 3 --retry-delay 2 -o "$f.part" \ "https://github.com/async-profiler/async-profiler/releases/download/v${ASYNC_PROFILER_VERSION}/${ASYNC_PROFILER_ARCHIVE}"; \ echo "${ASYNC_PROFILER_SHA256} $f.part" | sha256sum -c -; \ mv "$f.part" "$f"; \ else echo "using cached ${ASYNC_PROFILER_ARCHIVE}"; fi; \ mkdir -p /opt/extract /opt/async-profiler/lib /opt/async-profiler/bin; \ tar xzf "$f" -C /opt/extract; \ d="/opt/extract/${ASYNC_PROFILER_DIR}"; \ cp "$d/lib/libasyncProfiler.so" /opt/async-profiler/lib/; \ cp "$d/bin/asprof" /opt/async-profiler/bin/; \ cp "$d/LICENSE" /opt/async-profiler/LICENSE; \ rm -rf /opt/extract
# ---------------------------------------------------------------------------# arena: our own code# ---------------------------------------------------------------------------FROM base AS arenaCOPY --from=jre /opt/jdk /opt/jdkCOPY --from=megamek /opt/megamek /opt/megamekCOPY src/ /src/src/RUN JAVA_HOME=/opt/jdk MM_HOME=/opt/megamek OUT_JAR=/opt/arena.jar \ /src/src/build-jar.sh
# ---------------------------------------------------------------------------# lazy-png-agent: a javaagent that defers the PNG encode in Suramadu's# org.webswing.directdraw.model.ImageConst constructor to the first call of# toMessage() - see instrument/lazy-png-agent/. We have no source for# ImageConst, so this is a bytecode transform applied at classload rather# than a patch to Suramadu itself.## The only stage here that needs a build tool, so it gets its own base image# rather than javac by hand: the ASM tree API this is written against is not# worth hand-verifying bytecode offsets for. mavenCentral() is reached over# the network during the build, the same as the JDK and MegaMek/async-profiler# archives above; the Gradle dependency cache is its own mount so a rebuild# does not refetch ASM every time.# ---------------------------------------------------------------------------FROM gradle:8.10.2-jdk21 AS lazy-png-agentARG SURAMADU_WARCOPY instrument/lazy-png-agent/ /src/instrument/lazy-png-agent/WORKDIR /src/instrument/lazy-png-agentRUN --mount=type=cache,target=/home/gradle/.gradle,sharing=locked \ gradle --no-daemon build
# Run the transform against the real ImageConst before the jar is allowed into# the image. A javaagent that rewrites a third-party class at classload has no# business being on by default unless something proves the rewrite each build,# and until now nothing did: HarnessMain existed and was never run.## It constructs the real DirectDraw and ImageConst out of the war's own jars and# asserts what the transform is for - that the constructor stashed the source# image and did *not* fill the byte field. A rewrite that silently stopped# applying, or applied and broke, fails the build here rather than showing up as# a blank hex in somebody's match.COPY --from=suramadu /opt/suramadu /opt/suramaduRUN set -eu; \ mkdir -p /tmp/dd; \ unzip -q -o "/opt/suramadu/${SURAMADU_WAR}" 'WEB-INF/lib/*.jar' -d /tmp/dd; \ CP="$(find /tmp/dd -name '*.jar' | tr '\n' ':')"; \ echo "$CP" | grep -q directdraw \ || { echo "ERROR: no directdraw jar in the war; did Suramadu move it?" >&2; exit 1; }; \ javac -d /tmp/harness test/HarnessMain.java; \ java -javaagent:build/libs/lazy-png-agent.jar \ -cp "/tmp/harness:$CP" HarnessMain
# ---------------------------------------------------------------------------# test: everything that gates a build, run in the build environment# ---------------------------------------------------------------------------# git is here for tests/shell/test-deploy.sh, which builds throwaway# repositories to exercise the guards deploy.sh puts in front of a push. It# never reaches the runtime image - that is a separate FROM further down.FROM arena AS testRUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ rm -f /etc/apt/apt.conf.d/docker-clean \ && apt-get update && apt-get install -y --no-install-recommends \ shellcheck jq python3 git brotliCOPY --from=megamek /opt/MegaMek-patched.jar /opt/MegaMek-patched.jarCOPY . /src/RUN JAVA_HOME=/opt/jdk MM_HOME=/opt/megamek PATCHED_JAR=/opt/MegaMek-patched.jar \ /src/tests/run.sh
# ---------------------------------------------------------------------------# runtime# ---------------------------------------------------------------------------FROM debian:bookworm-slim AS runtime
# Only SURAMADU_WAR is declared here, because it feeds ENV below. The metadata# args - and the LABEL that consumes them - are at the very end of this stage on# purpose: BUILD_REF is the git SHA, so it changes on every commit, and anything# after an instruction that uses it is invalidated. With the LABEL up here, every# commit re-ran the apt install, every COPY and the fontconfig generation.ARG SURAMADU_WAR
# fontconfig is required: MegaMek draws armor pips, heat markers and status icons# with symbol codepoints, and the Suramadu toolkit reads a legacy# fontconfig.properties that we generate below. DejaVu has the coverage Noto# Sans lacks; Noto Sans Symbols2 supplies the pips.## The libx* packages are needed even though nothing renders to a display -# java.desktop links them regardless of the toolkit in use.RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ rm -f /etc/apt/apt.conf.d/docker-clean \ && apt-get update && apt-get install -y --no-install-recommends \ ca-certificates curl jq coreutils procps \ fontconfig fonts-dejavu-core fonts-noto-core \ libfreetype6 libfontconfig1 \ libx11-6 libxext6 libxi6 libxrender1 libxtst6 libxau6 libxdmcp6
ENV ARENA_HOME=/opt/arena \ ARENA_RUN=/run/arena \ ARENA_PORT_WS=8080 \ ARENA_PORT_MM=8850 \ ARENA_SURAMADU_WAR=${SURAMADU_WAR} \ JAVA_HOME=/opt/arena/jre \ PATH=/opt/arena/jre/bin:$PATH
COPY --from=jre /opt/jre ${ARENA_HOME}/jreCOPY --from=megamek /opt/megamek ${ARENA_HOME}/megamekCOPY --from=megamek /opt/MegaMek-patched.jar ${ARENA_HOME}/MegaMek-patched.jarCOPY --from=suramadu /opt/suramadu ${ARENA_HOME}/suramaduCOPY --from=arena /opt/arena.jar ${ARENA_HOME}/arena.jar
# ~675KB unpacked, ~290KB of the pulled image. Dead weight in every match that# does not set ARENA_PROFILE, and worth it: the alternative is a second image# that is no longer the one production runs.COPY --from=profiler /opt/async-profiler ${ARENA_HOME}/async-profiler
# Always loaded (see container/init/40-render-config.sh) - unlike the profiler,# this is not optional instrumentation.COPY --from=lazy-png-agent /src/instrument/lazy-png-agent/build/libs/lazy-png-agent.jar \ ${ARENA_HOME}/instrument/lazy-png-agent.jar
COPY container/ ${ARENA_HOME}/container/
# The repo groups these by component; the image groups them by function, because# the container scripts want one config directory.# Served instead of the war's index.html, so the loading page is ours, plus the# two files it adds: theme.css and perf.js, the latency instrument that stays# inert unless a match is opened with ?perf=1. Verified against the war in the# suramadu stage.# From the suramadu stage rather than the build context: it is where the .gz# and .br siblings are written, and a sibling that is not there is not served.COPY --from=suramadu /src/suramadu/web/ ${ARENA_HOME}/web/
COPY megamek/clientsettings.xml.template ${ARENA_HOME}/config/COPY megamek/gameoptions.xml.template ${ARENA_HOME}/config/COPY megamek/log4j2-quiet.xml ${ARENA_HOME}/config/COPY suramadu/suramadu.config.template ${ARENA_HOME}/config/COPY suramadu/jetty.properties.template ${ARENA_HOME}/config/COPY suramadu/log4j2-server.xml ${ARENA_HOME}/config/
# Shipped as source, not used at runtime: we distribute modified GPL-3.0 and# AGPL-3.0 code, so the modifications travel with the binaries that contain them.COPY megamek/patches/ ${ARENA_HOME}/patches/COPY suramadu/patches/ ${ARENA_HOME}/patches/suramadu/COPY LICENSING.md ${ARENA_HOME}/LICENSING.mdCOPY versions.env ${ARENA_HOME}/versions.env
# Generated here rather than in a build stage: the file records absolute font# paths, which only exist in this image. FONTCONFIG_STRICT makes a missing font# fail the build instead of shipping boxed glyphs.COPY suramadu/gen-fontconfig.sh /tmp/gen-fontconfig.shRUN chmod +x /tmp/gen-fontconfig.sh \ ${ARENA_HOME}/container/entrypoint.sh \ ${ARENA_HOME}/container/init/*.sh \ ${ARENA_HOME}/container/watch/*.sh \ ${ARENA_HOME}/container/exit/*.sh \ && MM_HOME=${ARENA_HOME}/megamek FONTCONFIG_STRICT=1 \ /tmp/gen-fontconfig.sh ${ARENA_HOME}/fontconfig.properties \ && rm /tmp/gen-fontconfig.sh
# Unprivileged. MegaMek and Suramadu write inside their own trees, so those# are the only writable paths; everything else stays owned by root.# --home-dir is the tmpfs, not ARENA_HOME. The JVM takes user.home from the# passwd entry rather than $HOME, and /opt/arena is root-owned, so pointing it# there made every JVM fail to create $user.home/.java/.userPrefs and log# "Couldn't create user preferences directory" twice at startup.# container/entrypoint.sh creates the directory before anything starts.RUN useradd --system --uid 10001 --home-dir ${ARENA_RUN}/home --shell /usr/sbin/nologin arena \ && mkdir -p ${ARENA_RUN} \ ${ARENA_HOME}/megamek/mmconf ${ARENA_HOME}/megamek/logs \ ${ARENA_HOME}/megamek/savegames \ ${ARENA_HOME}/suramadu/logs ${ARENA_HOME}/suramadu/tmp \ && chown -R arena:arena ${ARENA_RUN} \ ${ARENA_HOME}/megamek/mmconf ${ARENA_HOME}/megamek/logs \ ${ARENA_HOME}/megamek/savegames \ ${ARENA_HOME}/suramadu/logs ${ARENA_HOME}/suramadu/tmp
# MegaMek merges userdata/data over its own data/ for files opened through# MegaMekFile, and resolves "userdata" against its working directory. Pointing# it at the tmpfs keeps anything written there out of every image layer. The# target is created by container/init/30-assets.sh, so the symlink dangles# until launch.## Not the camo path. That merge does not cover camo - see the note in# 30-assets.sh - and per-match camo is staged on the tmpfs directly.RUN rm -rf ${ARENA_HOME}/megamek/userdata \ && ln -s ${ARENA_RUN}/userdata ${ARENA_HOME}/megamek/userdata
# MegaMek writes crew-portrait tooltips to data/images/temp at runtime, and# the image tree is read-only to the arena user. Point it at the tmpfs too;# container/init/30-assets.sh creates the target.RUN rm -rf ${ARENA_HOME}/megamek/data/images/temp \ && ln -s ${ARENA_RUN}/images-temp ${ARENA_HOME}/megamek/data/images/temp
USER arenaWORKDIR ${ARENA_HOME}EXPOSE 8080
# Last, so a new commit rebuilds only this metadata layer. See the note at the# top of this stage.ARG MEGAMEK_VERSIONARG SURAMADU_VERSIONARG BUILD_REF=unknownLABEL org.opencontainers.image.title="lance.blue arena" \ org.opencontainers.image.description="One MegaMek match, played in a browser via Suramadu" \ org.opencontainers.image.licenses="GPL-3.0-or-later AND AGPL-3.0-or-later AND CC-BY-NC-SA-4.0" \ org.opencontainers.image.revision="${BUILD_REF}" \ blue.lance.megamek.version="${MEGAMEK_VERSION}" \ blue.lance.suramadu.version="${SURAMADU_VERSION}"
ENTRYPOINT ["/opt/arena/container/entrypoint.sh"]