ARG RUST_IMAGE=rust:1.96-bookworm@sha256:19817ead3289c8c631c73df281e18b59b172f6a31f4f563290f69cddd06c30e9 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 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 RUN 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 \ && rm -rf /tmp/genarrative-cargo-cache FROM ${RUNNER_IMAGE} ARG NODE_VERSION=22.23.1 ARG NODE_LINUX_X64_SHA256=9749e988f437343b7fa832c69ded82a312e41a03116d766797ac14f6f9eee578 ARG GOOGLE_CHROME_VERSION=150.0.7871.181-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.07.23.1" 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/node /usr/local/bin/node \ && ln -sfn /usr/local/lib/genarrative-node/bin/npm /usr/local/bin/npm \ && 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 /usr/local/rustup /usr/local/rustup ARG NPM_LOCK_SHA256 ARG AGC_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/ai-game-creator-shell/package.json apps/ai-game-creator-shell/package-lock.json /usr/local/share/genarrative-ci/agc-npm/ 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 RUN test -n "${NPM_LOCK_SHA256}" \ && test -n "${AGC_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' \ "${AGC_NPM_LOCK_SHA256}" \ /usr/local/share/genarrative-ci/agc-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 \ --ignore-scripts \ --no-audit \ --no-fund \ --prefer-offline \ --prefix /usr/local/share/genarrative-ci/npm \ && rm -rf /usr/local/share/genarrative-ci/npm/node_modules \ && npm ci \ --ignore-scripts \ --no-audit \ --no-fund \ --prefer-offline \ --prefix /usr/local/share/genarrative-ci/agc-npm \ && rm -rf /usr/local/share/genarrative-ci/agc-npm/node_modules \ && npm cache verify 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_NPM_LOCK_SHA256=${AGC_NPM_LOCK_SHA256} \ 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_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.08.07.1" \ 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 \ && 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