Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 16s
PR Gates / CI image pin matches docker/ci (pull_request) Failing after 16s
PR Gates / Docs update reminder (pull_request) Successful in 25s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 11s
PR Gates / decisions lifecycle (pull_request) Successful in 23s
Build CI Toolchain Image / Build & push CI image (push) Successful in 1m4s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 17m38s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 18m48s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 23m19s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Second pin-recovery cycle on this branch — main moved twice during review and each rebase rewrites the sha of the commit that touched docker/ci. Records that #598 now length-checks the pin at exactly 7 chars, which turns a locally-computed 8-char `git rev-parse --short` into a loud gate failure instead of a confusing manifest-unknown at image-pull time. That guard came from #594, filed by this session after hitting exactly that ambiguity. This commit also IS the recovery: it touches docker/ci, so ci-image.yml tags it and the pin can be re-pointed in the follow-up commit. Refs #445 #594
146 lines
8.4 KiB
Docker
146 lines
8.4 KiB
Docker
# 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.
|
|
#
|
|
# ⚠️ If a branch carrying a change to THIS file gets rebased, `ci-image-pin` goes red: the pin must
|
|
# equal the short sha of the commit that touched docker/ci, and a rebase rewrites it. Re-dispatching
|
|
# ci-image.yml does not help — it tags the branch HEAD, not this commit. It cost ersatztv#445 two
|
|
# recovery cycles because main moved twice mid-review. The pin is also length-checked (exactly 7
|
|
# chars, ersatztv#594) so a locally-computed 8-char `--short` now fails loudly at the gate instead of
|
|
# at image-pull time. Recovery, and the reason to land a toolchain change on its OWN branch BEFORE
|
|
# the work that consumes it: docs/ci-cd.md -> "CI toolchain image".
|
|
|
|
# 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
|
|
|
|
# Headless Chromium for the UI-E2E Playwright flows (ersatztv#445), baked so the `functional-e2e`
|
|
# job installs no browser at run time — the same "jobs install nothing" rule as the rest of this
|
|
# image. `--with-deps` also pulls the system libraries headless Chromium needs, which the ffmpeg
|
|
# runtime base does not ship.
|
|
#
|
|
# PLAYWRIGHT_VERSION **must equal web/package.json's `@playwright/test` pin** (which is deliberately
|
|
# EXACT, no caret). Playwright ties a browser REVISION to the package version and resolves a
|
|
# revision-specific path, so a mismatch means "Executable doesn't exist at ...". `scripts/e2e-ui.sh`
|
|
# fails early with that instruction rather than a raw Playwright error. Renovate bumps the npm pin;
|
|
# when it does, bump this ARG, let ci-image.yml publish the new :<sha>, then update the five
|
|
# container pins in docker-build.yml (docs/ci-cd.md -> "CI toolchain image").
|
|
ARG PLAYWRIGHT_VERSION=1.62.0
|
|
|
|
# A fixed, world-readable location instead of root's HOME cache: the path is then stable for
|
|
# scripts/e2e-ui.sh to detect, and independent of which user a step runs as.
|
|
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
|
|
|
|
# `chromium-headless-shell`, NOT `chromium` — measured on this exact base: the headless shell is
|
|
# 267M where full chromium is 656M, and `chromium.launch()` (headless is the default, and
|
|
# web/playwright.config.ts never asks for headed) resolves to the shell anyway. The tradeoff: a
|
|
# HEADED run inside this image would fail. That is fine for CI and intentional; a developer running
|
|
# headed locally uses their own `npx playwright install chromium`, not this image.
|
|
#
|
|
# `--with-deps` also installs the system libraries headless Chromium needs, which the ffmpeg runtime
|
|
# base does not ship. Verified on the real base (Ubuntu 24.04): Chromium launches as root in a
|
|
# container with NO `--no-sandbox`/`chromiumSandbox:false` opt-out, so the config needs no workaround.
|
|
RUN npm install -g "playwright@${PLAYWRIGHT_VERSION}" && \
|
|
playwright install --with-deps chromium-headless-shell && \
|
|
chmod -R a+rX /ms-playwright && \
|
|
npm cache clean --force && \
|
|
apt-get clean -y && \
|
|
rm -rf /var/lib/apt/lists/*
|
|
|
|
# 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; \
|
|
# Headless Chromium must not merely be present — it must LAUNCH. The interesting failure mode is
|
|
# a missing system library or a sandbox refusal at startup, neither of which a file test would
|
|
# catch. Fail the IMAGE build here rather than a CI run.
|
|
NODE_PATH="$(npm root -g)" node -e "require('playwright').chromium.launch().then(async b => { console.log('chromium ' + b.version()); await b.close(); })"; \
|
|
# 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
|