Files
jiu/scripts/ci/lib-forgejo.sh
T
wangjia aa7099ba94 feat(devops): 发版拆分为 client/site/server 三条独立流水线
将单一 CI/CD 流水线拆成三条互不影响的发布流水线,各有 tag 前缀、独立版本
序列与独立 CHANGELOG:

- client(client-v*):Flutter 全平台 + version.yaml 自更新清单
- site(site-v*):web/ Eleventy 营销站,不含 web 版 app
- server(server-v*):backend Go 服务 + 共享基建 nginx/systemd

新增 3 个 workflow(deploy-client/site/server.yml)替换 deploy.yml;CI 脚本
按 part 拆分为 compile-/release-/deploy-{client,site,server}.sh,抽出公共函数
lib-forgejo.sh;compile-{macos,android,ios,windows}.sh 改去 client-v 前缀;
manual.yml 按前缀路由回滚。

跨流水线解耦:version.yaml 归 client,后端每请求实时读取(不重启、不触发
server 流水线);官网下载页的版本徽章/下载链接/更新日志时间线运行时经
/api/v1/public/release 动态拉取(API 不可达回退构建时静态内容)。为此一次性
扩展后端 changelog 字段(version.go/public.go)与 download.njk 动态渲染。

CHANGELOG.md 重命名为 CHANGELOG-client.md,新增 CHANGELOG-site/server.md;
重写 /release 命令为 /release <part> [version];同步更新 CLAUDE.md 与部署文档。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 08:21:28 +08:00

122 lines
4.4 KiB
Bash

#!/usr/bin/env bash
# lib-forgejo.sh — shared CI helpers for the three release pipelines
# (client / site / server). `source` this from release-*.sh and deploy-*.sh.
#
# Provides:
# ver_from_tag <tag> -> strips client-v|site-v|server-v|v prefix
# extract_release_notes <file> -> prints the latest "## [x]" section body
# create_release <tag> <body> -> POSTs a Forgejo Release, sets RELEASE_ID
# upload_asset <file> -> uploads one asset (needs RELEASE_ID)
# download_release_assets <tag> -> downloads all assets of a release into dist/
# setup_ssh / teardown_ssh -> prepare/clean the EC2 deploy key (sets SSH/SCP)
#
# Requires env (where relevant): FORGEJO_URL, FORGEJO_TOKEN, GITEA_REPOSITORY,
# EC2_SSH_KEY, EC2_HOST, EC2_USER.
# Strip the part-specific tag prefix, leaving a bare semver (1.2.3).
ver_from_tag() {
local tag="$1"
tag="${tag#client-v}"
tag="${tag#site-v}"
tag="${tag#server-v}"
tag="${tag#v}"
printf '%s' "$tag"
}
# Print the body (lines) of the most recent "## [ver] - date" section.
extract_release_notes() {
python3 - "$1" <<'PYEOF'
import re, sys
fn = sys.argv[1]
try:
with open(fn, encoding='utf-8') as f:
content = f.read()
parts = re.split(r'\n## ', '\n' + content)
if len(parts) > 1:
lines = parts[1].strip().split('\n')
notes = []
for line in lines[1:]:
s = line.strip()
if s.startswith('## '):
break
if s:
notes.append(s)
print('\n'.join(notes) if notes else lines[0])
else:
print('')
except Exception:
print('')
PYEOF
}
# create_release <tag> <body_json_string> — sets global RELEASE_ID on success.
create_release() {
local tag="$1" body="$2" http_code resp
echo "==> release: creating Forgejo Release ${tag}"
http_code=$(curl -k -w "%{http_code}" -o /tmp/release_resp.json \
-X POST "${FORGEJO_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases" \
-H "Authorization: token ${FORGEJO_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${tag}\",\"name\":\"Release ${tag}\",\"body\":${body},\"draft\":false,\"prerelease\":false}")
resp=$(cat /tmp/release_resp.json)
echo "==> release: API response (HTTP ${http_code}): ${resp}"
if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
echo "==> release: FAILED — HTTP ${http_code}" >&2
return 1
fi
RELEASE_ID=$(python3 -c "import sys,json; print(json.load(sys.stdin)['id'])" <<< "${resp}")
echo "==> release: release_id=${RELEASE_ID}"
}
# upload_asset <file> — requires RELEASE_ID.
upload_asset() {
local file="$1" code
code=$(curl -k -w "%{http_code}" -o /tmp/upload_resp.json \
-X POST "${FORGEJO_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/${RELEASE_ID}/assets" \
-H "Authorization: token ${FORGEJO_TOKEN}" \
-F "attachment=@${file}")
echo "==> release: uploaded ${file} (HTTP ${code}): $(cat /tmp/upload_resp.json)"
if [ "$code" -lt 200 ] || [ "$code" -ge 300 ]; then return 1; fi
}
# download_release_assets <tag> — pull every asset of the release into dist/.
download_release_assets() {
local tag="$1"
echo "==> deploy: downloading assets for ${tag} from Forgejo"
mkdir -p dist
curl -kf -H "Authorization: token ${FORGEJO_TOKEN}" \
"${FORGEJO_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/tags/${tag}" \
-o /tmp/release-info.json
python3 - <<'PYEOF'
import json, urllib.request, ssl, os
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
rel = json.load(open('/tmp/release-info.json'))
token = os.environ['FORGEJO_TOKEN']
url = os.environ['FORGEJO_URL']
repo = os.environ['GITEA_REPOSITORY']
rid = rel['id']
for a in rel.get('assets', []):
u = f"{url}/api/v1/repos/{repo}/releases/{rid}/assets/{a['id']}"
req = urllib.request.Request(u, headers={'Authorization': f'token {token}'})
with urllib.request.urlopen(req, context=ctx) as r, open(f"dist/{a['name']}", 'wb') as o:
o.write(r.read())
print('Downloaded:', a['name'])
PYEOF
}
# setup_ssh — write the EC2 deploy key and export SSH / SCP commands.
setup_ssh() {
mkdir -p ~/.ssh
printf '%s' "${EC2_SSH_KEY}" > ~/.ssh/ec2_deploy.pem
chmod 600 ~/.ssh/ec2_deploy.pem
ssh-keyscan -H "${EC2_HOST}" >> ~/.ssh/known_hosts 2>/dev/null
SSH="ssh -i ~/.ssh/ec2_deploy.pem -o StrictHostKeyChecking=no"
SCP="scp -O -i ~/.ssh/ec2_deploy.pem"
}
teardown_ssh() {
rm -f ~/.ssh/ec2_deploy.pem
}