ci: 打 v* tag 自动发版到 ali(Gitea Actions)

新增 Gitea Actions 流水线,复刻本地 deploy.sh 到 CI,打 v* tag 即自动发版:
- .gitea/workflows/deploy.yml:v*.*.* tag 触发 → go test → 交叉编译 linux/amd64
  → 传 ali → 备份 payd.bak → 切换 → systemctl restart pay → 探活(失败自动回滚)
- .gitea/workflows/checks.yml:PR/合并 main 跑 go vet+build+test 静态闸
- scripts/ci/deploy.sh:本地 deploy.sh 的 CI 版(从 secret 注入部署私钥 + 主机,
  不用 ~/.ssh 的 ali 别名);notify.sh:Telegram 通知(secrets 未配则静默跳过)
- runs-on mac,复用 jiu 的自建 runner;业务密钥仍走 ali /etc/pay/pay.env
- README 部署段补 CI 路径 + 所需 secrets(ALI_SSH_KEY/ALI_HOST/ALI_USER)

首次需在 pay 仓 Gitea → Settings → Actions → Secrets 配 3 个 ALI_* secret。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019UQmqWmV67sXGLrb3U1XXn
This commit is contained in:
wangjia
2026-07-05 12:16:36 +08:00
parent e0c7baa7cb
commit b9f89f6526
6 changed files with 183 additions and 2 deletions
+35
View File
@@ -0,0 +1,35 @@
name: Checks
# PR 与合并 main 时跑 Go 静态闸(vet + build + test),发版前先绿。
on:
pull_request:
push:
branches: [main]
concurrency:
group: checks-${{ gitea.ref }}
cancel-in-progress: true
jobs:
go:
runs-on: mac
env:
GOPROXY: https://goproxy.cn,direct
steps:
- uses: actions/checkout@v4
- name: go vet
run: |
export PATH="/opt/homebrew/bin:$PATH"
go vet ./...
- name: go build
run: |
export PATH="/opt/homebrew/bin:$PATH"
go build ./...
- name: go test
run: |
export PATH="/opt/homebrew/bin:$PATH"
go test ./...
+41
View File
@@ -0,0 +1,41 @@
name: Deploy
# 打 v* tag 即自动发版到 ali(单 Go 二进制 paydsystemd 服务 pay:8080/health)。
# git tag v1.0.2 && git push origin v1.0.2
# runs-on: mac —— 复用 jiu 的自建 mac runner(已注册、装了 go)。
on:
push:
tags:
- 'v[0-9]*.[0-9]*.[0-9]*'
concurrency:
group: deploy-pay
cancel-in-progress: false
jobs:
release-deploy:
runs-on: mac
env:
GOPROXY: https://goproxy.cn,direct
steps:
- uses: actions/checkout@v4
- name: Test (go test)
run: |
export PATH="/opt/homebrew/bin:$PATH"
go test ./...
- name: Deploy → Ali(交叉编译 + 传 + 备份 + 重启 + 探活/回滚)
env:
DEPLOY_HOST: ${{ secrets.ALI_HOST }}
DEPLOY_USER: ${{ secrets.ALI_USER }}
DEPLOY_SSH_KEY: ${{ secrets.ALI_SSH_KEY }}
run: sh scripts/ci/deploy.sh "${{ gitea.ref_name }}"
- name: Notify
if: always()
env:
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
run: sh scripts/ci/notify.sh "${{ gitea.ref_name }}" "${{ job.status }}"
+9 -2
View File
@@ -60,9 +60,16 @@ pay/
## 构建部署
**一键发布到 ali(推荐)**
**打 tag 自动发版到 ali(推荐)**Gitea Actions 在 `v*` tag 上跑 `go test` → 交叉编译 → 传 ali → 备份 → 重启 → 探活(失败自动回滚)。
```bash
./deploy.sh # 交叉编译 linux/amd64 → 传 ali → 备份 → 重启 pay → 探活(失败自动回滚)
git tag v1.0.2 && git push origin v1.0.2 # 触发 .gitea/workflows/deploy.yml
```
- runs-on `mac`(复用 jiu 的自建 runner);PR/合并 main 另跑 `checks.yml`vet+build+test)。
- **首次需在 pay 仓 Gitea → Settings → Actions → Secrets 配**`ALI_SSH_KEY`(授权 ali root 的部署私钥整段)、`ALI_HOST`、`ALI_USER`root);可选 `TELEGRAM_TOKEN`/`TELEGRAM_CHAT_ID`(发布通知,不配则跳过)。密钥只进 Gitea Secrets,不入库。
**本地一键发布(等价手动路径)**:
```bash
./deploy.sh # 交叉编译 linux/amd64 → 传 alissh 别名)→ 备份 → 重启 pay → 探活(失败自动回滚)
```
手动构建:
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# _env.sh — 共享 CI 镜像/PATH 环境,供其他脚本 `source`。幂等:只在未设置时赋值。
export GOPROXY="${GOPROXY:-https://goproxy.cn,direct}"
# 自建 mac runner 上 go 装在 Homebrew;确保在 PATH(对其他环境无害)。
case ":${PATH}:" in
*":/opt/homebrew/bin:"*) ;;
*) export PATH="/opt/homebrew/bin:${PATH}" ;;
esac
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# scripts/ci/deploy.sh <tag> — CI 发布:交叉编译 linux/amd64 → 传 ali → 备份 →
# 切换 → 重启 pay → 探活(失败自动回滚)。本地 ./deploy.sh 的 CI 版:不用
# ~/.ssh 里的 `ali` 别名,而是从 secret 注入部署私钥、从 secret 取主机。
#
# 由 .gitea/workflows/deploy.yml 在打 v* tag 时触发(runs-on: mac,复用 jiu
# 的自建 mac runner)。业务密钥不在此处理——运行时由 ali 上 /etc/pay/pay.env
# 提供(Bitwarden 灌)。
#
# 需要的 secretspay 仓 Gitea → Settings → Actions → Secrets):
# ALI_SSH_KEY 已授权 ali root 的部署私钥(整段,OpenSSH 或 PEM
# ALI_HOST ali 可达地址
# ALI_USER 部署用户(默认 root)
set -euo pipefail
# shellcheck source=scripts/ci/_env.sh
. "$(dirname "$0")/_env.sh"
TAG="${1:-manual}"
TARGET_HOST="${DEPLOY_HOST:?ALI_HOST secret 未设}"
TARGET_USER="${DEPLOY_USER:-root}"
DIR=/opt/pay
BIN=dist/payd
KEY="${HOME}/.ssh/pay_deploy.key"
echo "==> deploy: tag=${TAG} host=${TARGET_HOST}"
echo "[1/4] 交叉编译 linux/amd64 …"
mkdir -p dist
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o "$BIN" .
echo "[2/4] 写部署 key + 上传到 ${TARGET_HOST}:${DIR}/payd-new …"
mkdir -p "${HOME}/.ssh"
# printf '%s\n' 末尾补换行:Gitea 存 secret 会去掉结尾换行,而 OpenSSH 格式
# 私钥缺结尾换行会被判 "invalid format" 拒绝加载 → 退化成无密钥 Permission denied。
printf '%s\n' "${DEPLOY_SSH_KEY:?ALI_SSH_KEY secret 未设}" > "$KEY"
chmod 600 "$KEY"
ssh-keyscan -H "$TARGET_HOST" >> "${HOME}/.ssh/known_hosts" 2>/dev/null || true
SSH="ssh -i $KEY -o StrictHostKeyChecking=no"
SCP="scp -O -i $KEY -o StrictHostKeyChecking=no"
cleanup() { rm -f "$KEY"; }
trap cleanup EXIT
$SCP "$BIN" "${TARGET_USER}@${TARGET_HOST}:${DIR}/payd-new"
echo "[3/4] 备份旧版 + 切换 + 重启 …"
$SSH "${TARGET_USER}@${TARGET_HOST}" \
"cp ${DIR}/payd ${DIR}/payd.bak && mv ${DIR}/payd-new ${DIR}/payd && chmod +x ${DIR}/payd && systemctl restart pay"
echo "[4/4] 探活 …"
if $SSH "${TARGET_USER}@${TARGET_HOST}" \
"curl -fsS --retry 8 --retry-delay 1 --retry-connrefused -o /dev/null -w 'health %{http_code}\n' http://127.0.0.1:8080/health"; then
echo "✅ 发布完成(上一版已备份在 ${DIR}/payd.bak"
else
echo "❌ 探活失败,自动回滚到上一版 …"
$SSH "${TARGET_USER}@${TARGET_HOST}" "cp ${DIR}/payd.bak ${DIR}/payd && systemctl restart pay"
echo "↩️ 已回滚。请检查本次改动后重试。"
exit 1
fi
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# notify.sh <tag> <status> — 发 Telegram 发布通知。secrets 未配则静默跳过
# (所以没设 TELEGRAM_* 也不会让流水线失败)。
set -euo pipefail
TAG="${1:-}"
STATUS="${2:-}"
if [ -z "${TELEGRAM_TOKEN:-}" ] || [ -z "${TELEGRAM_CHAT_ID:-}" ]; then
echo "==> notify: 未配 TELEGRAM_* secrets,跳过"
exit 0
fi
if [ "$STATUS" = "success" ]; then
ICON="✅"; LABEL="发布成功"
else
ICON="❌"; LABEL="发布失败"
fi
MSG="${ICON} pay ${TAG} ${LABEL}
https://pay.51yanmei.com"
curl -f -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
-d "chat_id=${TELEGRAM_CHAT_ID}" \
--data-urlencode "text=${MSG}" \
> /dev/null || echo "==> notify: 发送失败(忽略,不阻断流水线)"
echo "==> notify: done (${STATUS})"