diff --git a/.docker/Dockerfile.linux-amd64 b/.docker/Dockerfile.linux-amd64 new file mode 100644 index 00000000..191037db --- /dev/null +++ b/.docker/Dockerfile.linux-amd64 @@ -0,0 +1,24 @@ +# Pre-baked builder for Linux amd64 Tauri releases. +# All system dependencies are installed once here; CI jobs skip apt-get entirely. +# Rebuild when: Rust toolchain version changes, webkit2gtk/gtk major version changes, +# or Node.js major version changes. Tag format: rust-node +FROM rust:1.88-slim + +RUN apt-get update -qq \ + && apt-get install -y -qq --no-install-recommends \ + libwebkit2gtk-4.1-dev \ + libssl-dev \ + libgtk-3-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + patchelf \ + pkg-config \ + curl \ + perl \ + jq \ + git \ + && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get install -y --no-install-recommends nodejs \ + && rm -rf /var/lib/apt/lists/* + +RUN rustup target add x86_64-unknown-linux-gnu diff --git a/.docker/Dockerfile.linux-arm64 b/.docker/Dockerfile.linux-arm64 new file mode 100644 index 00000000..e8939475 --- /dev/null +++ b/.docker/Dockerfile.linux-arm64 @@ -0,0 +1,45 @@ +# Pre-baked cross-compiler for Linux arm64 Tauri releases (runs on Linux amd64). +# Bakes in: amd64 cross-toolchain, arm64 multiarch dev libs, Node.js, and Rust. +# This image takes ~15 min to build but is only rebuilt when deps change. +# Rebuild when: Rust toolchain version, webkit2gtk/gtk major version, or Node.js changes. +# Tag format: rust-node +FROM ubuntu:22.04 + +ARG DEBIAN_FRONTEND=noninteractive + +# Step 1: amd64 host tools and cross-compiler +RUN apt-get update -qq \ + && apt-get install -y -qq --no-install-recommends \ + curl git gcc g++ make patchelf pkg-config perl jq \ + gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \ + && rm -rf /var/lib/apt/lists/* + +# Step 2: Enable arm64 multiarch. Ubuntu uses ports.ubuntu.com for arm64 to avoid +# binary-all index conflicts with the amd64 archive.ubuntu.com mirror. +RUN dpkg --add-architecture arm64 \ + && sed -i 's|^deb http://archive.ubuntu.com|deb [arch=amd64] http://archive.ubuntu.com|g' /etc/apt/sources.list \ + && sed -i 's|^deb http://security.ubuntu.com|deb [arch=amd64] http://security.ubuntu.com|g' /etc/apt/sources.list \ + && printf '%s\n' \ + 'deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse' \ + 'deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse' \ + 'deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse' \ + > /etc/apt/sources.list.d/arm64-ports.list \ + && apt-get update -qq \ + && apt-get install -y -qq --no-install-recommends \ + libwebkit2gtk-4.1-dev:arm64 \ + libssl-dev:arm64 \ + libgtk-3-dev:arm64 \ + librsvg2-dev:arm64 \ + && rm -rf /var/lib/apt/lists/* + +# Step 3: Node.js 22 +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get install -y --no-install-recommends nodejs \ + && rm -rf /var/lib/apt/lists/* + +# Step 4: Rust 1.88 with arm64 cross-compilation target +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ + --default-toolchain 1.88.0 --profile minimal --no-modify-path \ + && /root/.cargo/bin/rustup target add aarch64-unknown-linux-gnu + +ENV PATH="/root/.cargo/bin:${PATH}" diff --git a/.docker/Dockerfile.windows-cross b/.docker/Dockerfile.windows-cross new file mode 100644 index 00000000..8399f8c6 --- /dev/null +++ b/.docker/Dockerfile.windows-cross @@ -0,0 +1,20 @@ +# Pre-baked cross-compiler for Windows amd64 Tauri releases (runs on Linux amd64). +# All MinGW and Node.js dependencies are installed once here; CI jobs skip apt-get entirely. +# Rebuild when: Rust toolchain version changes or Node.js major version changes. +# Tag format: rust-node +FROM rust:1.88-slim + +RUN apt-get update -qq \ + && apt-get install -y -qq --no-install-recommends \ + mingw-w64 \ + curl \ + nsis \ + perl \ + make \ + jq \ + git \ + && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get install -y --no-install-recommends nodejs \ + && rm -rf /var/lib/apt/lists/* + +RUN rustup target add x86_64-pc-windows-gnu diff --git a/.gitea/workflows/build-images.yml b/.gitea/workflows/build-images.yml new file mode 100644 index 00000000..dd9b2583 --- /dev/null +++ b/.gitea/workflows/build-images.yml @@ -0,0 +1,107 @@ +name: Build CI Docker Images + +# Rebuilds the pre-baked builder images and pushes them to the local Gitea +# container registry (172.0.0.29:3000). +# +# WHEN TO RUN: +# - Automatically: whenever a Dockerfile under .docker/ changes on master. +# - Manually: via workflow_dispatch (e.g. first-time setup, forced rebuild). +# +# ONE-TIME SERVER PREREQUISITE (run once on 172.0.0.29 before first use): +# echo '{"insecure-registries":["172.0.0.29:3000"]}' \ +# | sudo tee /etc/docker/daemon.json +# sudo systemctl restart docker +# +# Images produced: +# 172.0.0.29:3000/sarman/trcaa-linux-amd64:rust1.88-node22 +# 172.0.0.29:3000/sarman/trcaa-windows-cross:rust1.88-node22 +# 172.0.0.29:3000/sarman/trcaa-linux-arm64:rust1.88-node22 + +on: + push: + branches: + - master + paths: + - '.docker/**' + workflow_dispatch: + +concurrency: + group: build-ci-images + cancel-in-progress: false + +env: + REGISTRY: 172.0.0.29:3000 + REGISTRY_USER: sarman + +jobs: + linux-amd64: + runs-on: linux-amd64 + container: + image: docker:24-cli + options: -v /var/run/docker.sock:/var/run/docker.sock + steps: + - name: Checkout + run: | + apk add --no-cache git + git init + git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git + git fetch --depth=1 origin "$GITHUB_SHA" + git checkout FETCH_HEAD + - name: Build and push linux-amd64 builder + env: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + run: | + echo "$RELEASE_TOKEN" | docker login $REGISTRY -u $REGISTRY_USER --password-stdin + docker build \ + -t $REGISTRY/$REGISTRY_USER/trcaa-linux-amd64:rust1.88-node22 \ + -f .docker/Dockerfile.linux-amd64 . + docker push $REGISTRY/$REGISTRY_USER/trcaa-linux-amd64:rust1.88-node22 + echo "✓ Pushed $REGISTRY/$REGISTRY_USER/trcaa-linux-amd64:rust1.88-node22" + + windows-cross: + runs-on: linux-amd64 + container: + image: docker:24-cli + options: -v /var/run/docker.sock:/var/run/docker.sock + steps: + - name: Checkout + run: | + apk add --no-cache git + git init + git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git + git fetch --depth=1 origin "$GITHUB_SHA" + git checkout FETCH_HEAD + - name: Build and push windows-cross builder + env: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + run: | + echo "$RELEASE_TOKEN" | docker login $REGISTRY -u $REGISTRY_USER --password-stdin + docker build \ + -t $REGISTRY/$REGISTRY_USER/trcaa-windows-cross:rust1.88-node22 \ + -f .docker/Dockerfile.windows-cross . + docker push $REGISTRY/$REGISTRY_USER/trcaa-windows-cross:rust1.88-node22 + echo "✓ Pushed $REGISTRY/$REGISTRY_USER/trcaa-windows-cross:rust1.88-node22" + + linux-arm64: + runs-on: linux-amd64 + container: + image: docker:24-cli + options: -v /var/run/docker.sock:/var/run/docker.sock + steps: + - name: Checkout + run: | + apk add --no-cache git + git init + git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git + git fetch --depth=1 origin "$GITHUB_SHA" + git checkout FETCH_HEAD + - name: Build and push linux-arm64 builder + env: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + run: | + echo "$RELEASE_TOKEN" | docker login $REGISTRY -u $REGISTRY_USER --password-stdin + docker build \ + -t $REGISTRY/$REGISTRY_USER/trcaa-linux-arm64:rust1.88-node22 \ + -f .docker/Dockerfile.linux-arm64 . + docker push $REGISTRY/$REGISTRY_USER/trcaa-linux-arm64:rust1.88-node22 + echo "✓ Pushed $REGISTRY/$REGISTRY_USER/trcaa-linux-arm64:rust1.88-node22" diff --git a/tests/unit/ciDockerBuilders.test.ts b/tests/unit/ciDockerBuilders.test.ts new file mode 100644 index 00000000..8dd17250 --- /dev/null +++ b/tests/unit/ciDockerBuilders.test.ts @@ -0,0 +1,142 @@ +import { describe, expect, it } from "vitest"; +import { readFileSync } from "node:fs"; +import path from "node:path"; + +const root = process.cwd(); + +const readFile = (rel: string) => readFileSync(path.resolve(root, rel), "utf-8"); + +// ─── Dockerfiles ───────────────────────────────────────────────────────────── + +describe("Dockerfile.linux-amd64", () => { + const df = readFile(".docker/Dockerfile.linux-amd64"); + + it("is based on the pinned Rust 1.88 slim image", () => { + expect(df).toContain("FROM rust:1.88-slim"); + }); + + it("installs webkit2gtk 4.1 dev package", () => { + expect(df).toContain("libwebkit2gtk-4.1-dev"); + }); + + it("installs Node.js 22 via NodeSource", () => { + expect(df).toContain("nodesource.com/setup_22.x"); + expect(df).toContain("nodejs"); + }); + + it("pre-adds the x86_64 Linux Rust target", () => { + expect(df).toContain("rustup target add x86_64-unknown-linux-gnu"); + }); + + it("cleans apt lists to keep image lean", () => { + expect(df).toContain("rm -rf /var/lib/apt/lists/*"); + }); +}); + +describe("Dockerfile.windows-cross", () => { + const df = readFile(".docker/Dockerfile.windows-cross"); + + it("is based on the pinned Rust 1.88 slim image", () => { + expect(df).toContain("FROM rust:1.88-slim"); + }); + + it("installs mingw-w64 cross-compiler", () => { + expect(df).toContain("mingw-w64"); + }); + + it("installs nsis for Windows installer bundling", () => { + expect(df).toContain("nsis"); + }); + + it("installs Node.js 22 via NodeSource", () => { + expect(df).toContain("nodesource.com/setup_22.x"); + }); + + it("pre-adds the Windows GNU Rust target", () => { + expect(df).toContain("rustup target add x86_64-pc-windows-gnu"); + }); + + it("cleans apt lists to keep image lean", () => { + expect(df).toContain("rm -rf /var/lib/apt/lists/*"); + }); +}); + +describe("Dockerfile.linux-arm64", () => { + const df = readFile(".docker/Dockerfile.linux-arm64"); + + it("is based on Ubuntu 22.04 (Jammy)", () => { + expect(df).toContain("FROM ubuntu:22.04"); + }); + + it("installs aarch64 cross-compiler", () => { + expect(df).toContain("gcc-aarch64-linux-gnu"); + expect(df).toContain("g++-aarch64-linux-gnu"); + }); + + it("sets up arm64 multiarch via ports.ubuntu.com", () => { + expect(df).toContain("dpkg --add-architecture arm64"); + expect(df).toContain("ports.ubuntu.com/ubuntu-ports"); + expect(df).toContain("jammy"); + }); + + it("installs arm64 webkit2gtk dev package", () => { + expect(df).toContain("libwebkit2gtk-4.1-dev:arm64"); + }); + + it("installs Rust 1.88 with arm64 cross-compilation target", () => { + expect(df).toContain("--default-toolchain 1.88.0"); + expect(df).toContain("rustup target add aarch64-unknown-linux-gnu"); + }); + + it("adds cargo to PATH via ENV", () => { + expect(df).toContain('ENV PATH="/root/.cargo/bin:${PATH}"'); + }); + + it("installs Node.js 22 via NodeSource", () => { + expect(df).toContain("nodesource.com/setup_22.x"); + }); +}); + +// ─── build-images.yml workflow ─────────────────────────────────────────────── + +describe("build-images.yml workflow", () => { + const wf = readFile(".gitea/workflows/build-images.yml"); + + it("triggers on changes to .docker/ files on master", () => { + expect(wf).toContain("- master"); + expect(wf).toContain("- '.docker/**'"); + }); + + it("supports manual workflow_dispatch trigger", () => { + expect(wf).toContain("workflow_dispatch:"); + }); + + it("mounts the host Docker socket for image builds", () => { + expect(wf).toContain("-v /var/run/docker.sock:/var/run/docker.sock"); + }); + + it("authenticates to the local Gitea registry before pushing", () => { + expect(wf).toContain("docker login"); + expect(wf).toContain("--password-stdin"); + expect(wf).toContain("172.0.0.29:3000"); + }); + + it("builds and pushes all three platform images", () => { + expect(wf).toContain("trcaa-linux-amd64:rust1.88-node22"); + expect(wf).toContain("trcaa-windows-cross:rust1.88-node22"); + expect(wf).toContain("trcaa-linux-arm64:rust1.88-node22"); + }); + + it("uses docker:24-cli image for build jobs", () => { + expect(wf).toContain("docker:24-cli"); + }); + + it("runs all three build jobs on linux-amd64 runner", () => { + const matches = wf.match(/runs-on: linux-amd64/g) ?? []; + expect(matches.length).toBeGreaterThanOrEqual(3); + }); + + it("uses RELEASE_TOKEN secret for registry auth", () => { + expect(wf).toContain("secrets.RELEASE_TOKEN"); + }); +});