From b507d6c154887c10abe5e15b85f6ee43949a2e9c Mon Sep 17 00:00:00 2001 From: gouki Date: Sun, 9 Aug 2026 17:17:23 +0000 Subject: [PATCH] =?UTF-8?q?feat(ci):=20=E8=BF=81=E7=A7=BB=E5=88=B0=20hk=20?= =?UTF-8?q?runner=20+=20Release=20=E4=BA=A7=E7=89=A9=E5=88=86=E5=8F=91?= =?UTF-8?q?=EF=BC=8C=E8=A7=A3=E9=99=A4=E5=AF=B9=20doc79=20runner=20?= =?UTF-8?q?=E7=9A=84=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 两个工作流 runs-on 改为 hk(在线);Go/Node 工具链自包含下载+工作区缓存 - 后端产物改为发布 Gitea Release 附件(server-v<版本>),与 runner 位置解耦 - deploy-watch.sh 重写为从 Release 拉取:匿名/令牌 HTTPS 下载、包结构预检、 按 release id 幂等、失败下轮重试 - 移除对 /ci-artifacts 本机目录的依赖(原设计要求 runner 与生产同机) - PULL_DEPLOY.md 架构图同步更新 --- .gitea/docs/PULL_DEPLOY.md | 11 ++- .gitea/scripts/deploy-watch.sh | 93 +++++++++++++++++----- .gitea/workflows/miniapp-preview.yml | 50 ++++++++---- .gitea/workflows/server-deploy.yml | 112 ++++++++++++++++++--------- 4 files changed, 191 insertions(+), 75 deletions(-) diff --git a/.gitea/docs/PULL_DEPLOY.md b/.gitea/docs/PULL_DEPLOY.md index 85b2d1d..8d6ff5e 100644 --- a/.gitea/docs/PULL_DEPLOY.md +++ b/.gitea/docs/PULL_DEPLOY.md @@ -7,13 +7,18 @@ ## 架构 ``` -push main → Gitea Actions 构建(runner 与 doc79 同机)→ 产物写入 /ci-artifacts +push main → Gitea Actions(hk runner 构建)→ 部署包发布为 Gitea Release 附件 ↓ -1Panel 计划任务(每分钟)运行 deploy-watch.sh → 发现新 sha256 → deploy.sh 原子切换 + 重启容器 + 健康检查 +1Panel 计划任务(每分钟)运行 deploy-watch.sh → 发现新 release → HTTPS 拉取 deploy.tar.gz + ↓ +deploy.sh 原子切换 + 重启容器 + 健康检查(失败自动回滚) ↓ EdgeOne CDN (HTTPS 终结) → doc79:80 → 1Panel OpenResty 反代 → 127.0.0.1:8080 lunar-server 容器 ``` +> 产物通过 Gitea Release 分发,与 runner 位置解耦(hk/bt 均可构建); +> 服务器侧只出站 HTTPS 拉取,无任何入站 SSH。 + ## 一次性安装步骤(按序执行) ### 1. EdgeOne 回源配置(控制台) @@ -87,6 +92,8 @@ ADMIN_PASSWORD=<后台登录密码> - 脚本内容: ```bash +# 可选:若仓库私有或需要令牌拉取,在 .env 中配置 GITEA_DEPLOY_TOKEN +[ -f /opt/lunar/production/.env ] && set -a && . /opt/lunar/production/.env && set +a bash /opt/lunar/scripts/deploy-watch.sh ``` diff --git a/.gitea/scripts/deploy-watch.sh b/.gitea/scripts/deploy-watch.sh index f657d93..5ba182b 100644 --- a/.gitea/scripts/deploy-watch.sh +++ b/.gitea/scripts/deploy-watch.sh @@ -1,47 +1,98 @@ #!/bin/bash -# 拉取式部署 watcher - 监听 CI 产物目录,发现新版本自动部署 +# 拉取式部署 watcher - 从 Gitea Release 拉取最新部署包并自动部署 # 设计原则: -# - CI 只负责构建并把产物写入 /ci-artifacts(runner 与本机同宿主) -# - 部署动作只由服务器本地触发(1Panel 计划任务每分钟调用本脚本), +# - CI 在任意 runner(hk 等)构建,产物以 Release 附件发布到 Gitea +# - 本脚本由服务器本地定时任务(1Panel 计划任务)每分钟调用,通过 HTTPS 拉取, # 任何 AI/CI 均不通过 SSH 操作服务器 # 安装方式见 .gitea/docs/PULL_DEPLOY.md(1Panel 原生方式,一次性配置) +# +# 可选环境变量(建议写入 /opt/lunar/production/.env 并在计划任务里 source): +# GITEA_DEPLOY_TOKEN 只读令牌(仓库公开时可省略,匿名即可下载) set -u -ARTIFACT="${ARTIFACT:-/ci-artifacts/server-deploy-latest.tar.gz}" -STATE_FILE="${STATE_FILE:-/opt/lunar/production/.last-deployed-sha256}" +GITEA_URL="${GITEA_URL:-https://gitea.neatcn.com}" +REPO="${REPO:-gouki/lunar-mini}" +STATE_FILE="${STATE_FILE:-/opt/lunar/production/.last-deployed-release}" DEPLOY_SCRIPT="${DEPLOY_SCRIPT:-/opt/lunar/scripts/deploy.sh}" LOG_FILE="${LOG_FILE:-/opt/lunar/deploy-watch.log}" +WORK_DIR="$(mktemp -d)" +trap 'rm -rf "$WORK_DIR"' EXIT log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE" } -# 产物不存在时静默等待 -if [ ! -f "$ARTIFACT" ]; then +AUTH_ARGS=() +if [ -n "${GITEA_DEPLOY_TOKEN:-}" ]; then + AUTH_ARGS=(-H "Authorization: token ${GITEA_DEPLOY_TOKEN}") +fi + +# 查询最新 20 个 release,取 tag 前缀为 server-v 且带 deploy.tar.gz 附件的最新一个 +RELEASE_JSON=$(curl -fsS --connect-timeout 10 --max-time 30 \ + "${AUTH_ARGS[@]}" \ + "$GITEA_URL/api/v1/repos/$REPO/releases?limit=20" \ + -o "$WORK_DIR/releases.json" && cat "$WORK_DIR/releases.json") || { + log "查询 release 列表失败,等待下次轮询" + exit 1 +} + +PARSED=$(python3 - "$WORK_DIR/releases.json" <<'PY' +import json, sys +releases = json.load(open(sys.argv[1])) +for r in releases: + if not r.get("tag_name", "").startswith("server-v"): + continue + if r.get("draft") or r.get("prerelease"): + continue + for a in r.get("assets", []): + if a.get("name") == "deploy.tar.gz": + print(r["id"]) + print(r["tag_name"]) + print(a["id"]) + print(a["browser_download_url"]) + sys.exit(0) +PY +) || true + +if [ -z "$PARSED" ]; then + # 尚无可部署的发布版本,静默等待 exit 0 fi -# 计算产物指纹 -CURRENT_SHA="$(sha256sum "$ARTIFACT" | awk '{print $1}')" -LAST_SHA="" -if [ -f "$STATE_FILE" ]; then - LAST_SHA="$(cat "$STATE_FILE")" -fi +RELEASE_ID=$(echo "$PARSED" | sed -n 1p) +TAG=$(echo "$PARSED" | sed -n 2p) +ASSET_ID=$(echo "$PARSED" | sed -n 3p) +DOWNLOAD_URL=$(echo "$PARSED" | sed -n 4p) -# 无变化则跳过 -if [ "$CURRENT_SHA" = "$LAST_SHA" ]; then +# 幂等:已部署过该 release 则跳过 +LAST="$(cat "$STATE_FILE" 2>/dev/null || true)" +if [ "$LAST" = "$RELEASE_ID" ]; then exit 0 fi -log "发现新版本产物 sha256=${CURRENT_SHA:0:12},开始部署" +log "发现新版本 $TAG (release #$RELEASE_ID),开始拉取部署" -# 执行部署;成功后才记录指纹(失败时下次轮询会重试) -if bash "$DEPLOY_SCRIPT" "$ARTIFACT" >> "$LOG_FILE" 2>&1; then +# 下载部署包(公开仓库可匿名;-L 跟随重定向) +if ! curl -fsSL --connect-timeout 10 --max-time 300 \ + "${AUTH_ARGS[@]}" \ + "$DOWNLOAD_URL" -o "$WORK_DIR/deploy.tar.gz"; then + log "下载部署包失败,等待下次轮询重试" + exit 1 +fi + +# 包结构预检(deploy.sh 内部还会再校验) +if ! tar -tzf "$WORK_DIR/deploy.tar.gz" | grep -q "^deploy/bin/server$"; then + log "部署包结构异常(缺少 deploy/bin/server),中止" + exit 1 +fi + +# 执行部署;成功后才记录 release id(失败时下次轮询会重试) +if bash "$DEPLOY_SCRIPT" "$WORK_DIR/deploy.tar.gz" >> "$LOG_FILE" 2>&1; then mkdir -p "$(dirname "$STATE_FILE")" - printf '%s' "$CURRENT_SHA" > "$STATE_FILE" - log "部署成功" + printf '%s' "$RELEASE_ID" > "$STATE_FILE" + log "部署成功: $TAG" else - log "部署失败,保留旧指纹,等待下次轮询重试或人工介入" + log "部署失败($TAG),保留旧版本,等待下次轮询重试或人工介入" exit 1 fi diff --git a/.gitea/workflows/miniapp-preview.yml b/.gitea/workflows/miniapp-preview.yml index c0a9493..a5e8252 100644 --- a/.gitea/workflows/miniapp-preview.yml +++ b/.gitea/workflows/miniapp-preview.yml @@ -11,21 +11,45 @@ on: jobs: publish: - # 专属运行器 lunar-node: docker://node:22-bookworm(注册于 doc79) - runs-on: lunar-node + # hk runner(海外,宿主机直接执行;可直连微信与 Telegram/Discord) + runs-on: hk steps: - uses: https://gitea.neatcn.com/gouki/action-checkout@v4 with: # Need recent commit subjects for the WeChat upload remark. fetch-depth: 20 + + - name: Prepare Node toolchain + run: | + # 优先复用 runner 宿主机缓存的工具链(工作目录持久化) + TOOLDIR="$RUNNER_WORKSPACE/.lunar-toolchain" + mkdir -p "$TOOLDIR" + if [ -x "$TOOLDIR/node/bin/node" ]; then + echo "复用缓存 Node 工具链" + else + wget -q --tries=2 --timeout=60 -O "$TOOLDIR/node.tgz" \ + "https://nodejs.org/dist/v22.12.0/node-v22.12.0-linux-x64.tar.xz" \ + || wget -q --tries=2 --timeout=60 -O "$TOOLDIR/node.tgz" \ + "https://mirrors.aliyun.com/nodejs-release/v22.12.0/node-v22.12.0-linux-x64.tar.xz" + rm -rf "$TOOLDIR/node-v22.12.0-linux-x64" + tar -C "$TOOLDIR" -xJf "$TOOLDIR/node.tgz" + mv "$TOOLDIR/node-v22.12.0-linux-x64" "$TOOLDIR/node" + rm -f "$TOOLDIR/node.tgz" + fi + "$TOOLDIR/node/bin/node" --version + echo "PATH=$TOOLDIR/node/bin:$PATH" >> "$GITEA_ENV" + - name: Verify Node runtime run: node --version + - name: Install dependencies working-directory: mini run: npm ci + - name: Run tests working-directory: mini run: npm test + - name: Configure production API and app version working-directory: mini env: @@ -39,7 +63,7 @@ jobs: mkdir -p ci-artifacts printf '%s\n' "$VERSION" > ci-artifacts/resolved-version.txt echo "Resolved Mini Program version for build+upload: $VERSION" - + # 注入版本号到 utils/version.js BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" COMMIT_SHORT="${GITEA_SHA:0:7}" @@ -48,7 +72,7 @@ jobs: * 版本号配置文件 * 此文件由 CI 构建时自动生成,请勿手动修改 */ - + module.exports = { version: "$VERSION", buildNumber: "$MINIAPP_BUILD_NUMBER", @@ -58,6 +82,7 @@ jobs: EOF echo "Version info injected into utils/version.js:" cat utils/version.js + - name: Materialize the upload key env: WECHAT_CI_PRIVATE_KEY: ${{ secrets.WECHAT_CI_PRIVATE_KEY }} @@ -65,6 +90,7 @@ jobs: test -n "$WECHAT_CI_PRIVATE_KEY" umask 077 printf '%s' "$WECHAT_CI_PRIVATE_KEY" > "$RUNNER_TEMP/wechat-ci.key" + - name: Upload WeChat development version working-directory: mini env: @@ -106,15 +132,7 @@ jobs: node ci/upload-ci.cjs test -s ci-artifacts/upload-result.json cat ci-artifacts/upload-result.json - - name: Persist upload result on runner host - run: | - test -s mini/ci-artifacts/upload-result.json - test -d /ci-artifacts - cp mini/ci-artifacts/upload-result.json "/ci-artifacts/miniapp-upload-${{ gitea.sha }}.json" - cp mini/ci-artifacts/upload-result.json /ci-artifacts/miniapp-upload-latest.json - ls -la /ci-artifacts/miniapp-upload-latest.json - echo "Upload result saved on runner host: /opt/lunar/ci-artifacts/miniapp-upload-latest.json" - + - name: Send success notification if: success() env: @@ -123,8 +141,8 @@ jobs: DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} run: | VERSION="$(tr -d '\n' < mini/ci-artifacts/resolved-version.txt)" - bash .gitea/scripts/notify.sh success "小程序开发版上传成功" "版本: ${VERSION} 上传结果已保存到: /opt/lunar/ci-artifacts/miniapp-upload-latest.json" - + bash .gitea/scripts/notify.sh success "小程序开发版上传成功" "版本: ${VERSION}" + - name: Send failure notification if: failure() env: @@ -135,7 +153,7 @@ jobs: bash .gitea/scripts/notify.sh failure \ "小程序开发版上传失败" \ "请检查 Actions 日志了解失败原因" - + - name: Remove temporary credentials if: always() run: rm -f "$RUNNER_TEMP/wechat-ci.key" diff --git a/.gitea/workflows/server-deploy.yml b/.gitea/workflows/server-deploy.yml index d35fe5d..c2a9d72 100644 --- a/.gitea/workflows/server-deploy.yml +++ b/.gitea/workflows/server-deploy.yml @@ -1,4 +1,4 @@ -name: Build and Deploy Server +name: Build and Publish Server on: push: @@ -10,58 +10,77 @@ on: jobs: build: - # 专属运行器:docker://golang:1.22-bookworm(注册于 doc79) - runs-on: lunar-ci + # hk runner(海外,宿主机直接执行,无容器标签映射) + runs-on: hk steps: - uses: https://gitea.neatcn.com/gouki/action-checkout@v4 with: fetch-depth: 20 - - - name: Verify Go runtime - run: go version - + + - name: Prepare Go toolchain + run: | + # 优先复用 runner 宿主机缓存的工具链(工作目录持久化) + TOOLDIR="$RUNNER_WORKSPACE/.lunar-toolchain" + mkdir -p "$TOOLDIR" + if [ -x "$TOOLDIR/go/bin/go" ]; then + echo "复用缓存 Go 工具链" + else + # 官方源与阿里云镜像双通道,先到先用 + wget -q --tries=2 --timeout=60 -O "$TOOLDIR/go.tgz" \ + "https://go.dev/dl/go1.22.10.linux-amd64.tar.gz" \ + || wget -q --tries=2 --timeout=60 -O "$TOOLDIR/go.tgz" \ + "https://mirrors.aliyun.com/golang/go1.22.10.linux-amd64.tar.gz" + rm -rf "$TOOLDIR/go" + tar -C "$TOOLDIR" -xzf "$TOOLDIR/go.tgz" + rm -f "$TOOLDIR/go.tgz" + fi + "$TOOLDIR/go/bin/go" version + echo "LUNAR_GO=$TOOLDIR/go/bin/go" >> "$GITEA_ENV" + - name: Install dependencies working-directory: server - run: go mod download - + run: | + export HOME="$RUNNER_WORKSPACE" + "$LUNAR_GO" mod download + - name: Run tests working-directory: server - run: go test ./... - + run: | + export HOME="$RUNNER_WORKSPACE" + "$LUNAR_GO" test ./... + - name: Build application working-directory: server env: GITEA_SHA: ${{ gitea.sha }} GITEA_RUN_NUMBER: ${{ gitea.run_number }} run: | + export HOME="$RUNNER_WORKSPACE" mkdir -p bin - + # 生成版本号(使用构建编号) VERSION="1.0.${GITEA_RUN_NUMBER}" BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" COMMIT_SHORT="${GITEA_SHA:0:7}" - + echo "Building server version: $VERSION" - echo "Build time: $BUILD_TIME" - echo "Commit SHA: $COMMIT_SHORT" - - # 使用 ldflags 注入版本信息 - go build -ldflags "\ + + # ldflags 注入版本信息;目标平台为 doc79(linux/amd64) + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 "$LUNAR_GO" build -ldflags "\ -X main.Version=$VERSION \ -X main.BuildTime=$BUILD_TIME \ -X main.CommitSha=$COMMIT_SHORT" \ -o bin/server cmd/main.go - - # 同时保存版本信息到文件(用于部署时读取) + + # 版本元数据随包分发,便于服务器侧追溯 cat > bin/version.env <> "$GITEA_ENV" + - name: Create deployment package working-directory: server run: | @@ -74,16 +93,37 @@ jobs: cp -r web/ deploy/web/ fi tar -czf deploy.tar.gz deploy/ - - - name: Persist deployment package on runner host + sha256sum deploy.tar.gz + + - name: Publish release with deployment package + env: + GITEA_SHA: ${{ gitea.sha }} + GITEA_RUN_NUMBER: ${{ gitea.run_number }} run: | - test -f server/deploy.tar.gz - test -d /ci-artifacts - cp server/deploy.tar.gz "/ci-artifacts/server-deploy-${{ gitea.sha }}.tar.gz" - cp server/deploy.tar.gz /ci-artifacts/server-deploy-latest.tar.gz - ls -la /ci-artifacts/server-deploy-latest.tar.gz - echo "Deployment package saved on runner host: /opt/lunar/ci-artifacts/server-deploy-latest.tar.gz" - + set -e + VERSION="1.0.${GITEA_RUN_NUMBER}" + TAG="server-v${VERSION}" + API="https://gitea.neatcn.com/api/v1/repos/gouki/lunar-mini" + # Gitea Actions 内建的 job 令牌(仓库写权限) + AUTH="Authorization: token ${GITEA_TOKEN}" + + # 幂等:同 tag 已存在则先删除(重跑场景) + EXISTING=$(curl -sS -H "$AUTH" "$API/releases/tags/$TAG" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true) + if [ -n "$EXISTING" ] && [ "$EXISTING" != "None" ]; then + curl -sS -X DELETE -H "$AUTH" "$API/releases/$EXISTING" -o /dev/null -w "删除旧 release: HTTP %{http_code}\n" + fi + + RELEASE_ID=$(curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \ + -d "{\"tag_name\":\"$TAG\",\"name\":\"server $VERSION\",\"target_commitish\":\"main\",\"body\":\"CI 自动构建,commit ${GITEA_SHA:0:7}。doc79 由 deploy-watch.sh 拉取本附件完成部署。\"}" \ + "$API/releases" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])") + echo "Release created: id=$RELEASE_ID tag=$TAG" + + curl -sS -X POST -H "$AUTH" \ + -F "attachment=@server/deploy.tar.gz" \ + "$API/releases/$RELEASE_ID/assets" \ + -o /dev/null -w "附件上传: HTTP %{http_code}\n" + echo "发布地址: https://gitea.neatcn.com/gouki/lunar-mini/releases/tag/$TAG" + - name: Send success notification if: success() env: @@ -94,9 +134,9 @@ jobs: run: | VERSION="1.0.${GITEA_RUN_NUMBER}" bash .gitea/scripts/notify.sh success \ - "后端构建成功" \ - "版本: v${VERSION}\n产物: /opt/lunar/ci-artifacts/server-deploy-latest.tar.gz\n部署由服务器侧 watcher 自动拉取完成" - + "后端构建发布成功" \ + "版本: v${VERSION}\n产物: releases/tag/server-v${VERSION}\ndoc79 将自动拉取部署" + - name: Send failure notification if: failure() env: @@ -107,7 +147,7 @@ jobs: bash .gitea/scripts/notify.sh failure \ "后端构建失败" \ "请检查 Actions 日志了解失败原因" - + - name: Cleanup if: always() run: |