# Shared CI toolchain image (ersatztv#390) — .NET 10 SDK + Node 22 + prod-identical ffmpeg. # # Built/pushed by .gitea/workflows/ci-image.yml to 192.168.1.95:3000/timothy/ersatztv-ci. # The toolchain jobs in docker-build.yml run *inside* this image via `container:`, so they # install nothing at run time: no setup-dotnet, no setup-node, no `apt-get install ffmpeg`, # no `dotnet tool install`. Project dependencies (NuGet/npm) are NOT baked in — those change # per commit and stay on actions/cache. # # Layering rationale: we start from our OWN ffmpeg base and copy the .NET SDK on top, which is # exactly the pattern docker/Dockerfile uses for the prod image (`FROM ersatztv-ffmpeg` + # `COPY --from=dotnet-runtime /usr/share/dotnet`). That base is ghcr.io/linuxserver/baseimage-ubuntu:noble, # the same Ubuntu release as mcr.microsoft.com/dotnet/sdk:10.0-noble, so the copied SDK matches # the base's glibc/ICU. It also gives CI the *prod* ffmpeg build rather than a generic apt one — # the fidelity that the ersatztv#299 seeded-media/scanner E2E follow-ups will need. # # Keep the pins below in sync with what CI actually needs; Renovate tracks the image pins # (see renovate.json). The two dotnet tool versions mirror what docker-build.yml used to # `dotnet tool install` per run — bump them here, not in the workflow. # Keep the ffmpeg tag on the FROM below equal to the one docker/Dockerfile pins, so CI's ffmpeg # stays prod-identical. Renovate manages both pins (dockerfile manager); mirror any bump here. FROM --platform=linux/amd64 192.168.1.95:3000/timothy/ersatztv-ffmpeg:8.1.2 AS ci-base # The ffmpeg base sets ENTRYPOINT ["ffmpeg"] / CMD ["--help"] because it ships as an ffmpeg CLI. # This image is a CI *job container*: the runner starts it long-lived and execs steps into it, # so an ffmpeg entrypoint is wrong here. Reset both. ENTRYPOINT [] CMD ["/bin/bash"] ENV DEBIAN_FRONTEND=noninteractive # Toolchain the workflow steps and the actions themselves need: # git — actions/checkout # ca-certificates, curl — NodeSource setup + general fetches # tar, zstd, unzip, xz — actions/cache archive round-trip # python3 — scripts/generate-endpoint-index.py (the api-docs job) # jq — ad-hoc JSON in CI steps # The ffmpeg base is a runtime image, so none of these are guaranteed present. RUN apt-get update && \ apt-get install -y --no-install-recommends \ ca-certificates \ curl \ git \ jq \ python3 \ tar \ unzip \ xz-utils \ zstd && \ apt-get clean -y && \ rm -rf /var/lib/apt/lists/* # Node 22 (NodeSource) — matches the `node-version: '22.x'` the workflow used to request from # setup-node, and the node:22 base docker/Dockerfile builds the SPA with. act_runner also needs a # `node` on PATH inside the job container to execute JavaScript actions (actions/checkout, cache). RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ apt-get install -y --no-install-recommends nodejs && \ apt-get clean -y && \ rm -rf /var/lib/apt/lists/* # actions/checkout clones as root into a runner-mounted workspace, which trips git's # "detected dubious ownership in repository" guard and breaks every `git` call in a step # (scripts/e2e-local.sh's `git rev-parse --show-toplevel`, the base-ref diffs, ...). Trusting all # paths is the right call for a throwaway single-tenant CI container. RUN git config --global --add safe.directory '*' # .NET 10 SDK. Copied from the official SDK image rather than dotnet-install.sh: same mechanism as # docker/Dockerfile's runtime copy, one pinned artifact, no install script to fail at build time. # global.json pins sdk 10.0.0 with rollForward latestMinor, which this satisfies. COPY --from=mcr.microsoft.com/dotnet/sdk:10.0-noble-amd64 /usr/share/dotnet /usr/share/dotnet ENV DOTNET_ROOT=/usr/share/dotnet \ PATH="/usr/share/dotnet:/root/.dotnet/tools:${PATH}" \ DOTNET_CLI_TELEMETRY_OPTOUT=1 \ DOTNET_NOLOGO=1 \ DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 # Global dotnet tools the jobs used to install per run (5s + ~4s each, every run, forever). # dotnet-ef — the migrations job (was: `dotnet tool install --global dotnet-ef --version 9.0.12`) # dotnet-reportgenerator-globaltool — the test job's coverage summary # Baked here so the versions live in one tracked place instead of inline workflow strings. RUN dotnet tool install --global dotnet-ef --version 9.0.12 && \ dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.5.10 # Build-time smoke test: fail the IMAGE build, not a CI run, if any part of the toolchain is # unusable. Notably `dotnet --info` catches a missing ICU/glibc dependency in the base — the one # real risk of copying the SDK onto a non-Microsoft base. RUN set -eux; \ dotnet --info; \ dotnet ef --version; \ node --version; \ npm --version; \ git --version; \ python3 --version; \ ffmpeg -version; \ ffprobe -version; \ # reportgenerator has no clean version probe: `--version` prints the banner and then exits 1 # ("No report files specified"), so asserting on it would fail this build. Probe that the # dotnet-tool shim resolves on PATH instead. command -v reportgenerator