# JellyTau Builder Image # Pre-built image with all dependencies for building, testing, and packaging: # - Android APK (SDK/NDK), Linux desktop (deb/rpm), # - Windows cross via the official Tauri path: MSVC target + cargo-xwin + NSIS # Arch packages build in a separate archlinux image (Dockerfile.arch) since # makepkg is Arch-specific. # Push to your registry: docker build -f Dockerfile.builder -t gitea.tourolle.paris/dtourolle/jellytau-builder:latest . FROM ubuntu:24.04 ENV DEBIAN_FRONTEND=noninteractive \ ANDROID_HOME=/opt/android-sdk \ NDK_VERSION=27.0.11902837 \ SDK_VERSION=36 \ BUILD_TOOLS_VERSION=35.0.0 \ RUST_BACKTRACE=1 \ PATH="/root/.bun/bin:/root/.cargo/bin:$PATH" \ CARGO_HOME=/root/.cargo # Install system dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ curl \ wget \ git \ ca-certificates \ unzip \ jq \ openjdk-17-jdk-headless \ pkg-config \ libssl-dev \ libclang-dev \ llvm-dev \ # Tauri Linux desktop dependencies (needed for `cargo test` on the host target) libglib2.0-dev \ libgtk-3-dev \ libwebkit2gtk-4.1-dev \ libjavascriptcoregtk-4.1-dev \ libsoup-3.0-dev \ librsvg2-dev \ libayatana-appindicator3-dev \ # mpv player library (linked via libmpv-sys) libmpv-dev \ && rm -rf /var/lib/apt/lists/* # Install Node.js 20.x from NodeSource RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ apt-get install -y --no-install-recommends nodejs && \ rm -rf /var/lib/apt/lists/* # Install Bun RUN curl -fsSL https://bun.sh/install | bash && \ ln -s /root/.bun/bin/bun /usr/local/bin/bun # Install Rust using rustup, pinned to an exact release. # # 🔴 RUST_VERSION must equal `channel` in src-tauri/rust-toolchain.toml. # # The two are a pair. rust-toolchain.toml is what makes a developer's `cargo # clippy` agree with CI's; this line is what makes the image already contain that # toolchain. If they drift, rustup silently downloads the pinned version the # first time cargo runs inside a job — a toolchain install at job time, which # CLAUDE.md's "🔴 CI installs no system tools" rule forbids (and which costs # ~1min plus a network dependency on every build). # # 🔴 Changing this line does NOT change CI on its own: the image must be # rebuilt and pushed (`scripts/build-builder-image.sh`) before the new pin is # authoritative. Bump rust-toolchain.toml and this line together, rebuild, push, # then merge. # # Was: `sh -s -- -y` (latest stable, whatever it happened to be on rebuild day). ENV RUST_VERSION=1.97.1 RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ sh -s -- -y --profile minimal --default-toolchain "$RUST_VERSION" && \ . $HOME/.cargo/env && \ rustup default "$RUST_VERSION" && \ rustup target add aarch64-linux-android && \ rustup target add armv7-linux-androideabi && \ rustup target add x86_64-linux-android && \ rustup component add rustfmt clippy && \ rustc --version && \ cargo clippy --version # Setup Android SDK RUN mkdir -p $ANDROID_HOME && \ mkdir -p /root/.android && \ echo '### User Sources for `android` cmd line tool ###' > /root/.android/repositories.cfg && \ echo 'count=0' >> /root/.android/repositories.cfg # Download and setup Android Command Line Tools RUN wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdline-tools.zip && \ unzip -q /tmp/cmdline-tools.zip -d $ANDROID_HOME && \ rm /tmp/cmdline-tools.zip && \ mkdir -p $ANDROID_HOME/cmdline-tools/latest && \ mv $ANDROID_HOME/cmdline-tools/* $ANDROID_HOME/cmdline-tools/latest/ 2>/dev/null || true # Accept all SDK licenses up front so Gradle can install/use components non-interactively RUN yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_HOME --licenses > /dev/null # Install Android SDK components (must match the compileSdk/targetSdk in the generated Gradle project) RUN $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_HOME \ "platform-tools" \ "platforms;android-$SDK_VERSION" \ "build-tools;$BUILD_TOOLS_VERSION" \ "ndk;$NDK_VERSION" \ --channel=0 2>&1 | grep -v "Warning" || true # Set NDK environment variable ENV NDK_HOME=$ANDROID_HOME/ndk/$NDK_VERSION # Gradle distribution. `tauri android init` regenerates gen/android with a # wrapper pointing at services.gradle.org, so every Android job would otherwise # download ~130MB of Gradle at build time — slow, and a hard failure when the # CDN hiccups ("Unexpected end of file from server"). Ship the distribution in # the image instead; scripts/sync-android-sources.sh repoints the regenerated # wrapper at this local copy. Keep GRADLE_VERSION in sync with the version # Tauri's generated wrapper requests. ENV GRADLE_VERSION=8.14.3 \ GRADLE_HOME=/opt/gradle/gradle-8.14.3 RUN mkdir -p /opt/gradle/dist && \ wget -q "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \ -O "/opt/gradle/dist/gradle-${GRADLE_VERSION}-bin.zip" && \ unzip -q "/opt/gradle/dist/gradle-${GRADLE_VERSION}-bin.zip" -d /opt/gradle && \ "$GRADLE_HOME/bin/gradle" --version ENV PATH="$GRADLE_HOME/bin:$PATH" # --------------------------------------------------------------------------- # Desktop packaging tools — kept in a trailing layer ON PURPOSE so that adding # or changing a packaging tool doesn't invalidate the expensive apt/rust/Android # layers above (a tool tweak becomes a ~1-2 min rebuild, not ~15). Covers Linux # (deb/rpm) and Windows cross (MSVC via cargo-xwin + NSIS). RUN apt-get update && apt-get install -y --no-install-recommends \ # Linux desktop packaging: rpmbuild for the .rpm bundle (deb needs nothing extra) rpm \ file \ # Windows cross-compile (official Tauri path: MSVC target via cargo-xwin). # clang provides clang-cl, the MSVC-compatible C compiler cc-rs uses to build # C deps (bundled sqlite, ring, ...); lld = linker; llvm = llvm-lib/ar etc; # nsis = installer generator. clang \ lld \ llvm \ nsis \ # AppImage bundling. linuxdeploy embeds xdg-open into the AppImage and # aborts the whole bundle if it is missing: # failed to bundle project: xdg-open binary not found # It is present on most desktop distros, which is why the AppImage built on # a developer machine and failed here. desktop-file-utils and zsync are the # other two linuxdeploy commonly wants (desktop-file-validate, and zsync for # delta updates), added together so a missing one does not cost another # image rebuild and another failed release build. xdg-utils \ desktop-file-utils \ zsync \ && rm -rf /var/lib/apt/lists/* \ # Ubuntu's clang package ships clang but NOT the clang-cl alias that cc-rs # invokes for MSVC targets. clang-cl is the same binary in MSVC-compat mode, # so provide it as a symlink. && ln -sf /usr/bin/clang /usr/local/bin/clang-cl # Windows rust target + cargo-xwin (downloads the MSVC CRT/SDK at build time). RUN . $HOME/.cargo/env && \ rustup target add x86_64-pc-windows-msvc && \ cargo install --locked cargo-xwin # --------------------------------------------------------------------------- # Supply-chain and docs tooling. # # cargo-deny — advisories/licences/bans/sources gate (src-tauri/deny.toml), # run by the `security` job. It fetches the RustSec advisory # database at run time; that is *data*, not a toolchain, so it # does not breach the no-installs-in-CI rule. # cargo-cyclonedx — SBOM for the Rust half of a release. # mdbook — builds the docs site. It used to be curl'd from GitHub # releases *inside* the job (publish-docs.yml), which was both a # breach of that rule and a hard dependency on GitHub's CDN # being up at publish time. Pinned to the version that job used. ENV MDBOOK_VERSION=v0.4.40 RUN . $HOME/.cargo/env && \ cargo install --locked cargo-deny cargo-cyclonedx && \ wget -q "https://github.com/rust-lang/mdBook/releases/download/${MDBOOK_VERSION}/mdbook-${MDBOOK_VERSION}-x86_64-unknown-linux-gnu.tar.gz" \ -O /tmp/mdbook.tar.gz && \ tar -xzf /tmp/mdbook.tar.gz -C /usr/local/bin && \ rm /tmp/mdbook.tar.gz && \ cargo deny --version && \ cargo cyclonedx --version && \ mdbook --version # --------------------------------------------------------------------------- # libmpv for Windows (DR-237) — the Windows build links it and ships the DLL. # # zhongfly/mpv-winbuild's **LGPL** dev build: libmpv-2.dll with FFmpeg compiled # in, built `-Dgpl=false` with no x264/x265 and no --enable-gpl (its # compile-lgpl-libmpv.patch). FFmpeg keeps --enable-version3, so the DLL is # LGPL-3.0: the installer ships the licence texts and keeps the DLL a separate, # replaceable file — THIRD_PARTY_NOTICES.md. Not the default (GPL) asset from # the same release, and not shinchiro's, which is GPL-3.0 only. # # Pinned by asset name *and* sha256: GitHub prunes old releases from that repo, # so a rebuild after pruning fails loudly here rather than quietly taking a # newer build. To bump, change all three values. # # The archive ships a MinGW import library (libmpv.dll.a); the MSVC target needs # `mpv.lib`, generated from the DLL's own mpv_* exports so it cannot name a # symbol the DLL lacks. Output: $LIBMPV_WIN_DIR/{libmpv-2.dll,mpv.lib,include}. ENV LIBMPV_WIN_RELEASE=2026-09-24-2a4eb8067c \ LIBMPV_WIN_ASSET=mpv-dev-lgpl-x86_64-20260924-git-2a4eb8067c.7z \ LIBMPV_WIN_SHA256=f89c6195e13bfce3e8c0cc5d7f5d889ebdcf12184a41c8288360f5acd1e7306c \ LIBMPV_WIN_DIR=/opt/libmpv-win64 RUN apt-get update && apt-get install -y --no-install-recommends libarchive-tools \ && rm -rf /var/lib/apt/lists/* \ && wget -q "https://github.com/zhongfly/mpv-winbuild/releases/download/${LIBMPV_WIN_RELEASE}/${LIBMPV_WIN_ASSET}" \ -O /tmp/mpv-dev.7z \ && echo "${LIBMPV_WIN_SHA256} /tmp/mpv-dev.7z" | sha256sum -c - \ && mkdir -p "$LIBMPV_WIN_DIR" \ && bsdtar -xf /tmp/mpv-dev.7z -C "$LIBMPV_WIN_DIR" libmpv-2.dll include \ && rm /tmp/mpv-dev.7z \ && { echo "LIBRARY libmpv-2.dll"; echo "EXPORTS"; \ llvm-readobj --coff-exports "$LIBMPV_WIN_DIR/libmpv-2.dll" \ | awk '/Name: mpv_/ { print " " $2 }'; } > "$LIBMPV_WIN_DIR/mpv.def" \ && llvm-lib /def:"$LIBMPV_WIN_DIR/mpv.def" /out:"$LIBMPV_WIN_DIR/mpv.lib" /machine:x64 \ && test "$(grep -c '^ mpv_' "$LIBMPV_WIN_DIR/mpv.def")" -ge 50 \ && ls -la "$LIBMPV_WIN_DIR" WORKDIR /app ENTRYPOINT ["/bin/bash"]