From 78ab1917f3c5b5c9ecbbfb6efb6c57f6ea45566f Mon Sep 17 00:00:00 2001 From: Blizzard Date: Thu, 1 Oct 2026 13:34:33 +0800 Subject: [PATCH] ci: verify every push before deploying main --- .gitea/workflows/ci.yml | 96 ++++++++++++++++++++++++++++++++++-- .gitea/workflows/deploy.yml | 98 ------------------------------------- 2 files changed, 91 insertions(+), 103 deletions(-) delete mode 100644 .gitea/workflows/deploy.yml diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index d9cddd1..0c385b4 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,9 +1,10 @@ -name: ci +name: ci-cd -on: - push: - branches: [feat/dev] - workflow_dispatch: +on: [push, workflow_dispatch] + +env: + REMOTE_DIR: /home/workspace/sundynix-pet + IMAGE: sundynix/pets-be:latest jobs: verify: @@ -20,3 +21,88 @@ jobs: - name: 测试并构建后端与管理端 run: docker build -f pets-be/Dockerfile -t "sundynix/pets-be:ci-${{ github.sha }}" pets-be + + deploy: + needs: verify + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest # 须匹配已注册 runner 的标签 + steps: + # Gitea 的 Git HTTP pack 当前不可用,改用带 job token 的归档接口检出当前提交。 + - name: 检出代码(内网 Gitea) + env: + GITEA_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + curl -fLsS -H "Authorization: token ${GITEA_TOKEN}" \ + "https://git.sundynix.cn/${{ github.repository }}/archive/${{ github.sha }}.tar.gz" \ + | tar -xz --strip-components=1 + + # 单镜像:内部先用 node 构建管理后台前端,再 Go 编译内嵌进二进制。 + # 构建上下文是 pets-be/(go.mod 和 web/admin 都在里面)。 + - name: 构建应用镜像 + run: docker build -f pets-be/Dockerfile -t "$IMAGE" pets-be + + - name: 导出镜像为压缩包 + run: docker save "$IMAGE" | gzip > image.tar.gz + + # 每次 job 都是全新容器,装过的东西不会留下。这里做两件事提速: + # 1) 已经有就直接跳过;2) 换阿里云镜像源,避免连境外源拖到几分钟。 + - name: 安装 ssh 工具 + run: | + if command -v sshpass >/dev/null && command -v ssh >/dev/null; then + echo "sshpass/ssh 已存在,跳过安装"; exit 0 + fi + if command -v apk >/dev/null; then + sed -i 's#dl-cdn.alpinelinux.org#mirrors.aliyun.com#g' /etc/apk/repositories + apk add --no-cache sshpass openssh-client + elif command -v apt-get >/dev/null; then + sed -i -e 's#deb.debian.org#mirrors.aliyun.com#g' \ + -e 's#security.debian.org#mirrors.aliyun.com#g' \ + -e 's#archive.ubuntu.com#mirrors.aliyun.com#g' \ + -e 's#security.ubuntu.com#mirrors.aliyun.com#g' \ + /etc/apt/sources.list 2>/dev/null || true + sed -i -e 's#deb.debian.org#mirrors.aliyun.com#g' \ + -e 's#archive.ubuntu.com#mirrors.aliyun.com#g' \ + /etc/apt/sources.list.d/*.sources /etc/apt/sources.list.d/*.list 2>/dev/null || true + apt-get update -qq && apt-get install -y -qq --no-install-recommends sshpass openssh-client + fi + + - name: 传镜像包与 compose 到服务器 + env: + SSHPASS: ${{ secrets.DEPLOY_PASSWORD }} + run: | + H="${{ secrets.DEPLOY_HOST }}" + U="${{ secrets.DEPLOY_USER }}" + # 只送两样:镜像包 + compose。服务器上的 .env 由人工预放,绝不覆盖。 + sshpass -e ssh -o StrictHostKeyChecking=no "$U@$H" "mkdir -p $REMOTE_DIR" + sshpass -e scp -o StrictHostKeyChecking=no image.tar.gz "$U@$H:$REMOTE_DIR/" + sshpass -e scp -o StrictHostKeyChecking=no \ + deploy/docker-compose.yml "$U@$H:$REMOTE_DIR/docker-compose.yml" + + - name: 服务器加载镜像并起服务 + env: + SSHPASS: ${{ secrets.DEPLOY_PASSWORD }} + run: | + H="${{ secrets.DEPLOY_HOST }}" + U="${{ secrets.DEPLOY_USER }}" + sshpass -e ssh -o StrictHostKeyChecking=no "$U@$H" "\ + cd $REMOTE_DIR && \ + test -f .env || { echo '❌ 缺少 .env,请先按 deploy/.env.example 创建'; exit 1; } && \ + gunzip -c image.tar.gz | docker load && \ + rm -f image.tar.gz && \ + docker compose up -d --no-build && \ + docker image prune -f" + + - name: 健康检查(宿主机 :4000/api/ping) + env: + SSHPASS: ${{ secrets.DEPLOY_PASSWORD }} + run: | + H="${{ secrets.DEPLOY_HOST }}" + U="${{ secrets.DEPLOY_USER }}" + sshpass -e ssh -o StrictHostKeyChecking=no "$U@$H" "\ + for i in \$(seq 1 20); do \ + curl -sf http://127.0.0.1:4000/api/ping && echo ' ✅ 健康' && exit 0; \ + sleep 3; \ + done; \ + echo '❌ 健康检查失败'; \ + cd $REMOTE_DIR && docker compose logs --tail 50 app; exit 1" diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml deleted file mode 100644 index 0b77aa7..0000000 --- a/.gitea/workflows/deploy.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: deploy - -# 只认 main(含 PR 合并入 main)。 -# runner 上构建单个应用镜像 → docker save 打包 → scp 镜像包 + compose 到服务器 -# → 服务器 docker load + docker compose up -d(用预构建镜像,不在服务器上 build)。 -# 服务器上只有 compose + .env(+ 临时镜像包),没有源码。 -on: - push: - branches: [main] - workflow_dispatch: - -env: - REMOTE_DIR: /home/workspace/sundynix-pet - IMAGE: sundynix/pets-be:latest - -jobs: - deploy: - runs-on: ubuntu-latest # 须匹配已注册 runner 的标签 - steps: - # Gitea 的 Git HTTP pack 当前不可用,改用带 job token 的归档接口检出当前提交。 - - name: 检出代码(内网 Gitea) - env: - GITEA_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - curl -fLsS -H "Authorization: token ${GITEA_TOKEN}" \ - "https://git.sundynix.cn/${{ github.repository }}/archive/${{ github.sha }}.tar.gz" \ - | tar -xz --strip-components=1 - - # 单镜像:内部先用 node 构建管理后台前端,再 Go 编译内嵌进二进制。 - # 构建上下文是 pets-be/(go.mod 和 web/admin 都在里面)。 - - name: 构建应用镜像 - run: docker build -f pets-be/Dockerfile -t "$IMAGE" pets-be - - - name: 导出镜像为压缩包 - run: docker save "$IMAGE" | gzip > image.tar.gz - - # 每次 job 都是全新容器,装过的东西不会留下。这里做两件事提速: - # 1) 已经有就直接跳过;2) 换阿里云镜像源,避免连境外源拖到几分钟。 - - name: 安装 ssh 工具 - run: | - if command -v sshpass >/dev/null && command -v ssh >/dev/null; then - echo "sshpass/ssh 已存在,跳过安装"; exit 0 - fi - if command -v apk >/dev/null; then - sed -i 's#dl-cdn.alpinelinux.org#mirrors.aliyun.com#g' /etc/apk/repositories - apk add --no-cache sshpass openssh-client - elif command -v apt-get >/dev/null; then - sed -i -e 's#deb.debian.org#mirrors.aliyun.com#g' \ - -e 's#security.debian.org#mirrors.aliyun.com#g' \ - -e 's#archive.ubuntu.com#mirrors.aliyun.com#g' \ - -e 's#security.ubuntu.com#mirrors.aliyun.com#g' \ - /etc/apt/sources.list 2>/dev/null || true - sed -i -e 's#deb.debian.org#mirrors.aliyun.com#g' \ - -e 's#archive.ubuntu.com#mirrors.aliyun.com#g' \ - /etc/apt/sources.list.d/*.sources /etc/apt/sources.list.d/*.list 2>/dev/null || true - apt-get update -qq && apt-get install -y -qq --no-install-recommends sshpass openssh-client - fi - - - name: 传镜像包与 compose 到服务器 - env: - SSHPASS: ${{ secrets.DEPLOY_PASSWORD }} - run: | - H="${{ secrets.DEPLOY_HOST }}" - U="${{ secrets.DEPLOY_USER }}" - # 只送两样:镜像包 + compose。服务器上的 .env 由人工预放,绝不覆盖。 - sshpass -e ssh -o StrictHostKeyChecking=no "$U@$H" "mkdir -p $REMOTE_DIR" - sshpass -e scp -o StrictHostKeyChecking=no image.tar.gz "$U@$H:$REMOTE_DIR/" - sshpass -e scp -o StrictHostKeyChecking=no \ - deploy/docker-compose.yml "$U@$H:$REMOTE_DIR/docker-compose.yml" - - - name: 服务器加载镜像并起服务 - env: - SSHPASS: ${{ secrets.DEPLOY_PASSWORD }} - run: | - H="${{ secrets.DEPLOY_HOST }}" - U="${{ secrets.DEPLOY_USER }}" - sshpass -e ssh -o StrictHostKeyChecking=no "$U@$H" "\ - cd $REMOTE_DIR && \ - test -f .env || { echo '❌ 缺少 .env,请先按 deploy/.env.example 创建'; exit 1; } && \ - gunzip -c image.tar.gz | docker load && \ - rm -f image.tar.gz && \ - docker compose up -d --no-build && \ - docker image prune -f" - - - name: 健康检查(宿主机 :4000/api/ping) - env: - SSHPASS: ${{ secrets.DEPLOY_PASSWORD }} - run: | - H="${{ secrets.DEPLOY_HOST }}" - U="${{ secrets.DEPLOY_USER }}" - sshpass -e ssh -o StrictHostKeyChecking=no "$U@$H" "\ - for i in \$(seq 1 20); do \ - curl -sf http://127.0.0.1:4000/api/ping && echo ' ✅ 健康' && exit 0; \ - sleep 3; \ - done; \ - echo '❌ 健康检查失败'; \ - cd $REMOTE_DIR && docker compose logs --tail 50 app; exit 1"