Wardrobe/.gitea/workflows/ota-build.yml
jif2.zhang 38b5e3190a init: Wardrobe 衣物管理系统 - 项目首页、OTA 升级、Android CI/CD
- 响应式项目首页 (Landing, 含 R2 APK 下载) + 路由 /home 调整
- OTA 升级: tauri-plugin-hotswap (lib.rs/main.rs 拆分, 客户端热替换)
- OTA 服务端 (check/bundle/manifest-web) + build-ota/genkey/upload 脚本
- 密钥/凭据统一存配置中心 (signing + ota + cloudflare 分组)
- Android CI (android-* 分支) + OTA CI (手动触发), 产物上传 R2
- 修复 tsc/构建阻塞问题
2026-09-11 15:46:42 +08:00

112 lines
4.6 KiB
YAML

name: OTA Frontend Build & Sign
# 手动触发 (发版前用): 构建前端 → 打生产 dist 的 OTA 升级包 (minisign 签名) → 上传 R2
# 产物:
# - ota-package/wardrobe-ota.tar.gz (前端替换包)
# - ota-package/latest.json (manifest, 客户端 check 用)
# 存储: Cloudflare R2 store/ota/ (公开桶)
# 依赖配置注册中心 (bh.vps.honor3.com:8008):
# - service-wardrobe / ota : MINISIGN_PRIVATE_KEY (整段 .key 内容, 多行)
# - CommonExternalService / cloudflare : CF_API_TOKEN, CF_R2_PUBLIC_URL
# 注意: 不修改 src-tauri/tauri.conf.json 的 pubkey, 换密钥请手动改 conf 并 commit。
# 部署 server (托管 OTA check 端点 + 首页 dist) 见 README "OTA 升级 与 首页" 章节。
on:
workflow_dispatch:
inputs:
sequence:
description: 'OTA sequence (单调递增整数, 留空用 timestamp)'
required: false
type: string
notes:
description: '发布说明 (写入 latest.json.notes)'
required: false
type: string
default: ''
jobs:
build-ota:
runs-on: ubuntu-latest
permissions:
contents: read
env:
CONFIG_CENTER_URL: '${{ secrets.CONFIG_CENTER_URL }}'
CONFIG_CENTER_TOKEN: '${{ secrets.CONFIG_CENTER_TOKEN }}'
REGISTRY_URL_FALLBACK: 'http://bh.vps.honor3.com:8008'
CFG_PROJECT_SIGNING: 'proj-mtwjnvir-k52zvp'
CFG_PROJECT_R2: 'proj-mts0s5jo-fb3rz7'
steps:
- name: Checkout
uses: https://gitea.com/actions/checkout@v4
- name: Setup Node
uses: https://gitea.com/actions/setup-node@v4
with:
node-version: 22
- name: Install deps
run: npm ci --no-audit --no-fund
- name: Build frontend
# 复用 vite build 产物 (同时是 OTA bundle 内容)
run: npm run build
- name: Install minisign
run: sudo apt-get update && sudo apt-get install -y minisign
- name: Fetch config center (minisign key + R2)
run: |
set -euo pipefail
export CONFIG_CENTER_URL="${CONFIG_CENTER_URL:-$REGISTRY_URL_FALLBACK}"
python3 - <<'PYEOF'
import json, os, urllib.request
url = os.environ["CONFIG_CENTER_URL"]
token = os.environ.get("CONFIG_CENTER_TOKEN", "") or ""
wanted = {}
req = urllib.request.Request(f"{url}/api/projects/{os.environ['CFG_PROJECT_SIGNING']}/configs")
if token:
req.add_header("Authorization", "Bearer " + token)
with urllib.request.urlopen(req, timeout=20) as r:
data = json.load(r).get("data", [])
for cfg in data:
if cfg.get("key") in ("MINISIGN_PRIVATE_KEY",):
wanted[cfg["key"]] = cfg.get("value", "")
req = urllib.request.Request(f"{url}/api/projects/{os.environ['CFG_PROJECT_R2']}/configs")
if token:
req.add_header("Authorization", "Bearer " + token)
with urllib.request.urlopen(req, timeout=20) as r:
data = json.load(r).get("data", [])
for cfg in data:
if cfg.get("key") in ("CF_API_TOKEN", "CF_R2_PUBLIC_URL"):
wanted[cfg["key"]] = cfg.get("value", "")
# GITHUB_ENV 多行值需用 heredoc 语法; 单行值直接 NAME=value
with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as f:
for k, v in wanted.items():
if "\n" in v or v.startswith("untrusted comment"):
f.write(f"{k}<<EOF\n{v}\nEOF\n")
else:
f.write(f"{k}={v}\n")
missing = [k for k in ("MINISIGN_PRIVATE_KEY", "CF_API_TOKEN") if k not in wanted]
if missing:
raise SystemExit("配置中心缺少: " + ", ".join(missing))
PYEOF
- name: Build & sign OTA package
# MINISIGN_PRIVATE_KEY 由上一步写入 GITHUB_ENV, 已是本步的真实环境变量,
# build-ota.mjs 直接读 process.env.MINISIGN_PRIVATE_KEY, 无需再映射
env:
OTA_NOTES: ${{ inputs.notes }}
run: |
set -euo pipefail
ARGS="--out ota-package"
if [ -n "${{ inputs.sequence }}" ]; then ARGS="${ARGS} --sequence ${{ inputs.sequence }}"; fi
if [ -n "${OTA_NOTES}" ]; then ARGS="${ARGS} --notes ${OTA_NOTES}"; fi
node scripts/build-ota.mjs ${ARGS}
echo "===== latest.json ====="
cat ota-package/latest.json
echo "===== bundle sha256 ====="
sha256sum ota-package/wardrobe-ota.tar.gz
- name: Upload OTA package to Cloudflare R2
run: |
bash scripts/upload-ota-r2.sh ota-package