Files
punktfunk/ci/rust-ci.Dockerfile
enricobuehler eed7b5e589
ci / bun-nix (pull_request) Successful in 28s
ci / docs-drift (pull_request) Successful in 30s
ci / web (pull_request) Successful in 1m14s
ci / docs-site (pull_request) Successful in 1m15s
ci / rust-arm64 (pull_request) Successful in 2m11s
apple / swift (pull_request) Successful in 2m11s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / rust (pull_request) Successful in 5m47s
android / android (pull_request) Successful in 6m31s
nix / flake (pull_request) Successful in 6m35s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Successful in 2m52s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 6m15s
fix(ci): pin the tools the builder images bake in, not just the ones the jobs fetch
The workflow-level pins closed the bootstrap path only. On the normal path
the bun that gets vendored into the published .deb/.rpm comes from the
builder image, and every image still installed it with the upstream
installer piped into bash — so the script still chose bytes that ship to
users. rust-ci and fedora-rpm now take the same pinned, SHA-256-checked
bun-v1.3.14 asset the workflows do; arch-ci takes bun from pacman, which
verifies package signatures.

Same class, found while sweeping and worse than the cited sites: five
images fetched sccache over a version-pinned URL with no integrity check at
all. sccache is RUSTC_WRAPPER for every binary we ship — it can serve
poisoned object files straight into a signed package, which is the position
the review called the highest-leverage in a build. Download, verify against
upstream's published sum, then extract.

packaging/flatpak/build-flatpak.sh took flatpak-cargo-generator.py from a
mutable master, the same fetch flatpak.yml just pinned; both now name the
same commit and sum, so the local build and CI agree.

Arch note: bun rides the existing -Syu transaction rather than a later
layer. A separate layer resolves against the DB baked into the CACHED -Syu
layer, and Arch mirrors carry only current versions, so a cache-hit rebuild
months on would fail to fetch a package the stale snapshot names.

None of this takes effect until the images are rebuilt: docker.yml's
`builders` job keys on the git tree hash of ci/, so this re-keys the whole
family. Until then the workflow bun pins sit behind `command -v bun ||` and
short-circuit against the image's baked bun.

rustup's own installer is left piped, as apple.yml already does — pinning
rustup-init is a separate decision, and the same argument reaches every
image at once.
2026-08-26 18:17:26 +02:00

97 lines
6.0 KiB
Docker

# CI builder for the Rust workspace — Ubuntu 26.04 to match the dev/host boxes
# (FFmpeg 8 / libavcodec 62, PipeWire 1.6). Used by .gitea/workflows/ci.yml as the job
# container; rebuilt+pushed by .gitea/workflows/docker.yml.
#
# docker build -f ci/rust-ci.Dockerfile -t punktfunk-rust-ci ci
#
# The workspace links real system libs at build time (CLAUDE.md "Pinned crate facts"):
# FFmpeg, PipeWire, Opus, GL/EGL/GBM — and libcuda, which has no real driver here; the
# zerocopy path only needs the symbols at link time, so a driver userspace package plus a
# libcuda.so -> libcuda.so.1 symlink stands in for it (CI never executes the CUDA path).
FROM ubuntu:26.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
# toolchain + bindgen; nodejs runs the JS actions (checkout/cache); unzip extracts the pinned bun zip
build-essential clang libclang-dev pkg-config cmake git curl ca-certificates nodejs unzip \
# mold: the link-phase accelerator. Linking is the one thing sccache cannot cache, and this
# image relinks the whole workspace on every job. Wired via cargo-config-mold.toml below.
mold \
# ffmpeg-next 9, built against whatever libav* 26.04 ships (FFmpeg 8 / libavcodec 62 today).
# The crate major is a CEILING — ffmpeg-sys-next 9 spans libavcodec 56..63 — so this image does
# not need to move in lockstep with Arch's FFmpeg 9; it just links what the distro has.
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev \
libavdevice-dev \
# capture / audio / display stacks (+xkbcommon for the wlr input backend)
libpipewire-0.3-dev libopus-dev libwayland-dev libxkbcommon-dev \
# zerocopy link deps (GL via libglvnd, EGL, GBM)
libgl-dev libegl-dev libgbm-dev \
# punktfunk-client-linux (GTK4/libadwaita shell, SDL3 gamepads)
libgtk-4-dev libadwaita-1-dev libsdl3-dev \
# No libvulkan-dev: nothing in the workspace compiles or links against Vulkan (pyrowave-sys
# bindgens its own vendored headers, and both host and client reach Vulkan through ash, which
# dlopens the loader), so neither the build nor deb.yml's dpkg-shlibdeps ever asks for it.
&& rm -rf /var/lib/apt/lists/*
# bun — builds the punktfunk-web console in deb.yml (which runs the web build in THIS image).
# ci.yml's web/docs jobs use the oven/bun image instead, so this is only for the deb job.
#
# A PINNED release asset, checked by SHA-256 — never `curl https://bun.sh/install | bash`.
# build-web-deb.sh VENDORS this very binary into the punktfunk-web .deb, so the installer would be
# upstream code choosing bytes a signing job then publishes. ONE bun across the repo: same version,
# asset and sum as deb.yml and rpm.yml — bump BUN_VERSION and BUN_SHA together (the sums are in the
# release's SHASUMS256.txt). `-baseline` on purpose: it needs no AVX2, so the bun we ship starts on
# every x86-64 box — something the auto-detecting installer never promised, since it reads the
# BUILDER's CPU, not the user's.
ARG BUN_VERSION=1.3.14
ARG BUN_SHA=a063908ae08b7852ca10939bbdc6ceed3ddabce8fb9402dce83d65d73b36e6c7
RUN curl -fsSL -o /tmp/bun.zip \
"https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/bun-linux-x64-baseline.zip" \
&& echo "${BUN_SHA} /tmp/bun.zip" | sha256sum -c - \
&& unzip -q -o -j /tmp/bun.zip '*/bun' -d /tmp \
&& install -m0755 /tmp/bun /usr/local/bin/bun \
&& rm -f /tmp/bun.zip /tmp/bun \
&& bun --version
# libcuda link stub: the NVIDIA userspace library (no kernel module needed) provides
# every cuXxx symbol. On 26.04 the package already ships the libcuda.so dev symlink;
# -sf keeps this idempotent if a future package drops it again.
RUN apt-get update \
&& apt-get install -y --no-install-recommends libnvidia-compute-580-server \
&& rm -rf /var/lib/apt/lists/* \
&& ln -sf libcuda.so.1 /usr/lib/x86_64-linux-gnu/libcuda.so \
&& test -e /usr/lib/x86_64-linux-gnu/libcuda.so.1
# Toolchain shared across CI users (jobs may run as different uids).
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --no-modify-path --profile minimal \
--component rustfmt,clippy \
&& chmod -R a+w "$RUSTUP_HOME" "$CARGO_HOME" \
&& rustc --version && cargo clippy --version && cargo fmt --version
# Shared compile cache: jobs set RUSTC_WRAPPER=sccache (backend = RustFS S3 on the LAN,
# see .gitea/workflows — the env lives there so dev use of this image stays uncached).
# musl build: one static binary serves the Ubuntu and Fedora images alike.
# Checked by SHA-256, like the bun pin: sccache is RUSTC_WRAPPER, so it sits in front of every
# rustc invocation that produces a SHIPPED binary. Bump SCCACHE_VERSION and SCCACHE_SHA together —
# upstream publishes the sum as <asset>.tar.gz.sha256 next to the release asset.
ARG SCCACHE_VERSION=0.10.0
ARG SCCACHE_SHA=1fbb35e135660d04a2d5e42b59c7874d39b3deb17de56330b25b713ec59f849b
RUN curl -fsSL -o /tmp/sccache.tar.gz \
"https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl.tar.gz" \
&& echo "${SCCACHE_SHA} /tmp/sccache.tar.gz" | sha256sum -c - \
&& tar -xzf /tmp/sccache.tar.gz --wildcards --strip-components=1 -C /usr/local/bin '*/sccache' \
&& rm -f /tmp/sccache.tar.gz \
&& sccache --version
# Link x86_64 with mold (see the file's own header for the rustflags-precedence traps).
#
# The assertion is the point: an image carrying the flag but NOT the linker would fail every cargo
# invocation in every consuming job, which is a catastrophic way to find out that a base image
# renamed the package. `mold --version` fails the docker build instead, so nothing is pushed and
# `:latest` keeps pointing at the previous working image — consumers never see it.
COPY cargo-config-mold.toml /usr/local/cargo/config.toml
RUN mold --version && test -r /usr/local/cargo/config.toml