From 746979bdd5c25e56c700d520db0cdd55591c8a3a Mon Sep 17 00:00:00 2001 From: nanxun Date: Sat, 19 Sep 2026 13:21:42 +0800 Subject: [PATCH] fix: restore multi-arch api builds --- scripts/build-arm64-image.sh | 37 +++++++++++++++------------ src/LiveRecorder.WebApi/Dockerfile | 41 +++++++++++++++++++----------- src/LiveRecorder.WebApi/Program.cs | 1 + 3 files changed, 48 insertions(+), 31 deletions(-) diff --git a/scripts/build-arm64-image.sh b/scripts/build-arm64-image.sh index 6c4a4a8..6affafd 100644 --- a/scripts/build-arm64-image.sh +++ b/scripts/build-arm64-image.sh @@ -16,10 +16,10 @@ # # 1. ARM64 QEMU emulation is registered via binfmt_misc with the `F` # (fix-binary) flag so the kernel-held fd works inside build containers. -# 2. Base images are pre-pulled into the local cache (resumable retries), -# then the build runs with `--pull=false`. +# 2. The SDK is pulled for the builder platform while runtime images are +# pulled for ARM64, then the build runs with `--pull=false`. # 3. `dotnet restore` goes through the host proxy; apt uses the in-Dockerfile -# Tsinghua mirror directly (NO_PROXY). +# Huawei Cloud mirror directly (NO_PROXY). # # Usage: # scripts/build-arm64-image.sh @@ -46,6 +46,7 @@ DOTNET_SDK_IMAGE="${DOTNET_SDK_IMAGE:-mcr.m.daocloud.io/dotnet/sdk:8.0-bookworm- DOTNET_RUNTIME_IMAGE="${DOTNET_RUNTIME_IMAGE:-mcr.m.daocloud.io/dotnet/aspnet:8.0-bookworm-slim}" NGINX_IMAGE="${NGINX_IMAGE:-docker.m.daocloud.io/library/nginx:1.27-alpine}" NO_PROXY_HOSTS="mirrors.tuna.tsinghua.edu.cn,.tsinghua.edu.cn,mirrors.huaweicloud.com,.huaweicloud.com,mcr.m.daocloud.io,docker.m.daocloud.io,.m.daocloud.io,localhost,127.0.0.1" +BUILD_PLATFORM="${BUILD_PLATFORM:-$(docker version --format '{{.Server.Os}}/{{.Server.Arch}}')}" log() { printf '\n\033[1;36m[%s] %s\033[0m\n' "$(date +%H:%M:%S)" "$*"; } die() { printf '\n\033[1;31mERROR: %s\033[0m\n' "$*" >&2; exit 1; } @@ -64,10 +65,10 @@ ensure_arm64_emulation() { for source_file in /etc/apt/sources.list /etc/apt/sources.list.d/*.sources; do [ -f "$source_file" ] || continue sed -i \ - -e "s|deb.debian.org|mirrors.tuna.tsinghua.edu.cn|g" \ - -e "s|security.debian.org/debian-security|mirrors.tuna.tsinghua.edu.cn/debian-security|g" \ - -e "s|archive.ubuntu.com|mirrors.tuna.tsinghua.edu.cn|g" \ - -e "s|security.ubuntu.com|mirrors.tuna.tsinghua.edu.cn|g" \ + -e "s|deb.debian.org|mirrors.huaweicloud.com|g" \ + -e "s|security.debian.org/debian-security|mirrors.huaweicloud.com/debian-security|g" \ + -e "s|archive.ubuntu.com|mirrors.huaweicloud.com|g" \ + -e "s|security.ubuntu.com|mirrors.huaweicloud.com|g" \ "$source_file" done apt-get update -qq @@ -87,15 +88,19 @@ ensure_arm64_emulation() { # --- 2. Pre-pull base images (resumable retries) ----------------------------- pull_with_retry() { - local img="$1" i + local platform="$1" img="$2" i for i in $(seq 1 8); do - if docker image inspect "$img" >/dev/null 2>&1; then return 0; fi - log "Pulling $img (attempt $i/8) ..." - docker pull --platform linux/arm64 "$img" >/dev/null 2>&1 || true - docker image inspect "$img" >/dev/null 2>&1 && return 0 + if [ "$(docker image inspect --format '{{.Os}}/{{.Architecture}}' "$img" 2>/dev/null || true)" = "$platform" ]; then + return 0 + fi + log "Pulling $img for $platform (attempt $i/8) ..." + docker pull --platform "$platform" "$img" >/dev/null 2>&1 || true + if [ "$(docker image inspect --format '{{.Os}}/{{.Architecture}}' "$img" 2>/dev/null || true)" = "$platform" ]; then + return 0 + fi sleep 3 done - docker image inspect "$img" >/dev/null 2>&1 || die "Could not pull $img (network)." + die "Could not pull $img for $platform (network)." } # --- 3. Build API image ------------------------------------------------------ @@ -142,9 +147,9 @@ export_images() { } ensure_arm64_emulation -pull_with_retry "$DOTNET_SDK_IMAGE" -pull_with_retry "$DOTNET_RUNTIME_IMAGE" -pull_with_retry "$NGINX_IMAGE" +pull_with_retry "$BUILD_PLATFORM" "$DOTNET_SDK_IMAGE" +pull_with_retry linux/arm64 "$DOTNET_RUNTIME_IMAGE" +pull_with_retry linux/arm64 "$NGINX_IMAGE" build_api_image build_frontend_image export_images diff --git a/src/LiveRecorder.WebApi/Dockerfile b/src/LiveRecorder.WebApi/Dockerfile index 55fce24..1840c2f 100644 --- a/src/LiveRecorder.WebApi/Dockerfile +++ b/src/LiveRecorder.WebApi/Dockerfile @@ -1,7 +1,7 @@ ARG DOTNET_SDK_IMAGE=mcr.m.daocloud.io/dotnet/sdk:8.0-bookworm-slim ARG DOTNET_RUNTIME_IMAGE=mcr.m.daocloud.io/dotnet/aspnet:8.0-bookworm-slim -FROM ${DOTNET_SDK_IMAGE} AS build +FROM --platform=$BUILDPLATFORM ${DOTNET_SDK_IMAGE} AS build WORKDIR /src # Keep proxy settings available for any package endpoint fallback. The primary @@ -12,6 +12,8 @@ ARG NO_PROXY ARG http_proxy ARG https_proxy ARG no_proxy +ARG BUILDPLATFORM +ARG TARGETARCH ENV HTTP_PROXY=${HTTP_PROXY} \ HTTPS_PROXY=${HTTPS_PROXY} \ NO_PROXY=${NO_PROXY} \ @@ -22,29 +24,38 @@ ENV HTTP_PROXY=${HTTP_PROXY} \ # Map Docker TARGETARCH to .NET RID so native dependencies (SQLite, EF Core) # resolve to the correct platform. Framework-dependent output — apphost stays # at /p:UseAppHost=false. -ARG TARGETARCH - COPY . . -# Workaround for QEMU ARM64 emulation -ENV DOTNET_EnableWriteXorExecute=0 -ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -ENV DOTNET_GCConserveMemory=9 - -RUN case "$TARGETARCH" in \ +# Run the SDK on the builder's native platform and cross-publish for the target +# architecture. This keeps restore/publish out of QEMU for ARM64 builds. +RUN --mount=type=cache,id=liverecorder-nuget,target=/root/.nuget/packages,sharing=locked \ + case "$TARGETARCH" in \ amd64) RID=linux-x64 ;; \ arm64) RID=linux-arm64 ;; \ *) echo "ERROR: unknown TARGETARCH=$TARGETARCH" >&2; exit 1 ;; \ esac ; \ - echo "Building for TARGETARCH=$TARGETARCH -> RID=$RID" ; \ - dotnet restore "src/LiveRecorder.WebApi/LiveRecorder.WebApi.csproj" -p:RestoreUseStaticGraphEvaluation=true --disable-parallel || \ - dotnet restore "src/LiveRecorder.WebApi/LiveRecorder.WebApi.csproj" -p:RestoreUseStaticGraphEvaluation=true --disable-parallel || \ - dotnet restore "src/LiveRecorder.WebApi/LiveRecorder.WebApi.csproj" -p:RestoreUseStaticGraphEvaluation=true --disable-parallel ; \ - dotnet publish "src/LiveRecorder.WebApi/LiveRecorder.WebApi.csproj" -c Release -o /app/publish -r "$RID" /p:UseAppHost=false /p:DebugType=None /p:DebugSymbols=false /maxcpucount:1 + echo "Building on BUILDPLATFORM=$BUILDPLATFORM for TARGETARCH=$TARGETARCH -> RID=$RID" ; \ + restore_attempt=1 ; \ + until dotnet restore "src/LiveRecorder.WebApi/LiveRecorder.WebApi.csproj" \ + -r "$RID" -p:RestoreUseStaticGraphEvaluation=true --disable-parallel; do \ + if [ "$restore_attempt" -ge 3 ]; then \ + echo "ERROR: dotnet restore failed after $restore_attempt attempts for RID=$RID" >&2 ; \ + exit 1 ; \ + fi ; \ + restore_attempt=$((restore_attempt + 1)) ; \ + echo "dotnet restore failed; retrying attempt $restore_attempt/3 in 5 seconds" >&2 ; \ + sleep 5 ; \ + done ; \ + dotnet publish "src/LiveRecorder.WebApi/LiveRecorder.WebApi.csproj" \ + -c Release -o /app/publish -r "$RID" --no-restore \ + /p:UseAppHost=false /p:DebugType=None /p:DebugSymbols=false /maxcpucount:1 FROM ${DOTNET_RUNTIME_IMAGE} AS runtime -RUN sed -i 's|deb.debian.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list.d/debian.sources \ +RUN sed -i \ + -e 's|http://deb.debian.org|https://mirrors.huaweicloud.com|g' \ + -e 's|https://deb.debian.org|https://mirrors.huaweicloud.com|g' \ + /etc/apt/sources.list.d/debian.sources \ && apt-get update -o Acquire::ForceIPv4=true -o Acquire::Retries=5 \ && apt-get install -o Acquire::ForceIPv4=true -o Acquire::Retries=5 -y --no-install-recommends ffmpeg nodejs ca-certificates curl jq \ && rm -rf /var/lib/apt/lists/* diff --git a/src/LiveRecorder.WebApi/Program.cs b/src/LiveRecorder.WebApi/Program.cs index b120949..18ba11e 100644 --- a/src/LiveRecorder.WebApi/Program.cs +++ b/src/LiveRecorder.WebApi/Program.cs @@ -12,6 +12,7 @@ using LiveRecorder.Application.Abstractions.Settings; using LiveRecorder.Application.Abstractions.Storage; using LiveRecorder.Application.Models.Reports; using LiveRecorder.Application.Services; +using LiveRecorder.Domain.Enums; using LiveRecorder.Infrastructure.Persistence; using LiveRecorder.Infrastructure.Persistence.Repositories; using LiveRecorder.Infrastructure.Platforms.Bilibili;