#!/usr/bin/env bash # # Build both the LiveRecorder WebApi and Frontend (nginx) images for # linux/arm64 and export them as a single gzip-compressed `docker load`-able # archive. # # The docker-compose.yml uses a split architecture: # nginx (frontend) — Vue SPA served by nginx, proxies /api/* to api:8080 # api (backend) — .NET 8 WebApi # # Both images must be ARM64 for a full deployment on an ARM64 host. # # Target environment: Windows + Docker Desktop (WSL2 backend) + a host HTTP proxy. # It encodes the workarounds discovered while building from a network where the # Docker daemon cannot reliably reach upstream MCR / Docker Hub: # # 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`. # 3. `dotnet restore` goes through the host proxy; apt uses the in-Dockerfile # Tsinghua mirror directly (NO_PROXY). # # Usage: # scripts/build-arm64-image.sh # # Override defaults via env vars: # PROXY=http://127.0.0.1:10808 # OUTPUT=live-recorder-arm64.tar.gz # WSL_DISTRO=Ubuntu set -euo pipefail # --- Resolve repo root (script lives in /scripts) --- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$ROOT_DIR" # --- Config --- PROXY="${PROXY:-http://127.0.0.1:10808}" PROXY_HOST="${PROXY_HOST:-http://host.docker.internal:${PROXY##*:}}" # container-visible proxy API_IMAGE_TAG="${API_IMAGE_TAG:-live-recorder-webapi:arm64}" FRONTEND_IMAGE_TAG="${FRONTEND_IMAGE_TAG:-live-recorder-frontend:arm64}" OUTPUT="${OUTPUT:-live-recorder-arm64.tar.gz}" WSL_DISTRO="${WSL_DISTRO:-Ubuntu}" DOTNET_SDK_IMAGE="${DOTNET_SDK_IMAGE:-mcr.m.daocloud.io/dotnet/sdk:8.0-bookworm-slim}" 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" 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; } # --- 1. Ensure ARM64 emulation works ----------------------------------------- ensure_arm64_emulation() { if docker run --rm --platform linux/arm64 "$DOTNET_RUNTIME_IMAGE" uname -m 2>/dev/null | grep -q aarch64; then log "ARM64 emulation already works — skipping binfmt registration." return 0 fi log "Registering qemu-aarch64 emulation via the '$WSL_DISTRO' WSL distro..." wsl.exe -d "$WSL_DISTRO" -u root -- bash -lc ' set -e if [ ! -x /usr/bin/qemu-aarch64-static ]; then 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" \ "$source_file" done apt-get update -qq apt-get install -y -qq qemu-user-static fi for name in qemu-aarch64 qemu-aarch64-static; do [ -e "/proc/sys/fs/binfmt_misc/$name" ] && echo -1 > "/proc/sys/fs/binfmt_misc/$name" || true done printf ":qemu-aarch64-static:M::\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-aarch64-static:F" \ > /proc/sys/fs/binfmt_misc/register ' || die "binfmt registration failed (need a Debian/Ubuntu WSL distro named '$WSL_DISTRO' with apt)." docker run --rm --platform linux/arm64 "$DOTNET_RUNTIME_IMAGE" uname -m 2>/dev/null | grep -q aarch64 \ || die "ARM64 emulation still not working after registration." log "ARM64 emulation registered and verified." } # --- 2. Pre-pull base images (resumable retries) ----------------------------- pull_with_retry() { local img="$1" 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 sleep 3 done docker image inspect "$img" >/dev/null 2>&1 || die "Could not pull $img (network)." } # --- 3. Build API image ------------------------------------------------------ build_api_image() { log "Building $API_IMAGE_TAG for linux/arm64 (restore via proxy, apt via mirror)..." docker buildx build \ --platform linux/arm64 \ -f src/LiveRecorder.WebApi/Dockerfile \ -t "$API_IMAGE_TAG" \ --build-arg "HTTP_PROXY=$PROXY_HOST" \ --build-arg "HTTPS_PROXY=$PROXY_HOST" \ --build-arg "NO_PROXY=$NO_PROXY_HOSTS" \ --build-arg "DOTNET_SDK_IMAGE=$DOTNET_SDK_IMAGE" \ --build-arg "DOTNET_RUNTIME_IMAGE=$DOTNET_RUNTIME_IMAGE" \ --pull=false \ --load \ . } # --- 4. Build Frontend (nginx) image ----------------------------------------- # Frontend dist is pre-built locally (npm run build inside frontend/) and copied # directly — avoids running Node.js under QEMU ARM64 emulation which is prohibitively # slow. Build context is frontend/ (not repo root) to bypass .dockerignore rules. build_frontend_image() { log "Building $FRONTEND_IMAGE_TAG for linux/arm64 (pre-built dist -> nginx)..." docker buildx build \ --platform linux/arm64 \ -f frontend/Dockerfile.arm64 \ -t "$FRONTEND_IMAGE_TAG" \ --pull=false \ --load \ frontend } # --- 5. Export both images as a single archive ------------------------------- export_images() { log "Exporting $API_IMAGE_TAG + $FRONTEND_IMAGE_TAG -> $OUTPUT ..." local tar="${OUTPUT%.gz}" docker save "$API_IMAGE_TAG" "$FRONTEND_IMAGE_TAG" -o "$tar" gzip -f "$tar" log "Done: $(ls -lh "$OUTPUT" | awk '{print $5, $9}')" log "Load on an arm64 host with: docker load < $OUTPUT" log "Then start with: docker compose up -d" } ensure_arm64_emulation pull_with_retry "$DOTNET_SDK_IMAGE" pull_with_retry "$DOTNET_RUNTIME_IMAGE" pull_with_retry "$NGINX_IMAGE" build_api_image build_frontend_image export_images