diff --git a/.gitlab/ci.yml b/.gitlab/ci.yml index 40a0b3ddd1..e1a84cbbf1 100644 --- a/.gitlab/ci.yml +++ b/.gitlab/ci.yml @@ -5,6 +5,8 @@ # Runner: docker executor + privileged dind (see [runners.docker] privileged = true). # # CI job images from ACR (${ACR_REGISTRY}/opencsg_public/...) to avoid Docker Hub timeouts. +# Dockerfile builder/runtime bases use opencsghq ACR mirrors that preserve the +# upstream multi-arch indexes for linux/amd64 and linux/arm64. # # Required CI/CD variables: # ACR_REGISTRY, ACR_USERNAME, ACR_PASSWORD @@ -32,6 +34,7 @@ variables: DOCKER_BUILDKIT: "1" BUILDX_NO_DEFAULT_ATTESTATIONS: "1" DOCKER_PLATFORMS: "linux/amd64,linux/arm64" + RUNTIME_ALPINE_BASE_IMAGE: "opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsghq/alpine:3.23" BINFMT_IMAGE: "${ACR_REGISTRY}/opencsg_public/binfmt:latest" BUILDKIT_CI_IMAGE: "${ACR_REGISTRY}/opencsg_public/moby-buildkit:buildx-stable-1" @@ -53,6 +56,7 @@ docker-build-push: - docker/csgclaw-cli/**/* - docker/csgclaw-cli/* - docker/Dockerfile + - .gitlab/ci.yml variables: DOCKER_HOST: tcp://docker:2375 DOCKER_TLS_CERTDIR: "" @@ -76,16 +80,37 @@ docker-build-push: - docker buildx rm "${BUILDX_BUILDER}" 2>/dev/null || true - echo "BuildKit image for buildx driver ${BUILDKIT_CI_IMAGE}" - docker buildx create --name "${BUILDX_BUILDER}" --driver docker-container --driver-opt "image=${BUILDKIT_CI_IMAGE}" --bootstrap --use + - | + set -euo pipefail + echo "Checking runtime base image platforms: ${RUNTIME_ALPINE_BASE_IMAGE}" + docker buildx imagetools inspect "${RUNTIME_ALPINE_BASE_IMAGE}" | tee /tmp/runtime-alpine-manifest.txt + grep -Eq 'Platform:[[:space:]]+linux/amd64' /tmp/runtime-alpine-manifest.txt + grep -Eq 'Platform:[[:space:]]+linux/arm64' /tmp/runtime-alpine-manifest.txt script: - | set -euo pipefail docker buildx build \ --platform "${DOCKER_PLATFORMS}" \ -f docker/Dockerfile \ + --build-arg "RUNTIME_ALPINE_BASE_IMAGE=${RUNTIME_ALPINE_BASE_IMAGE}" \ -t "${DOCKER_IMAGE}:${IMAGE_TAG}" \ --provenance=false \ --sbom=false \ --push \ . + - | + set -euo pipefail + docker buildx imagetools inspect "${DOCKER_IMAGE}:${IMAGE_TAG}" + for entry in "linux/amd64:x86_64" "linux/arm64:aarch64"; do + platform="${entry%%:*}" + expected_arch="${entry##*:}" + echo "Validating ${DOCKER_IMAGE}:${IMAGE_TAG} on ${platform}" + docker run --rm --platform "${platform}" --entrypoint /bin/sh "${DOCKER_IMAGE}:${IMAGE_TAG}" -ec " + test \"\$(uname -m)\" = \"${expected_arch}\" + /sbin/tini --version >/dev/null + /usr/local/bin/picoclaw version >/dev/null + /usr/local/bin/csgclaw-cli --help >/dev/null + " + done after_script: - docker buildx rm "picoclaw-mx-${CI_PIPELINE_ID}" 2>/dev/null || true diff --git a/docker/Dockerfile b/docker/Dockerfile index 4c644840d8..09619a0176 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,12 +1,15 @@ -# Base images mirrored to ACR (opencsg_public) so CI can pull without Docker Hub. -# Host: opencsg-registry.cn-beijing.cr.aliyuncs.com — push local pulls with: -# docker tag golang:1.25-alpine .../opencsg_public/golang:1.25-alpine -# docker tag alpine:3.23 .../opencsg_public/alpine:3.23 +# Base images mirrored to ACR (opencsghq) with full upstream multi-arch indexes +# so CI can pull without Docker Hub while preserving linux/amd64 and linux/arm64. # +ARG RUNTIME_ALPINE_BASE_IMAGE=opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsghq/alpine:3.23 + # ============================================================ # Stage 1: Build the picoclaw binary # ============================================================ -FROM opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsg_public/golang:1.25-alpine AS builder +FROM --platform=$BUILDPLATFORM opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsghq/golang:1.25-alpine AS builder +ARG TARGETOS +ARG TARGETARCH +ARG TARGETVARIANT # Alpine apk: use Aliyun mirror instead of dl-cdn (often slow or flaky in mainland CN). RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories @@ -26,22 +29,37 @@ RUN go mod download # Copy source and build COPY . . -RUN make build +RUN set -eux; \ + targetos="${TARGETOS:-linux}"; \ + targetarch="${TARGETARCH:-$(go env GOARCH)}"; \ + case "${targetarch}/${TARGETVARIANT:-}" in \ + arm/v*) export GOARM="${TARGETVARIANT#v}" ;; \ + esac; \ + make build GO="CGO_ENABLED=0 GOOS=${targetos} GOARCH=${targetarch} go" PLATFORM="${targetos}" ARCH="${targetarch}" # ============================================================ # Stage 1.5: Download platform-specific csgclaw-cli release tarball # ============================================================ -FROM opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsg_public/alpine:3.23 AS csgclaw-cli +FROM --platform=$BUILDPLATFORM opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsghq/alpine:3.23 AS csgclaw-cli ARG TARGETARCH # Override to pin a release, e.g. https://csgclaw.opencsg.com/releases/v1.0.0 ARG CSGCLAW_CLI_BASE_URL=https://csgclaw.opencsg.com/releases/latest RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \ && apk add --no-cache ca-certificates curl \ - && curl -fSL "${CSGCLAW_CLI_BASE_URL}/linux/${TARGETARCH}?package=csgclaw-cli" -o /tmp/csgclaw-cli.tar.gz \ + && set -eu; \ + arch="${TARGETARCH:-}"; \ + if [ -z "$arch" ]; then \ + case "$(uname -m)" in \ + x86_64) arch=amd64 ;; \ + aarch64) arch=arm64 ;; \ + *) echo "unsupported build arch: $(uname -m)" >&2; exit 1 ;; \ + esac; \ + fi; \ + curl -fSL "${CSGCLAW_CLI_BASE_URL}/linux/${arch}?package=csgclaw-cli" -o /tmp/csgclaw-cli.tar.gz \ && mkdir -p /tmp/extract \ && tar -xzf /tmp/csgclaw-cli.tar.gz -C /tmp/extract \ && set -eu; \ - bin="$(find /tmp/extract -type f \( -name csgclaw-cli -o -name "csgclaw-cli_linux_${TARGETARCH}" \) | head -n1)"; \ + bin="$(find /tmp/extract -type f \( -name csgclaw-cli -o -name "csgclaw-cli_linux_${arch}" \) | head -n1)"; \ if [ -z "$bin" ]; then bin="$(find /tmp/extract -type f -perm -111 | head -n1)"; fi; \ if [ -z "$bin" ]; then echo "csgclaw-cli binary not found in archive"; ls -laR /tmp/extract; exit 1; fi; \ install -m0755 "$bin" /csgclaw-cli @@ -49,7 +67,7 @@ RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories # ============================================================ # Stage 2: Minimal runtime image # ============================================================ -FROM opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsg_public/alpine:3.23 +FROM ${RUNTIME_ALPINE_BASE_IMAGE} RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories