feat(deploy): SemVer 版本管理 + 自动化发版流水线

- deploy.yml: tag 触发(v*.*.*),四阶段调脚本(compile/test/release/deploy)
- manual.yml: workflow_dispatch 支持手动回滚,checkout 指定 tag
- scripts/ci/compile.sh: 构建 Go 二进制 + Flutter Web,打包 dist/
- scripts/ci/test.sh: go test + flutter analyze
- scripts/ci/release.sh: 解析 CHANGELOG.md → 更新 version.yaml → 创建 Forgejo Release
- scripts/ci/deploy.sh: 从 Release 下载产物(自动/手动均可)→ 部署到 EC2
- CHANGELOG.md: Keep a Changelog 格式,初始 v1.0.0 条目
- backend: GetRelease 改读 version.yaml,移除 ENV 变量依赖
- backend/config/version.yaml: 重置为 v1.0.0
- web/download.html: 动态拉取 /api/v1/public/release 展示版本号和更新内容

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-05-24 22:41:08 +08:00
parent d48f9f4bd0
commit 53f56ce086
10 changed files with 398 additions and 109 deletions
+16 -83
View File
@@ -2,7 +2,8 @@ name: Deploy
on:
push:
branches: [main]
tags:
- 'v[0-9]*.[0-9]*.[0-9]*'
jobs:
deploy:
@@ -11,97 +12,29 @@ jobs:
GOPROXY: https://goproxy.cn,direct
PUB_HOSTED_URL: https://pub.flutter-io.cn
FLUTTER_STORAGE_BASE_URL: https://storage.flutter-io.cn
steps:
- uses: actions/checkout@v4
- name: Go tests
working-directory: backend
run: go test ./...
- name: Compile
run: sh scripts/ci/compile.sh "${{ gitea.ref_name }}"
- name: Flutter tests
working-directory: client
run: flutter test
- name: Test
run: sh scripts/ci/test.sh
- name: Build backend (linux/amd64)
working-directory: backend
run: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o jiu-server .
- name: Build Flutter Web
working-directory: client
run: flutter build web --release --base-href=/app/ --dart-define=BASE_URL=https://jiu.51yanmei.com --dart-define=PUBLIC_URL=https://jiu.51yanmei.com
- name: Package & Create Forgejo Release
- name: Release
env:
FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }}
FORGEJO_URL: ${{ secrets.FORGEJO_URL }}
run: |
tar -czf web.tar.gz -C client/build web
tar -czf configs.tar.gz \
deploy/nginx-jiu.conf \
deploy/jiu.service \
deploy/production.env.template \
deploy/setup-ec2.sh \
deploy/docker-compose.yml \
deploy/docker-compose.jiu.yml
VERSION="v$(date +%Y%m%d.%H%M)"
COMMIT="${{ gitea.sha }}"
COMMIT_MSG=$(git log -1 --pretty="%s" HEAD)
RECENT_LOGS=$(git log -5 --pretty="- %s (%h)" HEAD)
BODY=$(printf '## 构建信息\n- **版本**: %s\n- **Commit**: %s\n- **时间**: %s\n\n## 最近提交\n%s' \
"${VERSION}" "${COMMIT}" "$(date '+%Y-%m-%d %H:%M:%S %Z')" "${RECENT_LOGS}")
GITEA_REPOSITORY: ${{ gitea.repository }}
run: sh scripts/ci/release.sh "${{ gitea.ref_name }}"
RESP=$(curl -s -X POST "${FORGEJO_URL}/api/v1/repos/${{ gitea.repository }}/releases" \
-H "Authorization: token ${FORGEJO_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${VERSION}\",\"name\":\"Release ${VERSION}\",\"body\":$(echo "$BODY" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'),\"draft\":false,\"prerelease\":false}")
echo "API response: ${RESP}"
RELEASE_ID=$(echo "${RESP}" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
curl -sf -X POST "${FORGEJO_URL}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets" \
-H "Authorization: token ${FORGEJO_TOKEN}" -F "attachment=@backend/jiu-server"
curl -sf -X POST "${FORGEJO_URL}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets" \
-H "Authorization: token ${FORGEJO_TOKEN}" -F "attachment=@web.tar.gz"
curl -sf -X POST "${FORGEJO_URL}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets" \
-H "Authorization: token ${FORGEJO_TOKEN}" -F "attachment=@configs.tar.gz"
echo "Release ${VERSION} created"
- name: Setup SSH
run: |
mkdir -p ~/.ssh
printf '%s' "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/ec2_deploy.pem
chmod 600 ~/.ssh/ec2_deploy.pem
ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts
- name: Deploy to EC2
- name: Deploy
env:
FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }}
FORGEJO_URL: ${{ secrets.FORGEJO_URL }}
GITEA_REPOSITORY: ${{ gitea.repository }}
EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }}
EC2_HOST: ${{ secrets.EC2_HOST }}
EC2_USER: ${{ secrets.EC2_USER }}
run: |
scp -O -i ~/.ssh/ec2_deploy.pem backend/jiu-server ${EC2_USER}@${EC2_HOST}:/tmp/jiu-server
rsync -avz --delete -e "ssh -i ~/.ssh/ec2_deploy.pem" \
client/build/web/ ${EC2_USER}@${EC2_HOST}:/tmp/jiu-web-new/
rsync -avz --delete -e "ssh -i ~/.ssh/ec2_deploy.pem" \
web/ ${EC2_USER}@${EC2_HOST}:/tmp/jiu-marketing-new/
scp -O -i ~/.ssh/ec2_deploy.pem deploy/nginx-jiu.conf \
${EC2_USER}@${EC2_HOST}:/tmp/nginx-jiu.conf
ssh -i ~/.ssh/ec2_deploy.pem ${EC2_USER}@${EC2_HOST} << 'ENDSSH'
sudo systemctl stop jiu
cp /tmp/jiu-server /opt/jiu/backend/jiu-server
chmod +x /opt/jiu/backend/jiu-server
sudo systemctl start jiu
for i in $(seq 1 30); do
curl -sf http://localhost:8080/health && break
sleep 2
done
rm -rf /opt/jiu/web-old
mv /opt/jiu/web /opt/jiu/web-old 2>/dev/null || true
mv /tmp/jiu-web-new /opt/jiu/web
mkdir -p /opt/jiu/marketing
rsync -a --delete /tmp/jiu-marketing-new/ /opt/jiu/marketing/
rm -rf /tmp/jiu-marketing-new
sudo cp /tmp/nginx-jiu.conf /etc/nginx/conf.d/jiu.conf
sudo nginx -t && sudo nginx -s reload
ENDSSH
- name: Cleanup SSH key
if: always()
run: rm -f ~/.ssh/ec2_deploy.pem
run: sh scripts/ci/deploy.sh "${{ gitea.ref_name }}"
+28
View File
@@ -0,0 +1,28 @@
name: Manual Deploy
on:
workflow_dispatch:
inputs:
version:
description: 'Tag to deploy (e.g. v1.0.0)'
required: true
type: string
jobs:
deploy:
runs-on: mac
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.version }}
- name: Deploy
env:
FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }}
FORGEJO_URL: ${{ secrets.FORGEJO_URL }}
GITEA_REPOSITORY: ${{ gitea.repository }}
EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }}
EC2_HOST: ${{ secrets.EC2_HOST }}
EC2_USER: ${{ secrets.EC2_USER }}
run: sh scripts/ci/deploy.sh "${{ inputs.version }}"