Files
MoShiro/ci/build-dev-image.sh
T
justme 4515d7b075
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/verify Pipeline was successful
ci/woodpecker/push/dev-image Pipeline failed
scripts: publish unified Agent artifacts and restore CI coverage [CI]
Remove Probe build jobs and release paths. Transfer Gitea publishing credentials to both Agent architecture jobs, publish each platform archive once, and run slow tests after development images are published.
2026-10-21 11:56:06 +08:00

205 lines
8.3 KiB
Bash

#!/usr/bin/env bash
# Build and push one architecture-specific development image (main push with [CI]) to moshiro-dev.
set -euo pipefail
[[ "${CI_PIPELINE_EVENT:-}" == push && "${CI_COMMIT_BRANCH:-}" == main && "${CI_COMMIT_MESSAGE:-}" == *"[CI]"* ]] || {
echo "development image builds require a main push with [CI] in the commit message" >&2
exit 2
}
component="${1:?usage: build-dev-image.sh <backend|agent> [amd64|arm64]}"
arch="${2:-amd64}"
case "$component" in
backend|agent) ;;
*) echo "unsupported component: $component" >&2; exit 2 ;;
esac
case "$arch" in
amd64|arm64) ;;
*) echo "unsupported architecture: $arch" >&2; exit 2 ;;
esac
if [[ "$component" == backend && "$arch" != amd64 ]]; then
echo "the main development pipeline currently builds Backend for amd64 only" >&2
exit 2
fi
[[ -n "${ZOT_USERNAME:-}" && -n "${ZOT_SECRET:-}" ]] || {
echo "Zot credentials are required" >&2
exit 2
}
[[ "${CI_COMMIT_SHA:-}" =~ ^[0-9a-fA-F]{40}$ ]] || {
echo "CI_COMMIT_SHA must be a full commit SHA" >&2
exit 2
}
active_command_pid=""
cancel_active_command() {
local pid="${active_command_pid:-}"
[[ -n "$pid" ]] || return 0
kill -TERM -- "-$pid" 2>/dev/null || true
kill -TERM "$pid" 2>/dev/null || true
for _ in {1..10}; do
kill -0 -- "-$pid" 2>/dev/null || break
sleep 1
done
kill -KILL -- "-$pid" 2>/dev/null || true
kill -KILL "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
active_command_pid=""
}
handle_cancel() {
trap - TERM INT HUP
cancel_active_command
exit 143
}
trap handle_cancel TERM INT HUP
run_bounded() {
local duration="$1" status=0
shift
# setsid gives the command its own process group so a cancel signal can
# terminate Buildah and its children even when the local backend only signals
# the shell process. timeout is the fallback if cancellation is lost entirely.
setsid timeout --foreground --signal=TERM --kill-after=30s "$duration" "$@" <&0 &
active_command_pid=$!
if wait "$active_command_pid"; then
status=0
else
status=$?
fi
active_command_pid=""
return "$status"
}
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# 版本:v<下一个补丁版本>-dev.<自最近 tag 的提交数>-<提交前 7 位>(见 ci/version.sh);写进工作区的 Directory.Build.props,镜像标签、构建信息、签名清单都用它
source "$root/ci/version.sh"
moshiro_apply_version
registry="${ZOT_REGISTRY:-registry.oeo.one}"
repository="$registry/moshiro-dev/moshiro-$component"
work="$root/.release/dev-image-build/$component-$arch"
base_home="/home/justme/woodpecker/buildah-home/dev-image-build-noble-aot-v1-$component-$arch"
# 镜像标签不带 v(和正式版一致):latest 和 <版本>;构建信息和清单里的 tag 带 v
image_version="$MOSHIRO_VERSION"
mkdir -p "$work" "$base_home"
export REGISTRY_AUTH_FILE="$work/$component-auth.json"
if [[ ! -s "$REGISTRY_AUTH_FILE" ]]; then
printf '{"auths":{}}\n' > "$REGISTRY_AUTH_FILE"
chmod 600 "$REGISTRY_AUTH_FILE"
fi
if [[ "$component" == backend ]]; then
BUILDAH_HOME="$base_home" \
ZOT_REGISTRY="$registry" \
ZOT_IMAGE_REPOSITORY="$repository" \
IMAGE_TAGS="latest,$image_version" \
IMAGE_PUBLISH=true \
MOSHIRO_PUSH_GATE="$root/ci/wait-for-checks.sh" \
run_bounded 30m bash "$root/ci/release-linux.sh" "$arch" backend
exit 0
fi
export HOME="$base_home"
export BUILDAH_HOME="$base_home"
source "$root/ci/buildah-env.sh"
moshiro_setup_buildah "$base_home"
context="$work/$component"
local_image="localhost/moshiro-$component:dev-$arch-check"
node_rid="linux-musl-x64"
[[ "$arch" == arm64 ]] && node_rid="linux-musl-arm64"
# Build a dedicated .NET 10 NativeAOT/AotAnywhere environment in this isolated
# Buildah store. Keeping NuGet inside image layers avoids a shared writable
# package mount being corrupted by canceled or parallel builds.
builder_image="localhost/moshiro-dotnet10-nativeaot-aotanywhere:ci"
source "$root/ci/nativeaot-toolchain.sh"
moshiro_nativeaot_settings "$root"
builder_cache="${MOSHIRO_RUNNER_IMAGE_CACHE:-/home/justme/woodpecker/cache/runner-image}"
cached_key="$(cat "$builder_cache/nativeaot-builder.key" 2>/dev/null || true)"
builder_id="$(cat "$builder_cache/nativeaot-builder.id" 2>/dev/null || true)"
if [[ "$cached_key" == "$MOSHIRO_NATIVEAOT_BUILDER_KEY" && "$builder_id" =~ ^[0-9a-f]{64}$ && -f "$builder_cache/nativeaot-builder-$builder_id.oci" ]]; then
local_builder_id="$(buildah images --noheading --no-trunc --format '{{.ID}}' "$builder_image" 2>/dev/null || true)"
if [[ "${local_builder_id#sha256:}" != "$builder_id" ]]; then
run_bounded 10m buildah pull "oci-archive:$builder_cache/nativeaot-builder-$builder_id.oci"
buildah tag "$builder_id" "$builder_image"
fi
echo "using cached NativeAOT toolchain $builder_id"
else
builder_context="$work/nativeaot-builder"
mkdir -p "$builder_context"
tr -d '\r' < "$root/global.json" > "$builder_context/global.json"
run_bounded 30m buildah bud --layers --network host --tls-verify=true --arch amd64 \
--build-arg "AOTANYWHERE_VERSION=$MOSHIRO_AOTANYWHERE_VERSION" \
--build-arg "ZIG_TOOLSET_VERSION=$MOSHIRO_ZIG_TOOLSET_VERSION" \
-f "$root/packaging/container/NativeAot.Builder.Containerfile" \
-t "$builder_image" "$builder_context"
fi
# A stable timestamp lets a retry of the same commit reuse the publish layer.
commit_epoch="$(git -C "$root" show -s --format=%ct "$CI_COMMIT_SHA")"
built_at="$(date -u -d "@$commit_epoch" +%Y-%m-%dT%H:%M:%SZ)"
python3 "$root/ci/stage-node-image.py" "$component" "$node_rid" "$context"
# Zig compiles musl libc by spawning many compiler processes. Buildah's default
# nofile limit (1024) makes Zig fail with ProcessFdQuotaExceeded while linking.
run_bounded 30m buildah bud --layers --network host --tls-verify=true --ulimit nofile=65536:65536 --arch "$arch" \
--build-arg "NATIVEAOT_BUILDER_IMAGE=$builder_image" \
--build-arg "APP_VERSION=$image_version" \
--build-arg "NODE_RID=$node_rid" \
--build-arg "MOSHIRO_TAG=$MOSHIRO_TAG" --build-arg "MOSHIRO_COMMIT=${CI_COMMIT_SHA:0:10}" --build-arg "MOSHIRO_BUILT_AT=$built_at" \
-f "$context/Containerfile" \
-t "$local_image" "$context"
# 构建和测试是并行的:镜像已经好了,推送之前等 verify 和 web-build 都过(任何一个失败就不推)
bash "$root/ci/wait-for-checks.sh"
logged_in=false
for attempt in 1 2 3 4; do
if run_bounded 2m buildah login --tls-verify=true --username "$ZOT_USERNAME" --password-stdin "$registry" <<< "$ZOT_SECRET" >/dev/null; then
logged_in=true
break
fi
if [[ "$attempt" -lt 4 ]]; then
echo "Zot login attempt $attempt failed; retrying in 3 seconds" >&2
sleep 3
fi
done
[[ "$logged_in" == true ]] || { echo "could not authenticate to Zot after 4 attempts" >&2; exit 1; }
for tag in "$image_version-$arch"; do
pushed=false
for attempt in 1 2 3; do
if run_bounded 10m buildah push --tls-verify=true --digestfile "$work/$component.digest" "$local_image" "docker://$repository:$tag"; then
pushed=true
break
fi
if [[ "$attempt" -lt 3 ]]; then
echo "Zot push for $component:$tag attempt $attempt failed; retrying in 5 seconds" >&2
sleep 5
fi
done
[[ "$pushed" == true ]] || { echo "could not push $repository:$tag after 3 attempts" >&2; exit 1; }
echo "pushed development image $repository:$tag"
done
# Reuse the static Agent/enrollment binaries already built above; do not compile again.
# Publish before the multi-arch image offer wakes online native nodes.
if [[ "$component" == agent ]]; then
buildah rm "moshiro-openwrt-extract-$arch" >/dev/null 2>&1 || true
native_container="$(buildah from --arch "$arch" --name "moshiro-openwrt-extract-$arch" "$local_image")"
native_dir="$work/openwrt"
mkdir -p "$native_dir"
native_mount="$(buildah mount "$native_container")"
cp "$native_mount/usr/local/bin/moshiro-agent" "$native_mount/usr/local/bin/fleet-enroll" "$native_dir/"
buildah unmount "$native_container" >/dev/null
buildah rm "$native_container" >/dev/null
bash "$root/ci/publish-openwrt.sh" dev "$node_rid" "$native_dir"
fi
# Agent manifests are signed after both architecture-specific images
# have been pushed. Backend is still a single-architecture development image.
if [[ "$component" == backend && -n "${RELEASE_SIGNING_KEY:-}" ]]; then
bash "$root/ci/publish-release-offer.sh" "$component" "$repository" "$(cat "$work/$component.digest")" "$MOSHIRO_TAG" dev "$work/$component-release"
fi