From b9f89f65260a198ad617b3dabe524c2580dcea48 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sun, 5 Jul 2026 12:16:36 +0800 Subject: [PATCH] =?UTF-8?q?ci:=20=E6=89=93=20v*=20tag=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8F=91=E7=89=88=E5=88=B0=20ali=EF=BC=88Gitea=20Actions?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 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 Claude-Session: https://claude.ai/code/session_019UQmqWmV67sXGLrb3U1XXn --- .gitea/workflows/checks.yml | 35 ++++++++++++++++++++++ .gitea/workflows/deploy.yml | 41 +++++++++++++++++++++++++ README.md | 11 +++++-- scripts/ci/_env.sh | 10 +++++++ scripts/ci/deploy.sh | 60 +++++++++++++++++++++++++++++++++++++ scripts/ci/notify.sh | 28 +++++++++++++++++ 6 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 .gitea/workflows/checks.yml create mode 100644 .gitea/workflows/deploy.yml create mode 100755 scripts/ci/_env.sh create mode 100755 scripts/ci/deploy.sh create mode 100755 scripts/ci/notify.sh diff --git a/.gitea/workflows/checks.yml b/.gitea/workflows/checks.yml new file mode 100644 index 0000000..a81d55c --- /dev/null +++ b/.gitea/workflows/checks.yml @@ -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 ./... diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..a628c74 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,41 @@ +name: Deploy + +# 打 v* tag 即自动发版到 ali(单 Go 二进制 payd,systemd 服务 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 }}" diff --git a/README.md b/README.md index f48114e..5c6a7cb 100644 --- a/README.md +++ b/README.md @@ -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 → 传 ali(ssh 别名)→ 备份 → 重启 pay → 探活(失败自动回滚) ``` 手动构建: diff --git a/scripts/ci/_env.sh b/scripts/ci/_env.sh new file mode 100755 index 0000000..2f09a5f --- /dev/null +++ b/scripts/ci/_env.sh @@ -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 diff --git a/scripts/ci/deploy.sh b/scripts/ci/deploy.sh new file mode 100755 index 0000000..92c938f --- /dev/null +++ b/scripts/ci/deploy.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# scripts/ci/deploy.sh — 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 灌)。 +# +# 需要的 secrets(pay 仓 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 diff --git a/scripts/ci/notify.sh b/scripts/ci/notify.sh new file mode 100755 index 0000000..c74ddf9 --- /dev/null +++ b/scripts/ci/notify.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# notify.sh — 发 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})"