Files
MoShiro/ci/publish-gitea-release.sh
T
justme a6f9759238
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/verify Pipeline was successful
ci/woodpecker/push/dev-image Pipeline failed
ci: build musl only for arm64 and reuse toolchain cache [CI]
2026-10-21 12:41:58 +08:00

159 lines
7.8 KiB
Bash

#!/usr/bin/env bash
# Create/update the Gitea Release after all platform package workflows succeed.
set -euo pipefail
tag="${CI_COMMIT_TAG:-}"
[[ "$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
}
[[ -n "${GITEA_TOKEN:-}" ]] || { echo "GITEA_TOKEN is required" >&2; exit 2; }
version="${tag#v}"
gitea_base="${GITEA_BASE:-http://192.168.2.162:3100}"
gitea_base="${gitea_base%/}"
gitea_host="${gitea_base#https://}"
gitea_host="${gitea_host#http://}"
gitea_host="${gitea_host%%/*}"
gitea_host="${gitea_host%%:*}"
api="$gitea_base/api/v1/repos/justme/MoShiro"
tmp="$(mktemp -d)"
netrc="$tmp/netrc"
release_json="$tmp/release.json"
assets_json="$tmp/assets.json"
chmod 700 "$tmp"
printf 'machine %s login justme password %s\n' "$gitea_host" "$GITEA_TOKEN" > "$netrc"
chmod 600 "$netrc"
trap 'rm -rf "$tmp"' EXIT
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
install_sh="$(sha256sum "$root_dir/packaging/install/install.sh" | cut -d" " -f1)"
install_ps1="$(sha256sum "$root_dir/packaging/install/install.ps1" | cut -d" " -f1)"
release_body="Install script SHA-256 (compare with the panel before you run an install command): install.sh $install_sh, install.ps1 $install_ps1. Agent builds: Linux glibc (x64, arm64) and musl (arm64), macOS (x64, arm64), and Windows (x64, arm64). Linux Agent OCI image: registry.oeo.one/moshiro/moshiro-agent:$version (linux/amd64 and linux/arm64). Backend OCI image: registry.oeo.one/moshiro/moshiro-backend:$version (linux/amd64 and linux/arm64). Compose files for Agent and Backend: moshiro-compose-<role>.yaml (images use :latest, or pin MOSHIRO_VERSION in .env). macOS binaries are not Developer ID signed or notarized."
payload="$(printf '{"tag_name":"%s","target_commitish":"%s","name":"MoShiro %s","body":"%s","draft":false,"prerelease":false}' \
"$tag" "${CI_COMMIT_SHA:-}" "$tag" "$release_body")"
release_status="$(curl --silent --show-error --retry 3 --netrc-file "$netrc" \
--output "$release_json" --write-out '%{http_code}' "$api/releases/tags/$tag")"
release_created=false
if [[ "$release_status" == 404 ]]; then
release_status="$(curl --silent --show-error --retry 3 --netrc-file "$netrc" \
--header 'Content-Type: application/json' --data "$payload" \
--output "$release_json" --write-out '%{http_code}' "$api/releases")"
release_created=true
fi
[[ "$release_status" =~ ^2 ]] || {
echo "Gitea Release lookup/create failed (HTTP $release_status)" >&2
cat "$release_json" >&2
exit 1
}
release_id="$(grep -o '"id":[[:space:]]*[0-9][0-9]*' "$release_json" | head -1 | tr -cd '0-9')"
[[ -n "$release_id" ]] || { echo "Could not read Gitea Release ID" >&2; exit 1; }
if [[ "$release_created" == false ]]; then
update_response="$tmp/release-update.json"
update_status="$(curl --silent --show-error --retry 3 --netrc-file "$netrc" \
--request PATCH --header 'Content-Type: application/json' --data "$payload" \
--output "$update_response" --write-out '%{http_code}' "$api/releases/$release_id")"
[[ "$update_status" =~ ^2 ]] || {
echo "Gitea Release update failed (HTTP $update_status)" >&2
cat "$update_response" >&2
exit 1
}
fi
curl --fail-with-body --silent --show-error --retry 3 --netrc-file "$netrc" \
"$api/releases/$release_id/assets" -o "$assets_json"
assets=(
"moshiro-agent-v$version-linux-amd64.tar.gz"
"moshiro-agent-v$version-linux-arm64.tar.gz"
"moshiro-agent-v$version-linux-musl-arm64.tar.gz"
"moshiro-agent-v$version-macos-x64.tar.gz"
"moshiro-agent-v$version-macos-arm64.tar.gz"
"moshiro-agent-v$version-windows-amd64.zip"
"moshiro-agent-v$version-windows-arm64.zip"
)
upload_release_asset() {
local name="$1" file="$tmp/$1" upload_response="$tmp/$1.upload.json" status
curl --fail-with-body --silent --show-error --retry 3 --netrc-file "$netrc" \
"$gitea_base/api/packages/justme/generic/moshiro-node/$version/$name" -o "$file"
status="$(curl --silent --show-error --retry 3 --netrc-file "$netrc" \
--request POST --header 'Content-Type: application/octet-stream' --data-binary "@$file" \
--output "$upload_response" --write-out '%{http_code}' \
"$api/releases/$release_id/assets?name=$name")"
case "$status" in
2*) echo "attached $name" ;;
*) echo "Gitea Release asset upload failed for $name (HTTP $status)" >&2; cat "$upload_response" >&2; return 1 ;;
esac
}
pids=()
for archive in "${assets[@]}"; do
for name in "$archive" "$archive.sha256"; do
if grep -Eq "\"name\"[[:space:]]*:[[:space:]]*\"$name\"" "$assets_json"; then
echo "release asset $name already exists; keeping the existing asset"
continue
fi
upload_release_asset "$name" &
pids+=("$!")
if ((${#pids[@]} >= 8)); then
wait "${pids[0]}"
pids=("${pids[@]:1}")
fi
done
done
for pid in "${pids[@]}"; do
wait "$pid"
done
# 所有压缩包的校验和汇成一份 SHA256SUMS,用发布私钥签名(base64 的 DER ECDSA,和签名的发布清单是同一把密钥、同一种格式):
# 不走容器的安装(直接下载压缩包)先验这份签名再核对压缩包的哈希,发布页被改也骗不到;没有配置签名密钥(例如 fork)就跳过。
upload_local_asset() {
local name="$1" file="$2" upload_response="$tmp/$1.upload.json" status
if grep -Eq "\"name\"[[:space:]]*:[[:space:]]*\"$name\"" "$assets_json"; then
echo "release asset $name already exists; keeping the existing asset"
return 0
fi
status="$(curl --silent --show-error --retry 3 --netrc-file "$netrc" \
--request POST --header 'Content-Type: application/octet-stream' --data-binary "@$file" \
--output "$upload_response" --write-out '%{http_code}' "$api/releases/$release_id/assets?name=$name")"
[[ "$status" == 2* ]] || { echo "Gitea Release asset upload failed for $name (HTTP $status)" >&2; cat "$upload_response" >&2; return 1; }
echo "attached $name"
}
# compose 文件(Agent、Backend 各一份)和节点的 Podman Quadlet 单元(Agent 各一份),都是静态的(镜像用 :latest):作为发布资产,
# 第一次部署 Backend 时还没有面板,就从这里下载;它们也记进 SHA256SUMS(有签名密钥时一起签名)。
for role in agent backend; do
cp "$root_dir/packaging/compose/$role/compose.yaml" "$tmp/moshiro-compose-$role.yaml"
upload_local_asset "moshiro-compose-$role.yaml" "$tmp/moshiro-compose-$role.yaml"
done
for role in agent; do
cp "$root_dir/packaging/quadlet/moshiro-$role.container" "$tmp/moshiro-quadlet-$role.container"
upload_local_asset "moshiro-quadlet-$role.container" "$tmp/moshiro-quadlet-$role.container"
done
if [[ -n "${RELEASE_SIGNING_KEY:-}" ]]; then
sums="$tmp/SHA256SUMS"
: > "$sums"
for archive in "${assets[@]}"; do
curl --fail --silent --show-error --retry 3 --netrc-file "$netrc" \
"$gitea_base/api/packages/justme/generic/moshiro-node/$version/$archive.sha256" -o "$tmp/$archive.sha256"
cat "$tmp/$archive.sha256" >> "$sums"
done
[[ "$(wc -l < "$sums")" -eq "${#assets[@]}" ]] || { echo "SHA256SUMS does not have one line per archive" >&2; exit 1; }
for file in moshiro-compose-agent.yaml moshiro-compose-backend.yaml moshiro-quadlet-agent.container; do
printf '%s %s\n' "$(sha256sum "$tmp/$file" | cut -d' ' -f1)" "$file" >> "$sums"
done
signing_key="$(mktemp)"
chmod 600 "$signing_key"
printf '%s\n' "$RELEASE_SIGNING_KEY" > "$signing_key"
openssl dgst -sha256 -sign "$signing_key" -out "$tmp/SHA256SUMS.sig.bin" "$sums"
rm -f "$signing_key"
base64 -w0 "$tmp/SHA256SUMS.sig.bin" > "$tmp/SHA256SUMS.sig" 2>/dev/null || base64 "$tmp/SHA256SUMS.sig.bin" | tr -d '\n' > "$tmp/SHA256SUMS.sig"
upload_local_asset SHA256SUMS "$sums"
upload_local_asset SHA256SUMS.sig "$tmp/SHA256SUMS.sig"
else
echo "release signing is not configured; SHA256SUMS is not signed"
fi
echo "published Gitea Release $tag: https://git.oeo.one/justme/MoShiro/releases/tag/$tag"