Files
Genarrative/deploy/container/gitea-ci-job.Dockerfile
lhk229 1e6b0e5684 优化 Gitea CI 镜像下载缓存与维护日志
使用专用 BuildKit builder 持久复用 Cargo 与 npm 下载缓存,并支持从可信镜像导入
按当前依赖物化镜像下载快照,配置独立缓存回收策略
补齐 AGC vendor 本地依赖清单并缩小构建上下文
增加缓存维护阶段、耗时和失败日志路径
补充下载缓存与构建上下文测试,同步部署说明和共享记忆
2026-09-22 11:23:46 +00:00

300 lines
15 KiB
Docker
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
ARG RUST_IMAGE=rust:1.98-bookworm@sha256:93ce27a88655056a51dbdd8f5f2d7ddc071c7b0070fb288a37b5a285fc83971e
ARG RUNNER_IMAGE=docker.gitea.com/runner-images:ubuntu-latest@sha256:58ea92624c7c09582e05594d95488331045053d3a3f34cf09649f2a32313a614
FROM ${RUST_IMAGE} AS rust-toolchain
RUN rustup component add rustfmt \
&& rustc --version \
&& cargo --version \
&& rustfmt --version
# 显式的一次性迁移入口:只导入下载缓存,不继承旧 CI 镜像层。
FROM rust-toolchain AS download-cache-seed
RUN --mount=type=bind,from=download-seed,target=/seed \
--mount=type=cache,id=genarrative-ci-cargo-cache-v1,target=/downloads/cargo-cache,sharing=locked \
--mount=type=cache,id=genarrative-ci-cargo-index-v1,target=/downloads/cargo-index,sharing=locked \
--mount=type=cache,id=genarrative-ci-npm-v1,target=/downloads/npm,sharing=locked \
cp -a /seed/cargo-cache/. /downloads/cargo-cache/ \
&& cp -a /seed/cargo-index/. /downloads/cargo-index/ \
&& cp -a /seed/npm/. /downloads/npm/
FROM rust-toolchain AS rust-dependency-cache
ENV CARGO_HTTP_MULTIPLEXING=false \
CARGO_NET_RETRY=10
COPY server-rs /tmp/genarrative-cargo-cache/server-rs
COPY apps/desktop-shell/src-tauri /tmp/genarrative-cargo-cache/desktop-shell
COPY apps/ai-game-creator-shell/src-tauri /tmp/genarrative-cargo-cache/apps/ai-game-creator-shell/src-tauri
COPY plugins/agc-cocos-editor/native/cocos-editor-bridge /tmp/genarrative-cargo-cache/plugins/agc-cocos-editor/native/cocos-editor-bridge
COPY plugins/agc-unity-editor/native/unity-editor-bridge /tmp/genarrative-cargo-cache/plugins/agc-unity-editor/native/unity-editor-bridge
COPY plugins/agc-godot-editor/native/godot-editor-bridge /tmp/genarrative-cargo-cache/plugins/agc-godot-editor/native/godot-editor-bridge
RUN --mount=type=cache,id=genarrative-ci-cargo-cache-v1,target=/usr/local/cargo/registry/cache,sharing=locked \
--mount=type=cache,id=genarrative-ci-cargo-index-v1,target=/usr/local/cargo/registry/index,sharing=locked \
find /tmp/genarrative-cargo-cache -name Cargo.toml -exec dirname {} \; \
| while IFS= read -r crate_dir; do \
mkdir -p "${crate_dir}/src"; \
: > "${crate_dir}/src/lib.rs"; \
: > "${crate_dir}/src/main.rs"; \
: > "${crate_dir}/src/active.rs"; \
done \
&& cargo_fetch_with_retry() { \
manifest_path="$1"; \
for attempt in 1 2 3 4 5; do \
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path "${manifest_path}"; then \
return 0; \
fi; \
if [ "${attempt}" -eq 5 ]; then \
return 1; \
fi; \
sleep "$((attempt * 5))"; \
done; \
} \
&& cargo_fetch_with_retry /tmp/genarrative-cargo-cache/server-rs/Cargo.toml \
&& cargo_fetch_with_retry /tmp/genarrative-cargo-cache/desktop-shell/Cargo.toml \
&& cargo_fetch_with_retry /tmp/genarrative-cargo-cache/apps/ai-game-creator-shell/src-tauri/Cargo.toml \
&& CARGO_NET_OFFLINE=true cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path /tmp/genarrative-cargo-cache/server-rs/Cargo.toml \
&& CARGO_NET_OFFLINE=true cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path /tmp/genarrative-cargo-cache/desktop-shell/Cargo.toml \
&& CARGO_NET_OFFLINE=true cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path /tmp/genarrative-cargo-cache/apps/ai-game-creator-shell/src-tauri/Cargo.toml \
&& mkdir -p /opt/ci-downloads/registry/cache \
&& cp -a /usr/local/cargo/registry/index /opt/ci-downloads/registry/ \
&& for source in /usr/local/cargo/registry/src/*/*; do \
[ -d "${source}" ] || continue; \
registry="$(basename "$(dirname "${source}")")"; \
package="$(basename "${source}")"; \
mkdir -p "/opt/ci-downloads/registry/cache/${registry}"; \
cp "/usr/local/cargo/registry/cache/${registry}/${package}.crate" \
"/opt/ci-downloads/registry/cache/${registry}/" || exit 1; \
done \
&& rm -rf /tmp/genarrative-cargo-cache
FROM ${RUNNER_IMAGE}
ARG NODE_VERSION=22.23.1
ARG NODE_LINUX_X64_SHA256=9749e988f437343b7fa832c69ded82a312e41a03116d766797ac14f6f9eee578
ARG NPM_VERSION=10.9.7
ARG GOOGLE_CHROME_VERSION=153.0.8010.52-1
ARG GOOGLE_LINUX_SIGNING_KEY_FINGERPRINT=EB4C1BFD4F042F6DDDCCEC917721F63BD38B4796
LABEL org.opencontainers.image.title="Genarrative Gitea CI job image" \
org.opencontainers.image.description="Ubuntu 24.04 CI image with fixed toolchains and prewarmed npm/Cargo caches" \
org.opencontainers.image.version="2026.09.20.2"
RUN test "$(dpkg --print-architecture)" = "amd64" \
&& find /etc/apt/sources.list.d -maxdepth 1 -type f \
! -name ubuntu.sources -delete \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
bubblewrap \
build-essential \
ca-certificates \
clang \
cmake \
curl \
ffmpeg \
file \
libayatana-appindicator3-dev \
libclang-dev \
libcurl4-openssl-dev \
librsvg2-dev \
libssl-dev \
libwebkit2gtk-4.1-dev \
libxdo-dev \
lld \
patchelf \
pkg-config \
ripgrep \
wget \
xz-utils \
&& google_key="$(mktemp)" \
&& curl --retry 3 --retry-all-errors --retry-delay 2 \
--proto '=https' --tlsv1.2 -fsSL \
https://dl.google.com/linux/linux_signing_key.pub \
-o "${google_key}" \
&& test "$(gpg --batch --show-keys --with-colons "${google_key}" \
| awk -F: '$1 == "fpr" { print $10; exit }')" \
= "${GOOGLE_LINUX_SIGNING_KEY_FINGERPRINT}" \
&& install -d -m 0755 /etc/apt/keyrings \
&& install -m 0644 "${google_key}" /etc/apt/keyrings/google-chrome.asc \
&& printf '%s\n' \
'deb [arch=amd64 signed-by=/etc/apt/keyrings/google-chrome.asc] https://dl.google.com/linux/chrome/deb/ stable main' \
> /etc/apt/sources.list.d/google-chrome.list \
&& rm -f "${google_key}" \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
"google-chrome-stable=${GOOGLE_CHROME_VERSION}" \
&& rm -rf /var/lib/apt/lists/*
RUN node_archive="node-v${NODE_VERSION}-linux-x64.tar.xz" \
&& curl --retry 3 --retry-all-errors --retry-delay 2 \
--proto '=https' --tlsv1.2 -fsSLO \
"https://nodejs.org/dist/v${NODE_VERSION}/${node_archive}" \
&& printf '%s %s\n' "${NODE_LINUX_X64_SHA256}" "${node_archive}" \
| sha256sum --check --strict \
&& install -d -m 0755 /usr/local/lib/genarrative-node \
&& tar -xJf "${node_archive}" \
--strip-components=1 \
--directory /usr/local/lib/genarrative-node \
&& rm -f "${node_archive}" \
&& ln -sfn /usr/local/lib/genarrative-node/bin/npx /usr/local/bin/npx \
&& ln -sfn /usr/local/lib/genarrative-node/bin/corepack /usr/local/bin/corepack
COPY --from=rust-dependency-cache /usr/local/cargo /usr/local/cargo
COPY --from=rust-dependency-cache /opt/ci-downloads/registry /usr/local/cargo/registry
COPY --from=rust-dependency-cache /usr/local/rustup /usr/local/rustup
ARG NPM_LOCK_SHA256
ARG SERVER_RUST_LOCK_SHA256
ARG DESKTOP_RUST_LOCK_SHA256
ARG AGC_RUST_LOCK_SHA256
COPY package.json package-lock.json /usr/local/share/genarrative-ci/npm/
COPY apps/admin-web/package.json /usr/local/share/genarrative-ci/npm/apps/admin-web/package.json
COPY apps/ai-game-creator-shell/package.json /usr/local/share/genarrative-ci/npm/apps/ai-game-creator-shell/package.json
COPY apps/desktop-shell/package.json /usr/local/share/genarrative-ci/npm/apps/desktop-shell/package.json
COPY apps/mobile-shell/package.json /usr/local/share/genarrative-ci/npm/apps/mobile-shell/package.json
COPY apps/preview-deployer-web/package.json /usr/local/share/genarrative-ci/npm/apps/preview-deployer-web/package.json
COPY packages/image-canvas-core/package.json /usr/local/share/genarrative-ci/npm/packages/image-canvas-core/package.json
COPY packages/image-canvas-react/package.json /usr/local/share/genarrative-ci/npm/packages/image-canvas-react/package.json
COPY packages/shared/package.json /usr/local/share/genarrative-ci/npm/packages/shared/package.json
COPY tools/spine-json-export-validator/package.json /usr/local/share/genarrative-ci/npm/tools/spine-json-export-validator/package.json
COPY server-rs/Cargo.lock /usr/local/share/genarrative-ci/locks/server-rs.Cargo.lock
COPY apps/desktop-shell/src-tauri/Cargo.lock /usr/local/share/genarrative-ci/locks/desktop-shell.Cargo.lock
COPY apps/ai-game-creator-shell/src-tauri/Cargo.lock /usr/local/share/genarrative-ci/locks/ai-game-creator-shell.Cargo.lock
COPY deploy/container/gitea-ci-checkout.sh /usr/local/bin/genarrative-gitea-checkout
COPY scripts/export-ci-npm-download-cache.mjs /usr/local/share/genarrative-ci/export-npm-cache.mjs
# npm registry 偶发 ECONNRESET,镜像预热也需要整命令级有界重试;
# 失败重试复用同一 npm cache,不会重复下载已完成的包。
RUN --mount=type=cache,id=genarrative-ci-npm-v1,target=/var/cache/genarrative-ci-npm,sharing=locked \
test -n "${NPM_LOCK_SHA256}" \
&& test -n "${SERVER_RUST_LOCK_SHA256}" \
&& test -n "${DESKTOP_RUST_LOCK_SHA256}" \
&& test -n "${AGC_RUST_LOCK_SHA256}" \
&& printf '%s %s\n' \
"${NPM_LOCK_SHA256}" \
/usr/local/share/genarrative-ci/npm/package-lock.json \
| sha256sum --check --strict \
&& printf '%s %s\n' \
"${SERVER_RUST_LOCK_SHA256}" \
/usr/local/share/genarrative-ci/locks/server-rs.Cargo.lock \
| sha256sum --check --strict \
&& printf '%s %s\n' \
"${DESKTOP_RUST_LOCK_SHA256}" \
/usr/local/share/genarrative-ci/locks/desktop-shell.Cargo.lock \
| sha256sum --check --strict \
&& printf '%s %s\n' \
"${AGC_RUST_LOCK_SHA256}" \
/usr/local/share/genarrative-ci/locks/ai-game-creator-shell.Cargo.lock \
| sha256sum --check --strict \
&& chmod 0755 /usr/local/bin/genarrative-gitea-checkout \
&& npm_ci_with_retry() { \
for attempt in 1 2 3 4 5; do \
if npm ci \
--cache /var/cache/genarrative-ci-npm \
--ignore-scripts \
--no-audit \
--no-fund \
--prefer-offline \
--prefix /usr/local/share/genarrative-ci/npm; then \
return 0; \
fi; \
if [ "${attempt}" -eq 5 ]; then \
return 1; \
fi; \
sleep "$((attempt * 5))"; \
done; \
} \
&& npm_ci_with_retry \
&& rm -rf \
/usr/local/share/genarrative-ci/npm/node_modules \
/usr/local/share/genarrative-ci/npm/apps/*/node_modules \
/usr/local/share/genarrative-ci/npm/packages/*/node_modules \
/usr/local/share/genarrative-ci/npm/tools/*/node_modules \
&& rm -rf /root/.npm/_cacache \
&& mkdir -p /root/.npm/_cacache \
&& node /usr/local/share/genarrative-ci/export-npm-cache.mjs \
/usr/local/lib/genarrative-node/lib/node_modules/npm \
/usr/local/share/genarrative-ci/npm/package-lock.json \
/var/cache/genarrative-ci-npm/_cacache /root/.npm/_cacache \
&& npm cache verify
# 依赖预热会在 workspace 内解析出 Node 发行包自带的 npm(例如 10.9.8),
# 并通过 PATH 里的 node_modules/.bin 影子化固定版本,因此全局固定版本在预热之后安装。
# 基础 runner 镜像把 /opt/acttoolcache/node/24.18.0/x64/bin 放在 PATH 最前,会影子化固定 Node / npm;
# 而 command.exec 只信任 /usr/local/bin 等系统目录,Node 必须在那里可见。
# 因此固定工具链写成绝对路径 wrapper,并用同一组链接覆盖 base 镜像残留的 toolcache bin,
# 避免 shell 是否登录改变解析结果,同时保证 command.exec 能找到受信任 node。
RUN npm install --global --prefix /usr/local/lib/genarrative-node \
--no-audit --no-fund "npm@${NPM_VERSION}" \
&& test "$(/usr/local/lib/genarrative-node/bin/node /usr/local/lib/genarrative-node/lib/node_modules/npm/bin/npm-cli.js --version)" = "${NPM_VERSION}" \
&& ln -sfn /usr/local/lib/genarrative-node/bin/node /usr/local/bin/node \
&& rm -f /usr/local/bin/npm \
&& printf '%s\n' '#!/bin/sh' \
'exec /usr/local/lib/genarrative-node/bin/node /usr/local/lib/genarrative-node/lib/node_modules/npm/bin/npm-cli.js "$@"' \
> /usr/local/bin/npm \
&& chmod 0755 /usr/local/bin/npm \
&& rm -rf /opt/acttoolcache/node/24.18.0/x64/bin \
&& mkdir -p /opt/acttoolcache/node/24.18.0/x64/bin \
&& ln -sfn /usr/local/lib/genarrative-node/bin/node /opt/acttoolcache/node/24.18.0/x64/bin/node \
&& ln -sfn /usr/local/bin/npm /opt/acttoolcache/node/24.18.0/x64/bin/npm \
&& ln -sfn /usr/local/lib/genarrative-node/bin/npx /opt/acttoolcache/node/24.18.0/x64/bin/npx \
&& test "$(readlink -f "$(command -v node)")" = "/usr/local/lib/genarrative-node/bin/node" \
&& test "$(node --version)" = "v${NODE_VERSION}" \
&& test "$(npm --version)" = "${NPM_VERSION}"
RUN install -m 0755 /usr/local/cargo/bin/rustup /usr/local/bin/rustup \
&& for command_name in cargo cargo-fmt rustc rustdoc rustfmt; do \
ln -sfn rustup "/usr/local/bin/${command_name}"; \
done
ENV CARGO_HOME=/usr/local/cargo \
GENARRATIVE_GITEA_CI_AGC_RUST_LOCK_SHA256=${AGC_RUST_LOCK_SHA256} \
GENARRATIVE_GITEA_CI_DESKTOP_RUST_LOCK_SHA256=${DESKTOP_RUST_LOCK_SHA256} \
GENARRATIVE_GITEA_CI_NPM_LOCK_SHA256=${NPM_LOCK_SHA256} \
GENARRATIVE_GITEA_CI_NPM_VERSION=${NPM_VERSION} \
GENARRATIVE_GITEA_CI_SERVER_RUST_LOCK_SHA256=${SERVER_RUST_LOCK_SHA256} \
NPM_CONFIG_AUDIT=false \
NPM_CONFIG_FETCH_RETRIES=10 \
NPM_CONFIG_FETCH_RETRY_FACTOR=2 \
NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT=60000 \
NPM_CONFIG_FETCH_RETRY_MINTIMEOUT=2000 \
NPM_CONFIG_FUND=false \
NPM_CONFIG_PREFER_OFFLINE=true \
RUSTUP_AUTO_INSTALL=0 \
RUSTUP_HOME=/usr/local/rustup \
PATH=/usr/local/lib/genarrative-node/bin:/usr/local/bin:/usr/local/cargo/bin:/opt/acttoolcache/node/24.18.0/x64/bin:/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
ARG IMAGE_REVISION=uncommitted
LABEL org.opencontainers.image.vendor="GenarrativeAI" \
org.opencontainers.image.version="2026.09.20.2" \
org.opencontainers.image.source="https://git.genarrative.world/GenarrativeAI/Genarrative" \
org.opencontainers.image.revision="${IMAGE_REVISION}" \
org.opencontainers.image.base.name="docker.gitea.com/runner-images:ubuntu-latest@sha256:58ea92624c7c09582e05594d95488331045053d3a3f34cf09649f2a32313a614" \
com.genarrative.ci.dockerfile="deploy/container/gitea-ci-job.Dockerfile" \
com.genarrative.ci.definition-sha256="${IMAGE_REVISION}"
RUN node --version \
&& test "$(npm --version)" = "${NPM_VERSION}" \
&& rustup show active-toolchain \
&& rustc --version \
&& cargo --version \
&& rustfmt --version \
&& google-chrome --version \
&& bwrap --version \
&& test -x /usr/local/bin/genarrative-gitea-checkout \
&& bash -n /usr/local/bin/genarrative-gitea-checkout \
&& test -d /root/.npm/_cacache \
&& test -d /usr/local/cargo/registry/cache \
&& rg --version \
&& ffmpeg -version | head -n 1