Files
MoShiro/ci/release-linux.sh
T
justme cd9e677331
ci/woodpecker/push/main-amd64-build Pipeline is running
ci/woodpecker/tag/linux-release Pipeline is running
ci/woodpecker/tag/windows-release Pipeline was successful
ci/woodpecker/tag/gitea-release unknown status
fix(ci): build the web apps once before the two backend architectures; release 0.3.1 [CI]
2026-10-08 16:05:38 +08:00

139 lines
6.0 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Build a glibc NativeAOT Backend OCI image with statically linked SQLite; publishing is optional.
# glibc and a dynamic executable are required: the musl AotAnywhere output is fully static and cannot dlopen libssl, so the signing code fails at startup.
set -euo pipefail
arch="${1:?usage: release-linux.sh <amd64|arm64>}"
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
tag="${CI_COMMIT_TAG:-}"
if [[ -n "$tag" ]]; then
[[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || {
echo "release must run from a vMAJOR.MINOR.PATCH tag" >&2
exit 2
}
version="${tag#v}"
else
[[ "${CI_PIPELINE_EVENT:-}" == push && "${CI_COMMIT_BRANCH:-}" == main && "${CI_COMMIT_MESSAGE:-}" == *"[CI]"* ]] || {
echo "untagged backend builds require a main push with [CI] in the commit message" >&2
exit 2
}
version=main
fi
image_publish="${IMAGE_PUBLISH:-true}"
[[ "$image_publish" == true || "$image_publish" == false ]] || {
echo "IMAGE_PUBLISH must be true or false" >&2; exit 2;
}
if [[ "$image_publish" == true ]]; then
[[ -n "${ZOT_USERNAME:-}" && -n "${ZOT_SECRET:-}" ]] || {
echo "Zot credentials are required" >&2; exit 2;
}
fi
case "$arch" in
amd64) rid=linux-x64 ;;
arm64) rid=linux-arm64 ;;
*) echo "unsupported architecture: $arch" >&2; exit 2 ;;
esac
project_version="$(sed -n 's#.*<Version>\(.*\)</Version>.*#\1#p' "$root/backend/Directory.Build.props" | head -1)"
if [[ -n "$tag" && "$version" != "$project_version" ]]; then
echo "tag version $version does not match project version $project_version" >&2
exit 1
fi
registry="${ZOT_REGISTRY:-registry.oeo.one}"
image="${ZOT_IMAGE_REPOSITORY:-$registry/moshiro/moshiro-backend}"
output="$root/.release/linux-$arch/backend"
build_root="$root/.release/build/linux-$arch-backend"
container_context="$root/.release/container/linux-$arch"
mkdir -p "$output" "$build_root" "$container_context/app" "$container_context/data"
# 构建信息:tag 发布用 tag,main 构建用 main-<提交>
export MOSHIRO_TAG="${MOSHIRO_TAG:-${tag:-main-${CI_COMMIT_SHA:0:10}}}"
export MOSHIRO_COMMIT="${MOSHIRO_COMMIT:-${CI_COMMIT_SHA:0:10}}"
export MOSHIRO_BUILT_AT="${MOSHIRO_BUILT_AT:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}"
# 前端先构建:Backend.Api 在 web/*/dist 存在时把它们内嵌进程序(展示端 /,管理端 /admin/),这样 passkey 能绑定在面板自己的域名上
# 发版流水线里有单独的 web-build 步骤先构建好了(并行的两个架构不能同时 npm ci);main 的 [CI] 构建只有一个 Backend 步骤,这里自己构建
if [[ ! -f "$root/web/display/dist/index.html" || ! -f "$root/web/admin/dist/index.html" ]]; then
if [[ -z "${NPM_CONFIG_CACHE:-}" && -d /home/justme/.npm ]]; then export NPM_CONFIG_CACHE=/home/justme/.npm; fi
(cd "$root" && npm ci --prefix web --prefer-offline && npm run build --prefix web)
fi
[[ -f "$root/web/display/dist/index.html" && -f "$root/web/admin/dist/index.html" ]] || { echo "the web build did not produce both apps" >&2; exit 1; }
(cd "$root/backend" && dotnet publish src/MoShiro.Backend \
-m:3 -c Release -r "$rid" --self-contained true \
-p:DebugType=none -p:DebugSymbols=false \
-p:PublishAot=true -p:UseAotAnywhere=true -p:SqliteStaticLink=true -p:OpenApiGenerateDocuments=false \
--artifacts-path "$build_root" -o "$output")
[[ -x "$output/moshiro-backend" ]] || { echo "NativeAOT Backend executable is missing" >&2; exit 1; }
[[ "$(wc -c < "$output/moshiro-backend")" -gt 2000000 ]] || { echo "Backend output is an apphost, not a NativeAOT executable" >&2; exit 1; }
install -m 0755 "$output/moshiro-backend" "$container_context/app/moshiro-backend"
: > "$container_context/data/.keep"
# Woodpecker's local backend isolates HOME per workflow. Keep Buildah's image
# store outside that temporary directory so the runtime base image is reusable
# across releases. Separate stores avoid concurrent amd64/arm64 writers.
buildah_home="${BUILDAH_HOME:-/home/justme/woodpecker/buildah-home/$arch}"
mkdir -p "$buildah_home"
export HOME="$buildah_home"
local_image="localhost/moshiro-backend:$version-$arch"
buildah bud --tls-verify=true --arch "$arch" \
--build-arg "APP_VERSION=$version" \
-f "$root/packaging/container/Backend.Containerfile" \
-t "$local_image" "$container_context"
if [[ "$image_publish" == false ]]; then
echo "built local Backend image for $arch; not publishing it"
exit 0
fi
export REGISTRY_AUTH_FILE="$root/.release/auth/zot-linux-$arch.json"
mkdir -p "$(dirname "$REGISTRY_AUTH_FILE")"
logged_in=false
for attempt in 1 2 3 4; do
if 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; }
push_tag() {
local target_tag="$1" pushed=false
for attempt in 1 2 3; do
if buildah push --tls-verify=true --digestfile "$output/image.digest" "$local_image" "docker://$image:$target_tag"; then
pushed=true
break
fi
if [[ "$attempt" -lt 3 ]]; then
echo "Zot push attempt $attempt failed; retrying in 5 seconds" >&2
sleep 5
fi
done
[[ "$pushed" == true ]] || { echo "could not push image to Zot after 3 attempts" >&2; return 1; }
echo "pushed Zot image $image:$target_tag"
}
if [[ -n "${IMAGE_TAGS:-}" ]]; then
IFS=',' read -r -a image_tags <<< "$IMAGE_TAGS"
((${#image_tags[@]} > 0)) || { echo "IMAGE_TAGS must contain at least one tag" >&2; exit 2; }
for image_tag in "${image_tags[@]}"; do
[[ "$image_tag" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$ ]] || {
echo "invalid image tag: $image_tag" >&2
exit 2
}
push_tag "$image_tag"
done
else
push_tag "$version-$arch"
if [[ -z "$tag" ]]; then
push_tag "$version"
fi
fi
if [[ -z "$tag" ]]; then
bash "$root/ci/publish-release-offer.sh" backend "$image" "$(cat "$output/image.digest")" "$MOSHIRO_TAG" dev "$output/release"
fi