ci: 发布脚本自动创建 Release 并按 OTA 命名上传附件

应用内 OTA 只认 Gitea Release 附件(update.rs 查 /releases),此前脚本
只上传 Generic 包,Release 不存在时更新检查永远失败。现在:

- 自动查询/创建与版本同名 tag 的 Release,附件命名规范为
  mqttx-v<ver>-<triple>-mqttx[.exe](不符约定时重命名后上传),
  附带 .sha256 侧车,供 OTA sha256 校验
- 版本号统一剥离 v 前缀(git describe 与 Cargo.toml 两种来源一致)
- 平台三元组按运行环境推断(与 update.rs::platform_triple 一致),
  可用 GITEA_TRIPLE 覆盖
- owner/repo 均支持从 git remote 推断,可用 GITEA_REPO 参数覆盖
- ps1 附件上传走 .NET HttpClient multipart(PS 5.1 无 -Form)
This commit is contained in:
2026-09-29 10:33:42 +08:00
parent 5dc1a8cc0b
commit 6ff05f7216
2 changed files with 177 additions and 14 deletions
+85 -6
View File
@@ -1,5 +1,8 @@
#!/usr/bin/env bash
# 发布构建产物到 Gitea Package Registry(Generic 包)。
# 发布构建产物到 Gitea:
# 1) Generic Package Registry(API 包,GET /api/packages/...)
# 2) Release 附件 —— OTA 更新源只认 Release 附件
# (命名约定 mqttx-v<ver>-<triple>-mqttx[.exe],含 .sha256 侧车)
#
# 用法:
# GITEA_URL=https://gitea.example.com GITEA_TOKEN=xxx \
@@ -7,8 +10,9 @@
#
# 环境变量:
# GITEA_URL 实例地址,如 https://gitea.example.com(必填)
# GITEA_TOKEN 访问令牌,需 package 写权限(必填)
# GITEA_TOKEN 访问令牌,需 package + repo 写权限(必填)
# GITEA_OWNER 软件包所属用户/组织(默认:由 git remote 推断,失败则报错)
# GITEA_REPO 仓库名(默认:由 git remote 推断,失败为 mqttx)
# GITEA_PKG 软件包名(默认 mqttx)
#
# 版本推断顺序:参数 > git describe --tags > gpui-app/Cargo.toml。
@@ -23,8 +27,8 @@ GITEA_URL="${GITEA_URL%/}"
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PKG="${GITEA_PKG:-mqttx}"
# ── owner:优先环境变量,其次从 git remote 推断 ──────────────────────────────
owner_of_remote() {
# ── owner/repo:优先环境变量,其次从 git remote 推断 ─────────────────────────
remote_owner_repo() {
local url path
url="$(git -C "$ROOT" remote get-url origin 2>/dev/null)" || return 1
case "$url" in
@@ -33,10 +37,12 @@ owner_of_remote() {
*) return 1 ;;
esac
path="${path%.git}"
printf '%s\n' "${path%%/*}"
printf '%s\n' "${path%%/*}" "${path#*/}"
}
OWNER="${GITEA_OWNER:-$(owner_of_remote || true)}"
OWNER="${GITEA_OWNER:-$(remote_owner_repo | head -1 || true)}"
REPO="${GITEA_REPO:-$(remote_owner_repo | tail -1 || true)}"
[ -n "${OWNER:-}" ] || die "无法推断 GITEA_OWNER,请显式设置"
[ -n "${REPO:-}" ] || REPO="$PKG"
# ── 版本:参数 > git describe > Cargo.toml ───────────────────────────────────
FILES=()
@@ -66,6 +72,7 @@ fi
echo "→ Gitea: $GITEA_URL"
echo "→ 软件包: $OWNER/$PKG@$VERSION"
echo "→ 仓库: $OWNER/$REPO"
echo "→ 产物: ${FILES[*]}"
sha256() {
@@ -73,6 +80,19 @@ sha256() {
else shasum -a 256 "$1" | cut -d' ' -f1; fi
}
# OTA 产物命名约定的平台三元组(与 update.rs::platform_triple 一致)
triple_of() {
case "$(uname -s)/$(uname -m)" in
MINGW*/*x86_64*|MSYS*/*x86_64*|CYGWIN*/*x86_64*) echo "x86_64-pc-windows-msvc" ;;
Linux/x86_64|Linux/amd64) echo "x86_64-unknown-linux-gnu" ;;
Darwin/x86_64) echo "x86_64-apple-darwin" ;;
Darwin/arm64) echo "aarch64-apple-darwin" ;;
Linux/aarch64|Linux/arm64) echo "aarch64-unknown-linux-gnu" ;;
*) echo "" ;;
esac
}
TRIPLE="${GITEA_TRIPLE:-$(triple_of)}"
for f in "${FILES[@]}"; do
[ -f "$f" ] || die "文件不存在: $f"
name="$(basename "$f")"
@@ -111,3 +131,62 @@ for f in "${FILES[@]}"; do
done
echo "完成。软件包页面: ${GITEA_URL}/${OWNER}?tab=packages"
# ── Release 附件:OTA 更新源(update.rs 只查 /releases)──────────────────────
API="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}"
STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"' EXIT
# 规范化版本号:tag 推断出的 "v1.0.2" 与 Cargo 的 "1.0.2" 统一为无 v 数字段
VER_NUM="${VERSION#v}"
[ -n "$TRIPLE" ] || TRIPLE="unknown"
# 取(或创建)与版本同名 tag 的 Release;找不到时按需创建
code="$(curl -sS -o "$STAGE/rel.json" -w '%{http_code}' \
-H "Authorization: token ${GITEA_TOKEN}" \
"${API}/releases/tags/v${VER_NUM}")"
if [ "$code" = "404" ]; then
echo "→ Release v${VER_NUM} 不存在,创建中…"
code="$(curl -sS -o "$STAGE/rel.json" -w '%{http_code}' \
-X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"v${VER_NUM}\",\"name\":\"v${VER_NUM}\",\"draft\":false,\"prerelease\":false}" \
"${API}/releases")"
if [ "$code" != "201" ]; then
echo " ! Release 创建失败 (HTTP $code),跳过附件上传" >&2
cat "$STAGE/rel.json" >&2 || true
exit 0
fi
elif [ "$code" != "200" ]; then
echo " ! 查询 Release 失败 (HTTP $code),跳过附件上传" >&2
exit 0
fi
# 顶层 "id" 是首个出现的 id 字段
RELEASE_ID="$(sed -n 's/.*"id":[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$STAGE/rel.json" | head -1)"
[ -n "$RELEASE_ID" ] || { echo " ! 无法解析 Release id,跳过附件上传" >&2; exit 0; }
echo "→ Release v${VER_NUM} (id=$RELEASE_ID),上传附件…"
for f in "${FILES[@]}"; do
name="$(basename "$f")"
# 已符合 OTA 命名约定的原样使用;否则按约定规范重命名后上传
case "$name" in
mqttx-v*-mqttx|mqttx-v*-mqttx.exe) asset="$name" ;;
*.exe) asset="mqttx-v${VER_NUM}-${TRIPLE}-mqttx.exe" ;;
*) asset="mqttx-v${VER_NUM}-${TRIPLE}-mqttx" ;;
esac
cp -f "$f" "$STAGE/$asset"
printf '%s %s\n' "$(sha256 "$f")" "$asset" > "$STAGE/$asset.sha256"
for a in "$asset" "$asset.sha256"; do
code="$(curl -sS -o "$STAGE/up.json" -w '%{http_code}' \
-X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-F "attachment=@${STAGE}/${a}" \
"${API}/releases/${RELEASE_ID}/assets?name=${a}")"
case "$code" in
201) echo " ✓ Release 附件 $a" ;;
*) echo " ! Release 附件 $a 上传失败 (HTTP $code)" >&2; cat "$STAGE/up.json" >&2 || true ;;
esac
done
done
echo "完成。Release 页面: ${GITEA_URL}/${OWNER}/${REPO}/releases/tag/v${VER_NUM}"