build: add one-shot ARM64 image build/export script

scripts/build-arm64-image.sh automates the full linux/arm64 image build on Docker Desktop (WSL2): register qemu-aarch64 binfmt with the F flag so emulation works inside build containers, pre-pull the dotnet base images with resumable retries, build with dotnet restore routed through the host proxy and apt via the in-Dockerfile mirror, then docker save + gzip into a loadable archive. .gitignore now excludes the *.tar/*.tar.gz build artifacts, the qemu-*-static emulator binary, and the stray root package-lock.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
西街长安 2026-06-12 23:00:11 +08:00
parent a062bf84bf
commit 90fd9f0ba8
2 changed files with 124 additions and 0 deletions

7
.gitignore vendored
View File

@ -32,3 +32,10 @@ src/LiveRecorder.WebApi/live-recorder.db*
# Frontend source/config TypeScript files must stay trackable.
!frontend/**/*.ts
!frontend/**/*.tsx
# Docker image build artifacts and the ARM64 QEMU emulator binary.
*.tar
*.tar.gz
qemu-*-static
# Stray root lockfile created by running npm in the repo root (frontend has its own).
/package-lock.json

View File

@ -0,0 +1,117 @@
#!/usr/bin/env bash
#
# Build the LiveRecorder WebApi image for linux/arm64 and export it as a
# gzip-compressed `docker load`-able archive.
#
# 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 mcr.microsoft.com / 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 IMAGE_TAG=live-recorder-webapi:arm64
# OUTPUT=live-recorder-webapi-arm64.tar.gz WSL_DISTRO=Ubuntu
set -euo pipefail
# --- Resolve repo root (script lives in <root>/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
IMAGE_TAG="${IMAGE_TAG:-live-recorder-webapi:arm64}"
OUTPUT="${OUTPUT:-live-recorder-webapi-arm64.tar.gz}"
DOCKERFILE="${DOCKERFILE:-src/LiveRecorder.WebApi/Dockerfile}"
WSL_DISTRO="${WSL_DISTRO:-Ubuntu}"
SDK_IMAGE="mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim"
RUNTIME_IMAGE="mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim"
NO_PROXY_HOSTS="mirrors.tuna.tsinghua.edu.cn,.tsinghua.edu.cn,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 -----------------------------------------
# binfmt_misc lives in the (shared) WSL2 kernel and is lost on `wsl --shutdown`/reboot,
# so this is idempotent and re-runs the registration whenever emulation is missing.
ensure_arm64_emulation() {
if docker run --rm --platform linux/arm64 "$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..."
# Install the statically-linked emulator inside the distro, then re-register it pointing
# directly at the static binary with the F (fix-binary) flag — the kernel opens the
# interpreter at registration time so the held fd works inside Docker build containers.
wsl.exe -d "$WSL_DISTRO" -u root -- bash -lc '
set -e
if [ ! -x /usr/bin/qemu-aarch64-static ]; then
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 "$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 ----------------------------------------------------------------
build_image() {
log "Building $IMAGE_TAG for linux/arm64 (restore via proxy, apt via mirror)..."
docker buildx build \
--platform linux/arm64 \
-f "$DOCKERFILE" \
-t "$IMAGE_TAG" \
--build-arg "HTTP_PROXY=$PROXY_HOST" \
--build-arg "HTTPS_PROXY=$PROXY_HOST" \
--build-arg "NO_PROXY=$NO_PROXY_HOSTS" \
--pull=false \
--load \
.
}
# --- 4. Export + compress ----------------------------------------------------
export_image() {
log "Exporting $IMAGE_TAG -> $OUTPUT ..."
local tar="${OUTPUT%.gz}"
docker save "$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"
}
ensure_arm64_emulation
pull_with_retry "$SDK_IMAGE"
pull_with_retry "$RUNTIME_IMAGE"
build_image
export_image