From af1bbf9c1e4c3f6507a0125d60c1049c2e6240db Mon Sep 17 00:00:00 2001 From: zqlit Date: Tue, 6 Oct 2026 21:11:48 +0800 Subject: [PATCH] =?UTF-8?q?chore(=E8=BF=9C=E7=AB=AF):=20=E5=BD=BB=E5=BA=95?= =?UTF-8?q?=E5=89=94=E9=99=A4=20GitHub=20=E2=80=94=E2=80=94=20=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E5=B7=B2=E9=80=80=E5=BD=B9=E7=9A=84=20GitHub=20Action?= =?UTF-8?q?s=20=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户明确「GitHub 现在整改,不能再作为备份库」,据此把 GitHub 从本项目 所有环节清干净。上一轮(b39dc297)已完成 remote / 推送别名 / 发布链路的 去 GitHub 化,本轮清掉最后一块实体:CI 定义。 - 删除整个 `.github/` 目录(3 个文件、共 1268 行): deploy.yml 1169 行 原主构建发布流水线(已由 .cnb.yml 接管) aliyun-backup.yml 54 行 备份到阿里云 OSS cleanup.yml 45 行 清理 artifacts / cache 已确认无任何脚本、配置或流水线引用它们(全仓只有 .cnb.yml 的注释提过 deploy.yml)。 实现完整保留在 git 历史里,需要时可翻。 同步更新引用它们的注释与文档(否则会指向不存在的文件): - `.cnb.yml` 头部:说明原流水线已删除、要查旧实现请翻 git 历史; 去掉「见 deploy.yml 第 290 行」这类失效指向 - `CNB构建落地方案.md`: §4.0 标题「CNB 主仓 + GitHub 备份」→「+ 辅仓备份」 §pushurl 缺陷论证里「GitHub 那份备份」→「辅仓那份备份」 §敏感文件:「已核实 github.com 返回 Page not found」→ 更新为 账号被标记 + 仓库被 AUP 清空,该威胁面已消失 §历史清理的判断已变:当初不做的唯一顾虑(备份会分叉)已随 GitHub 消失, filter-repo 现在是可行窗口(代价:SHA 全变、自建 Gitea 需重推) - `架构总览.md`:§5.1 标注当前实际只用 `git push origin main`(gitee remote 未建); §6 #1 改写为「GitHub 已彻底退出备份体系」、#5 由「可删」改为「已删除」; 末尾「已退役参考」改为「已删除,可翻 git 历史」 - `README.md`:脚本表补上 `backup-bundle.mjs` / `backup-task.cmd`; `setup-cnb-remotes.sh` 的说明去掉 GitHub --- .cnb.yml | 6 +- .github/workflows/aliyun-backup.yml | 54 -- .github/workflows/cleanup.yml | 45 -- .github/workflows/deploy.yml | 1170 --------------------------- CNB构建落地方案.md | 19 +- README.md | 4 +- 架构总览.md | 10 +- 7 files changed, 24 insertions(+), 1284 deletions(-) delete mode 100644 .github/workflows/aliyun-backup.yml delete mode 100644 .github/workflows/cleanup.yml delete mode 100644 .github/workflows/deploy.yml diff --git a/.cnb.yml b/.cnb.yml index c2d19945..ad1e3ca3 100644 --- a/.cnb.yml +++ b/.cnb.yml @@ -1,11 +1,13 @@ # ===================================================================== -# CNB 流水线 | 由 .github/workflows/deploy.yml 迁移(2026-10-04) +# CNB 流水线 | 由原 GitHub Actions 流水线迁移(2026-10-04) +# ★ 原 .github/workflows/ 已于 2026-10-06 随 GitHub 一并删除 +# (账号被平台标记 + 仓库被按 AUP 清空),需要查旧实现请翻 git 历史 # ===================================================================== # 为什么迁过来反而更简单: # 1. 构建节点在国内(腾讯云)→ 又拍云「境内直传」恢复。 # 原版因 runner 在境外(Gitea 在境外 VPS),直传又拍云恒失败被 if:false # 停用,改用「COS 中转 + 广州机 sync.sh 每分钟轮询」兜底。CNB 让这条 -# 整链失去存在理由(见 deploy.yml 第 290 行那段注释的「复活条件」)。 +# 整链失去存在理由。 # 2. 不需要 artifact 传递:同一 pipeline 的 stages 顺序执行、共享工作目录, # public/ 天然传给后续 stage —— 连带 upload/download-artifact 在 Gitea 上 # 那两个坑(v4 不支持 / v3 finalize 500)一起消失。 diff --git a/.github/workflows/aliyun-backup.yml b/.github/workflows/aliyun-backup.yml deleted file mode 100644 index a0fab4d5..00000000 --- a/.github/workflows/aliyun-backup.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: 备份到阿里云OSS - -on: - schedule: - - cron: "0 0 * * 0" # 每周日的00:00执行一次(UTC) - workflow_dispatch: # 手动触发工作流 - -concurrency: - group: "aliyun-backup" - cancel-in-progress: true - -jobs: - backup: - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: 下载代码 - uses: actions/checkout@v4 - with: - submodules: true - fetch-depth: 0 - - - name: 配置阿里云OSS - uses: manyuanrong/setup-ossutil@v3.0 - with: - endpoint: ${{ secrets.OSS_ENDPOINT }} - access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} - access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} - - - name: 增量上传资源(排除 public 文件夹) - run: | - # 使用 find -print0 和 xargs -0 安全处理特殊字符 - # 排除 public、.git、node_modules 等不必要的文件 - find . \ - -path ./public -prune -o \ - -path ./.git -prune -o \ - -path ./node_modules -prune -o \ - -path ./resources -prune -o \ - -type f -print0 | xargs -0 -I {} ossutil cp "{}" "oss://${{ secrets.OSS_BUCKET }}/{}" --update - - - name: 验证备份完整性 - run: | - # 统计本地文件数(排除目录) - LOCAL_COUNT=$(find . -path ./public -prune -o -path ./.git -prune -o -path ./node_modules -prune -o -path ./resources -prune -o -type f | wc -l) - echo "本地文件数: $LOCAL_COUNT" - - # 统计 OSS 上的文件数 - REMOTE_COUNT=$(ossutil ls oss://${{ secrets.OSS_BUCKET }}/ --recursive | grep -v "Directory:" | wc -l) - echo "OSS 文件数: $REMOTE_COUNT" - - echo "## 备份统计" >> $GITHUB_STEP_SUMMARY - echo "- **本地文件数**: $LOCAL_COUNT" >> $GITHUB_STEP_SUMMARY - echo "- **OSS 文件数**: $REMOTE_COUNT" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml deleted file mode 100644 index 366370c7..00000000 --- a/.github/workflows/cleanup.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Cleanup Artifacts and Cache - -on: - workflow_dispatch: - -jobs: - cleanup: - runs-on: ubuntu-latest - permissions: - actions: write - contents: read - env: - GH_TOKEN: ${{ github.token }} - steps: - - name: Setup GitHub CLI - run: | - gh extension install actions/gh-actions-cache || true - - - name: Delete all artifacts - run: | - echo "🗑️ 正在删除所有 artifacts..." - for artifact in $(gh api repos/${{ github.repository }}/actions/artifacts --paginate | jq -r '.artifacts[].id'); do - echo "删除 artifact: $artifact" - gh api -X DELETE repos/${{ github.repository }}/actions/artifacts/$artifact || true - done - echo "✅ Artifacts 清理完成!" - - - name: Delete all caches - run: | - echo "🗑️ 正在删除所有 caches..." - for cache in $(gh api repos/${{ github.repository }}/actions/caches --paginate | jq -r '.actions_caches[].id'); do - echo "删除 cache: $cache" - gh api -X DELETE repos/${{ github.repository }}/actions/caches/$cache || true - done - echo "✅ Caches 清理完成!" - - - name: Verify cleanup - run: | - echo "📊 当前存储状态:" - echo "Artifacts:" - gh api repos/${{ github.repository }}/actions/artifacts --paginate | jq -r '.artifacts | length' | xargs -I {} echo " - {} 个 artifacts" - echo "Caches:" - gh api repos/${{ github.repository }}/actions/caches --paginate | jq -r '.actions_caches | length' | xargs -I {} echo " - {} 个 caches" - echo "" - echo "✅ 清理完成!" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index ff30344b..00000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,1170 +0,0 @@ -name: Deploy to Production - -on: - push: - branches: - - main - schedule: - - cron: '0 1 * * *' # UTC 01:00 = 北京时间 09:00 - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - TZ: Asia/Shanghai - -jobs: - # ============================================================ - # Job 0: Pre-Flight Checks (前置检查) - # ============================================================ - pre-check: - runs-on: ubuntu-latest - timeout-minutes: 2 - outputs: - can_deploy: ${{ steps.decide.outputs.can_deploy }} - duration: ${{ steps.end-pre-check.outputs.duration }} - - steps: - - name: Start timing - id: start-pre-check - run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Validate repository - id: repo-check - run: | - if [ "${{ github.repository }}" != "zqlit/blog" ]; then - echo "❌ 只能在主仓库执行部署" - echo "can_deploy=false" >> $GITHUB_OUTPUT - exit 1 - fi - echo "✅ 仓库验证通过" - echo "can_deploy=true" >> $GITHUB_OUTPUT - - - name: Validate commit (only for push events) - if: github.event_name == 'push' - id: commit-check - # 注意:commit message 是用户可控的多行文本,必须经 env 传入。 - # 直接写 ${{ github.event.head_commit.message }} 会把内容展开进脚本, - # 多行信息或含 $ / ` 的字符会导致 "bad substitution" 甚至命令注入。 - env: - COMMIT_AUTHOR: ${{ github.event.head_commit.author.name }} - COMMIT_MESSAGE: ${{ github.event.head_commit.message }} - run: | - echo "📝 提交作者: $COMMIT_AUTHOR" - echo "📄 提交信息: $COMMIT_MESSAGE" - - if echo "$COMMIT_MESSAGE" | grep -qE "^(feat|fix|chore|docs|style|refactor|test): "; then - echo "✅ 提交信息符合规范" - else - echo "⚠️ 提交信息格式建议: feat/fix/chore/docs/style/refactor/test: description" - fi - - - name: Decide whether to deploy - id: decide - run: | - # 基础决策来自仓库校验 - CAN_DEPLOY="${{ steps.repo-check.outputs.can_deploy }}" - echo "📌 仓库校验结果: can_deploy=${CAN_DEPLOY}" - - # 定时触发:若 1 小时内无新提交则跳过,避免重复构建 - if [ "${{ github.event_name }}" = "schedule" ]; then - LAST_COMMIT_TIME=$(git log -1 --format=%ct) - CURRENT_TIME=$(date +%s) - DIFF_HOURS=$(( (CURRENT_TIME - LAST_COMMIT_TIME) / 3600 )) - - echo "⏰ 最后一次提交时间: $(date -d @$LAST_COMMIT_TIME)" - echo "⏰ 当前时间: $(date)" - echo "⏰ 距离上次提交: ${DIFF_HOURS} 小时" - - if [ "$DIFF_HOURS" -ge 1 ]; then - echo "⏭️ 超过 1 小时没有新提交,跳过定时构建" - CAN_DEPLOY="false" - else - echo "✅ 最近有提交,继续构建" - fi - fi - - echo "can_deploy=${CAN_DEPLOY}" >> $GITHUB_OUTPUT - echo "## 🚦 部署决策: can_deploy=${CAN_DEPLOY}" >> $GITHUB_STEP_SUMMARY - - - name: Summary - run: | - echo "## 🔍 前置检查完成" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- **仓库**: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY - echo "- **分支**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY - echo "- **触发方式**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY - echo "- **Commit SHA**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY - - - name: End timing - id: end-pre-check - run: | - end_time=$(date +%s) - start_time=${{ steps.start-pre-check.outputs.start_time }} - duration=$((end_time - start_time)) - echo "duration=${duration}" >> $GITHUB_OUTPUT - echo "## ⏱️ Pre-check 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY - - # ============================================================ - # Job 1: Build (构建) - # ============================================================ - build: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: write - outputs: - build_hash: ${{ steps.hash.outputs.build_hash }} - duration: ${{ steps.end-build.outputs.duration }} - file_count: ${{ steps.stats.outputs.file_count }} - total_size: ${{ steps.stats.outputs.total_size }} - # Upload to UpYun 的真实三态:success / failure / skipped(未执行)。 - # 该步骤标了 continue-on-error,conclusion 恒为 success,不能用来判断成败。 - # 这里刻意不用 steps..outcome,而是让步骤自己在退出时写 GITHUB_OUTPUT, - # 只依赖最通用的 output 传递机制,GitHub / Gitea 两边都稳。 - upyun_status: ${{ steps.upload.outputs.upyun_status || 'skipped' }} - upyun_duration: ${{ steps.upload.outputs.duration }} - needs: pre-check - if: needs.pre-check.outputs.can_deploy == 'true' - - steps: - - name: Start timing - id: start-build - run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - # Hugo 的 lastmod 用了 :git,必须保留完整提交历史(commit/tree), - # 但历史版本的文件内容(blob,多为图片)不需要。 - # partial clone 只按需拉取当前工作区的 blob,实测本仓库可省约 200MB 传输。 - filter: blob:none - - - name: Setup Hugo - uses: peaceiris/actions-hugo@v2 - with: - hugo-version: '0.128.2' - extended: true - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '22' - # Gitea 的 runner 缓存服务与 job 容器跨 bridge 网络不可达, - # 每次 restore/save 各死等约 4 分半后 ETIMEDOUT(纯浪费约 9 分钟), - # 因此仅在 GitHub 上启用缓存;Gitea 上传空值让 setup-node 跳过缓存。 - cache: ${{ github.server_url == 'https://github.com' && 'npm' || '' }} - - - name: Install Node.js dependencies - run: npm ci --no-audit --no-fund - - - name: Cache Hugo resources - # 同 Setup Node:Gitea 环境缓存服务不可达,跳过以免每次白等 - if: github.server_url == 'https://github.com' - uses: actions/cache@v4 - id: hugo-cache - with: - path: | - resources/_gen - /home/runner/.cache/hugo_cache - key: ${{ runner.os }}-hugo-${{ hashFiles('go.sum', 'package-lock.json', 'hugo.toml', 'config/_default/*.toml') }} - restore-keys: | - ${{ runner.os }}-hugo- - - - name: Run Pre-Build Scripts - shell: bash - run: python3 scripts/add_draft_to_hidden.py - - - name: Optimize Images - continue-on-error: true - timeout-minutes: 5 - run: node scripts/optimize_images.js - - - name: Commit Optimized Images - continue-on-error: true - run: | - git config --global user.name "GitHub Actions" - git config --global user.email "actions@github.com" - git add content/posts static - if ! git diff --staged --quiet; then - git commit -m "chore: auto-optimize images [skip ci]" - else - echo "No changes to commit" - fi - - - name: Build Hugo site - timeout-minutes: 10 - run: rm -rf public resources/_gen && hugo --minify --gc - - - name: Generate build hash - id: hash - run: | - BUILD_HASH=$(find public -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum | awk '{print $1}') - echo "build_hash=${BUILD_HASH}" >> $GITHUB_OUTPUT - # 写两份: - # .build_hash —— 历史沿用(也用于 upx put 到又拍云 /__build_hash) - # build_hash.txt —— 非隐藏。upload-artifact(v3/v4)对「以 . 开头的隐藏 - # 文件」处理不一致,可能整个丢掉,非隐藏文件才稳。 - # 中转机优先读 .build_hash,读不到退到 build_hash.txt,都没有才本地复算。 - # 注意:本步的 find 在写这两个文件之前执行,所以 hash 不含它们自身。 - echo "$BUILD_HASH" > public/.build_hash - echo "$BUILD_HASH" > public/build_hash.txt - - - name: Build statistics - id: stats - run: | - FILE_COUNT=$(find public -type f | wc -l) - TOTAL_SIZE=$(du -sh public | awk '{print $1}') - echo "file_count=${FILE_COUNT}" >> $GITHUB_OUTPUT - echo "total_size=${TOTAL_SIZE}" >> $GITHUB_OUTPUT - - - name: Pack build output (single tarball) - id: pack - # ★ 必须打成「单文件」再上传,不能让 upload-artifact 直接传 public/ 目录。 - # - # run #56 实测(2026-10-04):archive v3 收目录时会「逐文件」上传 —— - # 3216 个文件、376MB 全部传完(日志可见 Processed file #3216 / 100%), - # 但紧接着的 finalize 调用被 Gitea 拒绝: - # Finalize artifact upload - Attempt 1..5 of 5 failed: Request timeout - # ::error::Finalize artifact upload failed: Artifact service responded with 500 - # 于是整轮 build 白跑约 30 分钟。文件一多,Gitea 侧的合并/登记就扛不住。 - # 打包成 1 个文件后,上传是 1 次 PUT、finalize 是常数级开销。 - # 顺带:tar.zst 也正是原 COS 链路用过的形态,中转机侧解包逻辑可直接复用。 - run: | - if command -v zstd >/dev/null 2>&1; then - tar -c public | zstd -3 -T0 -o public.tar.zst - F=public.tar.zst - else - # 兜底:万一 runner 镜像没带 zstd(gzip 一定在) - tar -czf public.tar.gz public - F=public.tar.gz - fi - echo "file=$F" >> $GITHUB_OUTPUT - ls -lh "$F" - - - name: Upload build artifact - # 2026-10-04 起产物不再上传腾讯云 COS,改用 Gitea artifact 传递: - # - deploy-edgeone 用 actions/download-artifact 直接拿(同 runner,零跨境) - # - 国内中转机用 Gitea API 下载 artifact(替代原先的「从 COS 下载」) - # - # ★ 必须用 v3,不能用 v4:实测 v4 在 Gitea 上直接失败 —— - # "@actions/artifact v2.0.0+, upload-artifact@v4+ and download-artifact@v4+ - # are not currently supported on GHES."(Gitea 被识别为 GHES,未实现新版 - # artifact 服务)。v3 走的是 Gitea 已实现的旧 artifact API。 - # ★ path 只指向上一步打好的那 1 个文件(见上方 finalize 500 的教训)。 - uses: actions/upload-artifact@v3 - with: - name: hugo-public - path: ${{ steps.pack.outputs.file }} - retention-days: 7 - - - name: Report deploy status (built) - # 上报「构建完成、产物已进 artifact」到 api.200181.xyz 的部署状态接口, - # 中转机同步完成后会上报 phase=synced —— 打开 - # https://api.200181.xyz/api/deploy-status 即可看到国内线路是否同步完。 - # 需要 Gitea 仓库 Secrets 里配置 RSS_API_TOKEN = rss-robot-cf 的站点口令; - # 未配置时静默跳过上报(不影响部署)。 - continue-on-error: true - env: - REPORT_TOKEN: ${{ secrets.RSS_API_TOKEN }} - run: | - if [ -z "$REPORT_TOKEN" ]; then - echo "ℹ️ 未配置 RSS_API_TOKEN,跳过部署状态上报" - exit 0 - fi - BUILD_HASH="${{ steps.hash.outputs.build_hash }}" - curl -s --max-time 20 -X POST \ - "https://api.200181.xyz/api/deploy-status?token=${REPORT_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "{\"hash\":\"${BUILD_HASH}\",\"phase\":\"built\"}" \ - && echo "✅ 已上报 built: ${BUILD_HASH:0:12}" - - - name: Upload to UpYun - # ┌─────────────────────────────────────────────────────────────────┐ - # │ 2026-10-03 起永久跳过海外直传:本 runner 在境外,对 UpYun v0 接口 │ - # │ 无论 -w 10/--strong 还是 -w 3 均为 70 秒左右 EOF,3 轮重试全败, │ - # │ 每次部署白耗 5 分钟(2026-10-03 build #25 日志实锤)。 │ - # │ 国内线路由腾讯云广州中转机兜底(1Panel 计划任务「又拍云同步(国内 │ - # │ 中转)」每分钟比对 COS build hash → 下载 355M → upx sync → │ - # │ 又拍云 purge → 多吉云刷新,实测 5 分钟内完成且它自己会刷 CDN)。 │ - # │ 将来若把 runner 挪到国内,把下面的 if: false 删掉即可恢复。 │ - # └─────────────────────────────────────────────────────────────────┘ - if: false - timeout-minutes: 30 - env: - UPYUN_SERVICE: ${{ secrets.UPYUN_BUCKET }} - UPYUN_OPERATOR: ${{ secrets.UPYUN_OPERATOR }} - UPYUN_PASSWORD: ${{ secrets.UPYUN_PASSWORD }} - id: upload - # 直传失败不阻断整条流水线 —— 国内线路已由腾讯云广州的中转机兜底 - # (1Panel 计划任务「又拍云同步(国内中转)」每分钟比对 COS 上的 build hash, - # 有变化才增量推送)。但失败必须被如实上报,绝不能再伪装成成功。 - continue-on-error: true - run: | - echo "📤 上传构建产物到 UpYun..." - START_TS=$(date +%s) - # 无论成功、失败还是中途退出,都在退出时如实上报结果与耗时。 - # 这是"不再假绿"的核心:过去无论传没传上去,通知都显示成功。 - on_exit() { - rc=$? - if [ "$rc" -eq 0 ]; then - echo "upyun_status=success" >> $GITHUB_OUTPUT - else - echo "upyun_status=failure" >> $GITHUB_OUTPUT - fi - echo "duration=$(($(date +%s) - START_TS))" >> $GITHUB_OUTPUT - } - trap on_exit EXIT - - UPX_VERSION="0.4.9" - UPX_BIN="/tmp/upyun-upx" - - # 主源用 GitHub Releases(实测 0.5s 稳定), - # 官方 collection.b0.upaiyun.com 实测 2/3 概率整站无响应,仅作兜底 - UPX_URLS=( - "https://github.com/upyun/upx/releases/download/v${UPX_VERSION}/upx_${UPX_VERSION}_linux_amd64.tar.gz" - "https://collection.b0.upaiyun.com/softwares/upx/upx_${UPX_VERSION}_linux_amd64.tar.gz" - ) - - if [ ! -x "$UPX_BIN" ]; then - ok=false - for U in "${UPX_URLS[@]}"; do - echo "⬇️ 下载 upx: $U" - if curl -fsSL --connect-timeout 10 --max-time 60 --retry 2 --retry-delay 2 \ - -o /tmp/upx.tar.gz "$U"; then - if tar -xzf /tmp/upx.tar.gz -C /tmp && [ -f /tmp/upx ]; then - mv /tmp/upx "$UPX_BIN" && chmod +x "$UPX_BIN" && ok=true - echo "✅ upx 就绪: $("$UPX_BIN" --version 2>&1 | head -1)" - break - fi - fi - echo "⚠️ 该源失败,尝试下一个..." - done - if [ "$ok" != "true" ]; then - echo "❌ 所有 upx 下载源均失败,跳过 UpYun 上传" - exit 1 - fi - fi - - for i in 1 2 3 4 5; do - echo "🔐 UpYun 登录 (尝试 $i/5)..." - if timeout 30 "$UPX_BIN" login "$UPYUN_SERVICE" "$UPYUN_OPERATOR" "$UPYUN_PASSWORD" 2>&1; then - echo "✅ 登录成功" - break - else - echo "⚠️ 登录失败,等待 10 秒后重试..." - sleep 10 - fi - done - - echo "🔄 开始上传到 UpYun..." - # ⚠️ 参数是踩过坑调出来的,别改回 -w 10 / --strong: - # 本 runner 在海外,UpYun 的 v0 接口对并发敏感。2026-10-02 连续两次失败, - # 现象是一批目录的 mkdir 全部 EOF: - # sync .../public/20210212 → /20210212 FAIL mkdir: Post "https://v0.api.upyun.com/...": EOF - # -w 10 + --strong(每文件强校验)把请求量放大到 UpYun 扛不住的量级。 - # 现在:并发 3、去掉 --strong(默认按 mtime/size 增量比对)、 - # 单次超时放宽到 25 分钟、整轮重试 3 次(sync 可重复执行,已传的会跳过)。 - UPYUN_OK=false - for attempt in 1 2 3; do - echo "⬆️ 上传尝试 $attempt/3(并发 3,无 --strong)" - if timeout 1500 "$UPX_BIN" sync public/ / -w 3 --delete; then - UPYUN_OK=true - break - fi - echo "⚠️ 第 $attempt/3 次失败(多为 EOF 瞬断),20 秒后重试" - sleep 20 - done - if [ "$UPYUN_OK" != "true" ]; then - echo "❌ UpYun 连续 3 次上传失败,国内源站仍是旧内容" - exit 1 - fi - timeout 60 "$UPX_BIN" put public/.build_hash /__build_hash - echo "✅ UpYun 上传完成" - - - name: Push Image Optimizations - continue-on-error: true - run: | - git fetch origin main - git stash push -m "temp stash for rebase" || true - git rebase origin/main || true - git stash pop || true - if git log --oneline -1 | grep -q "\[skip ci\]"; then - git push origin HEAD:main - else - echo "No auto-commits to push" - fi - - - name: End timing - id: end-build - run: | - end_time=$(date +%s) - start_time=${{ steps.start-build.outputs.start_time }} - duration=$((end_time - start_time)) - echo "duration=${duration}" >> $GITHUB_OUTPUT - - echo "## ⏱️ Build 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 📊 构建统计" >> $GITHUB_STEP_SUMMARY - echo "- **文件总数**: ${{ steps.stats.outputs.file_count }}" >> $GITHUB_STEP_SUMMARY - echo "- **总大小**: ${{ steps.stats.outputs.total_size }}" >> $GITHUB_STEP_SUMMARY - echo "- **构建哈希**: ${{ steps.hash.outputs.build_hash }}" >> $GITHUB_STEP_SUMMARY - if [ "${{ github.server_url }}" = "https://github.com" ]; then - echo "- **Hugo 缓存**: ${{ steps.hugo-cache.outputs.cache-hit == 'true' && '✅ 命中' || '🔄 未命中' }}" >> $GITHUB_STEP_SUMMARY - else - echo "- **Hugo 缓存**: ⏭️ 已跳过 (Gitea 缓存服务跨网段不可达)" >> $GITHUB_STEP_SUMMARY - fi - - # ============================================================ - # Job 3: Deploy to EdgeOne Pages (并行) - # ============================================================ - deploy-edgeone: - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - needs: build - outputs: - deployed: ${{ steps.check.outputs.deployed }} - duration: ${{ steps.end-edgeone.outputs.duration }} - upload_duration: ${{ steps.end-edgeone-upload.outputs.duration }} - - steps: - - name: Start timing - id: start-edgeone - run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - - - name: Check if deployment is needed - id: check - run: | - # 2026-10-04 起去掉「比对 last_hash 跳过未变化部署」的优化: - # last_hash 原本存 COS,砍 COS 后无处持久化(Gitea 缓存服务跨网段不可达)。 - # 且 push 才触发构建,本就意味着有内容变化,直接部署即可。 - echo "🔄 构建完成,部署到 EdgeOne" - echo "deployed=true" >> $GITHUB_OUTPUT - - - name: Download build artifact - id: download-files - # 从 Gitea artifact 下载(build job 上传的,同 runner 零跨境、零费用) - # ★ 必须与上传端同为 v3:v4 在 Gitea(被识别为 GHES)上不受支持。 - uses: actions/download-artifact@v3 - with: - name: hugo-public - path: . - - - name: Unpack build artifact - # artifact 里只有 1 个打包文件(public.tar.zst / .tar.gz),解出来就是 - # public/index.html 在根。不再有 upload-artifact「目录语义」导致的 - # 「多一层 public/」问题,产物结构完全确定。 - run: | - set -o pipefail - T=$(find . -maxdepth 1 -type f \( -name 'public.tar.zst' -o -name 'public.tar.gz' \) -print -quit) - if [ -z "$T" ]; then - echo "❌ artifact 里没找到 public.tar.zst / public.tar.gz" - ls -la - exit 1 - fi - echo "ℹ️ 解包 $T ($(du -h "$T" | cut -f1))" - rm -rf public - case "$T" in - *.zst) zstd -dc "$T" | tar -x ;; - *) tar -xzf "$T" ;; - esac - if [ ! -f public/index.html ]; then - echo "❌ 产物结构异常:public/index.html 不存在" - find . -maxdepth 2 | head -20 - exit 1 - fi - echo "✅ public/index.html 就位,文件数 $(find public -type f | wc -l)" - - - name: Start upload timing - id: start-edgeone-upload - run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - - - name: Deploy to EdgeOne Pages - env: - EDGEONE_API_TOKEN: ${{ secrets.EDGEONE_API_TOKEN }} - run: | - # 内联 scripts/deploy_edgeone.sh —— 本步骤只需一个部署命令, - # 原先为此 checkout 整个仓库要花约 81 秒,不值得。 - if [ -z "$EDGEONE_API_TOKEN" ]; then - echo "Error: EDGEONE_API_TOKEN is not set." - exit 1 - fi - echo "Deploying to EdgeOne Pages..." - echo "Project: hugo-blog | Directory: ./public | Area: overseas" - # 国际站必须加 --area overseas - npx edgeone pages deploy ./public -n hugo-blog -t "$EDGEONE_API_TOKEN" --area overseas - - - name: End upload timing - id: end-edgeone-upload - run: | - end_time=$(date +%s) - start_time=${{ steps.start-edgeone-upload.outputs.start_time }} - duration=$((end_time - start_time)) - echo "duration=${duration}" >> $GITHUB_OUTPUT - - - name: End timing - id: end-edgeone - run: | - end_time=$(date +%s) - start_time=${{ steps.start-edgeone.outputs.start_time }} - duration=$((end_time - start_time)) - echo "duration=${duration}" >> $GITHUB_OUTPUT - - echo "## ⏱️ Deploy EdgeOne 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 📊 部署统计" >> $GITHUB_STEP_SUMMARY - echo "- **上传部署**: ${duration}秒" >> $GITHUB_STEP_SUMMARY - echo "- **文件总数**: ${{ needs.build.outputs.file_count }}" >> $GITHUB_STEP_SUMMARY - - # ============================================================ - # 注意:这里原先有一个名为 deploy-upyun 的 job —— 它是纯空壳: - # 零上传动作,却无条件打印「UpYun 已同步最新内容」,且它的输出被 - # Telegram / 飞书 / 邮件通知当成"UpYun 部署成功"的依据。 - # 因它持续掩盖真实故障(7/25 起国内线路停更两个多月),已于 2026-10-01 删除。 - # 又拍云的真实状态现在来自 build job 的 upyun_status 输出。 - # ============================================================ - - # ============================================================ - # Job 5: CDN Refresh & Notifications (after all deployments) - # ============================================================ - finalize: - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - contents: write - needs: [pre-check, build, deploy-edgeone] - if: always() && needs.build.result == 'success' && needs.deploy-edgeone.result == 'success' - - steps: - - name: Start timing - id: start-finalize - run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '22' - # 本 job 不再安装 npm 依赖(CDN 刷新脚本只用 node 内置模块), - # 因此不启用 npm 缓存 —— 也免得在没有 package-lock.json 时算缓存 key 出错。 - - - name: Determine deployment status - id: deploy-status - run: | - EDGEONE_DEPLOYED="${{ needs.deploy-edgeone.outputs.deployed }}" - UPYUN_STATUS="${{ needs.build.outputs.upyun_status }}" - - echo "EdgeOne deployed: $EDGEONE_DEPLOYED" - echo "UpYun status: $UPYUN_STATUS" - - if [ "$EDGEONE_DEPLOYED" = "true" ] || [ "$UPYUN_STATUS" = "success" ]; then - echo "any_deployed=true" >> $GITHUB_OUTPUT - else - echo "any_deployed=false" >> $GITHUB_OUTPUT - fi - - # 多吉云 CDN 只服务国内线路,回源是又拍云。 - # 2026-10-03 起海外直传 UpYun 已永久跳过,国内线路由中转机同步, - # sync.sh 同步完成后会自己刷多吉云 —— 这里保持 false 即可。 - if [ "$UPYUN_STATUS" = "success" ]; then - echo "cdn_refresh=true" >> $GITHUB_OUTPUT - else - echo "cdn_refresh=false" >> $GITHUB_OUTPUT - echo "ℹ️ 国内线路由中转机同步(sync.sh 完成后会自行刷新多吉云),CI 侧跳过" - fi - - - name: Refresh DogeCloud CDN - if: steps.deploy-status.outputs.cdn_refresh == 'true' - env: - DOGECLOUD_ACCESS_KEY: ${{ secrets.DOGECLOUD_ACCESS_KEY }} - DOGECLOUD_SECRET_KEY: ${{ secrets.DOGECLOUD_SECRET_KEY }} - CDN_URL_LIST: ${{ secrets.CDN_URL_LIST }} - run: | - # 内联 scripts/refresh_cdn.js(修改该文件时需同步这里)。 - # 该脚本零外部依赖 —— 仅用 node 内置的 crypto 与 fetch —— - # 因此不必 checkout 整个仓库、也不必 npm ci(原先这两步合计约 211 秒)。 - cat > /tmp/refresh_cdn.js <<'REFRESH_CDN_EOF' - const crypto = require("crypto"); - - async function dogecloudApi(apiPath, data = {}) { - const accessKey = process.env.DOGECLOUD_ACCESS_KEY; - const secretKey = process.env.DOGECLOUD_SECRET_KEY; - - if (!accessKey || !secretKey) { - console.error("Error: DOGECLOUD_ACCESS_KEY or DOGECLOUD_SECRET_KEY not set."); - process.exit(1); - } - - const body = JSON.stringify(data); - const signStr = apiPath + "\n" + body; - const sign = crypto.createHmac("sha1", secretKey).update(signStr).digest("hex"); - const authorization = `TOKEN ${accessKey}:${sign}`; - - const res = await fetch(`https://api.dogecloud.com${apiPath}`, { - method: "POST", - headers: { - Authorization: authorization, - "Content-Type": "application/json", - }, - body, - }); - return res.json(); - } - - async function main() { - const cdnUrlListStr = process.env.CDN_URL_LIST; - if (!cdnUrlListStr) { - console.log("Warning: CDN_URL_LIST not found or empty."); - return; - } - - const urlList = cdnUrlListStr - .replace(/\n/g, ",") - .split(",") - .map((u) => u.trim()) - .filter(Boolean); - - if (urlList.length === 0) { - console.log("No URLs to refresh."); - return; - } - - console.log(`Refreshing ${urlList.length} URLs...`); - - const result = await dogecloudApi("/cdn/refresh/add.json", { - rtype: "path", - urls: JSON.stringify(urlList), - }); - - if (result && result.code === 200) { - console.log(`Refresh task submitted. Task ID: ${result.data.task_id}`); - } else { - console.error(`Failed to submit refresh task. Response: ${JSON.stringify(result)}`); - process.exit(1); - } - } - - main(); - REFRESH_CDN_EOF - node /tmp/refresh_cdn.js - - - name: Send Telegram notification with detailed timing - env: - TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} - TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} - run: | - if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then - echo "⚠️ Telegram secrets 未配置,跳过通知" - exit 0 - fi - - PRECHECK_DUR="${{ needs.pre-check.outputs.duration }}" - BUILD_DUR="${{ needs.build.outputs.duration }}" - EDGEONE_DUR="${{ needs.deploy-edgeone.outputs.duration }}" - EDGEONE_DEPLOYED="${{ needs.deploy-edgeone.outputs.deployed }}" - UPYUN_DUR="${{ needs.build.outputs.upyun_duration }}" - UPYUN_STATUS_RAW="${{ needs.build.outputs.upyun_status }}" - FILE_COUNT="${{ needs.build.outputs.file_count }}" - TOTAL_SIZE="${{ needs.build.outputs.total_size }}" - - EDGEONE_DETAIL="" - if [ "$EDGEONE_DEPLOYED" = "true" ]; then - EDGEONE_UPLOAD="${{ needs.deploy-edgeone.outputs.upload_duration }}" - EDGEONE_DETAIL=$(printf "\n 📤 上传: %ss" "$EDGEONE_UPLOAD") - fi - - # 三态映射:直传结果如实呈现,不再有"文件已在 Build 阶段同步"这种没验证过的说辞 - case "$UPYUN_STATUS_RAW" in - success) UPYUN_TEXT="✅ 已直传";; - failure) UPYUN_TEXT="❌ 直传失败(国内中转机兜底)";; - skipped) UPYUN_TEXT="⏭️ 跳过(无变化)";; - *) UPYUN_TEXT="❔ 未执行";; - esac - if [ -n "$UPYUN_DUR" ]; then UPYUN_DUR_TEXT="${UPYUN_DUR}s"; else UPYUN_DUR_TEXT="—"; fi - - if [ "${{ steps.deploy-status.outputs.any_deployed }}" = "true" ]; then - STATUS_ICON="🎉" - STATUS_TEXT="部署成功" - else - STATUS_ICON="⏭️" - STATUS_TEXT="无变化,跳过部署" - fi - - EDGEONE_STATUS=$([ "$EDGEONE_DEPLOYED" = "true" ] && echo "✅ 已部署" || echo "⏭️ 跳过") - - CURRENT_TIME=$(date +'%Y-%m-%d %H:%M:%S') - - MESSAGE="$STATUS_ICON *$STATUS_TEXT*" - MESSAGE="$MESSAGE"$'\n\n' - MESSAGE="$MESSAGE""📦 *项目*: \`${{ github.repository }}\`"$'\n' - MESSAGE="$MESSAGE""🌿 *分支*: \`${{ github.ref_name }}\`"$'\n' - MESSAGE="$MESSAGE""🔗 *Commit*: \`${{ github.sha }}\`"$'\n' - MESSAGE="$MESSAGE""⏰ *时间*: $CURRENT_TIME"$'\n' - MESSAGE="$MESSAGE""🚀 *触发方式*: ${{ github.event_name }}"$'\n\n' - MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE="$MESSAGE""📊 *构建统计*"$'\n' - MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE="$MESSAGE""📁 文件总数: $FILE_COUNT"$'\n' - MESSAGE="$MESSAGE""💾 总大小: $TOTAL_SIZE"$'\n\n' - MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE="$MESSAGE""⏱️ *耗时统计*"$'\n' - MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE="$MESSAGE""🔍 Pre-check: ${PRECHECK_DUR}s"$'\n' - MESSAGE="$MESSAGE""🏗️ Build: ${BUILD_DUR}s"$'\n' - MESSAGE="$MESSAGE""🌐 EdgeOne: ${EDGEONE_DUR}s$EDGEONE_DETAIL"$'\n' - MESSAGE="$MESSAGE""☁️ UpYun 直传: ${UPYUN_DUR_TEXT}"$'\n\n' - MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE="$MESSAGE""📌 *部署状态*"$'\n' - MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE="$MESSAGE""🌐 EdgeOne Pages: $EDGEONE_STATUS"$'\n' - MESSAGE="$MESSAGE""☁️ UpYun: $UPYUN_TEXT"$'\n\n' - if [ "${{ github.server_url }}" = "https://github.com" ]; then - RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - else - RUN_URL="http://23.254.236.47:3001/${{ github.repository }}/actions/runs/${{ github.run_id }}" - fi - MESSAGE="$MESSAGE""🔗 [查看详情]($RUN_URL)" - - curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ - -d chat_id="${TELEGRAM_CHAT_ID}" \ - -d text="$MESSAGE" \ - -d parse_mode="Markdown" \ - -d disable_web_page_preview=true - - - name: Send Feishu notification with detailed timing - env: - FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} - run: | - if [ -z "$FEISHU_WEBHOOK_URL" ]; then - echo "⚠️ 飞书 Webhook URL 未配置,跳过通知" - exit 0 - fi - - PRECHECK_DUR="${{ needs.pre-check.outputs.duration }}" - BUILD_DUR="${{ needs.build.outputs.duration }}" - EDGEONE_DUR="${{ needs.deploy-edgeone.outputs.duration }}" - EDGEONE_DEPLOYED="${{ needs.deploy-edgeone.outputs.deployed }}" - UPYUN_DUR="${{ needs.build.outputs.upyun_duration }}" - UPYUN_STATUS_RAW="${{ needs.build.outputs.upyun_status }}" - FILE_COUNT="${{ needs.build.outputs.file_count }}" - TOTAL_SIZE="${{ needs.build.outputs.total_size }}" - - EDGEONE_DETAIL="" - if [ "$EDGEONE_DEPLOYED" = "true" ]; then - EDGEONE_UPLOAD="${{ needs.deploy-edgeone.outputs.upload_duration }}" - EDGEONE_DETAIL=$(printf "\n 📤 上传: %ss" "$EDGEONE_UPLOAD") - fi - - # 三态映射:直传结果如实呈现,不再有"文件已在 Build 阶段同步"这种没验证过的说辞 - case "$UPYUN_STATUS_RAW" in - success) UPYUN_TEXT="✅ 已直传";; - failure) UPYUN_TEXT="❌ 直传失败(国内中转机兜底)";; - skipped) UPYUN_TEXT="⏭️ 跳过(无变化)";; - *) UPYUN_TEXT="❔ 未执行";; - esac - if [ -n "$UPYUN_DUR" ]; then UPYUN_DUR_TEXT="${UPYUN_DUR}s"; else UPYUN_DUR_TEXT="—"; fi - - if [ "${{ steps.deploy-status.outputs.any_deployed }}" = "true" ]; then - STATUS_ICON="🎉" - STATUS_TEXT="部署成功" - else - STATUS_ICON="⏭️" - STATUS_TEXT="无变化,跳过部署" - fi - - EDGEONE_STATUS=$([ "$EDGEONE_DEPLOYED" = "true" ] && echo "✅ 已部署" || echo "⏭️ 跳过") - - CURRENT_TIME=$(date +'%Y-%m-%d %H:%M:%S') - - MESSAGE_TEXT="$STATUS_ICON $STATUS_TEXT" - MESSAGE_TEXT="$MESSAGE_TEXT"$'\n\n' - MESSAGE_TEXT="$MESSAGE_TEXT""📦 项目: ${{ github.repository }}"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""🌿 分支: ${{ github.ref_name }}"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""🔗 Commit: ${{ github.sha }}"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""⏰ 时间: $CURRENT_TIME"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""🚀 触发方式: ${{ github.event_name }}"$'\n\n' - MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""📊 构建统计"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""📁 文件总数: $FILE_COUNT"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""💾 总大小: $TOTAL_SIZE"$'\n\n' - MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""⏱️ 耗时统计"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""🔍 Pre-check: ${PRECHECK_DUR}s"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""🏗️ Build: ${BUILD_DUR}s"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""🌐 EdgeOne: ${EDGEONE_DUR}s$EDGEONE_DETAIL"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""☁️ UpYun 直传: ${UPYUN_DUR_TEXT}"$'\n\n' - MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""📌 部署状态"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""🌐 EdgeOne Pages: $EDGEONE_STATUS"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""☁️ UpYun: $UPYUN_TEXT"$'\n\n' - if [ "${{ github.server_url }}" = "https://github.com" ]; then - RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - else - RUN_URL="http://23.254.236.47:3001/${{ github.repository }}/actions/runs/${{ github.run_id }}" - fi - MESSAGE_TEXT="$MESSAGE_TEXT""🔗 查看详情: $RUN_URL" - - echo "📤 发送飞书通知..." - echo "Message length: ${#MESSAGE_TEXT}" - - RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST "$FEISHU_WEBHOOK_URL" \ - -H "Content-Type: application/json" \ - -d "$(jq -n --arg text "$MESSAGE_TEXT" '{"msg_type":"text","content":{"text":$text}}')") - - echo "飞书响应: $RESPONSE" - - - name: Get current date (CN) - if: steps.deploy-status.outputs.any_deployed == 'true' - id: date - run: echo "date=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT - - - name: Send email notification with detailed timing - if: steps.deploy-status.outputs.any_deployed == 'true' - uses: dawidd6/action-send-mail@v3 - with: - server_address: smtp.qq.com - server_port: 465 - secure: true - username: ${{ secrets.MAIL_USERNAME }} - password: ${{ secrets.MAIL_PASSWORD }} - subject: "🎉 部署成功 - ${{ github.repository }} - ${{ steps.date.outputs.date }}" - to: ${{ secrets.MAIL_USERNAME }} - from: GitHub Actions <${{ github.event_name }}> - body: | - 🎉 部署成功 🎉 - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - 📦 项目信息 - ────────────────────────────────── - 项目: ${{ github.repository }} - 分支: ${{ github.ref_name }} - Commit: ${{ github.sha }} - ⏰ 时间: ${{ steps.date.outputs.date }} - 🚀 触发方式: ${{ github.event_name }} - - 📊 构建统计 - ────────────────────────────────── - 📁 文件总数: ${{ needs.build.outputs.file_count }} - 💾 总大小: ${{ needs.build.outputs.total_size }} - - ⏱️ 耗时统计 - ────────────────────────────────── - 🔍 Pre-check: ${{ needs.pre-check.outputs.duration }}s - 🏗️ Build: ${{ needs.build.outputs.duration }}s - - 🌐 EdgeOne Pages: ${{ needs.deploy-edgeone.outputs.duration }}s - 📤 上传部署: ${{ needs.deploy-edgeone.outputs.upload_duration }}s - - ☁️ UpYun 直传: ${{ needs.build.outputs.upyun_status == 'success' && '✅ 成功' || (needs.build.outputs.upyun_status == 'skipped' && '⏭️ 跳过(无变化)' || '❌ 失败(国内中转机兜底)') }} - - 📌 部署状态 - ────────────────────────────────── - 🌐 EdgeOne Pages: ${{ needs.deploy-edgeone.outputs.deployed == 'true' && '✅ 已部署' || '⏭️ 跳过(无变化)' }} - ☁️ UpYun: ${{ needs.build.outputs.upyun_status == 'success' && '✅ 已直传' || (needs.build.outputs.upyun_status == 'skipped' && '⏭️ 跳过(无变化)' || '❌ 直传失败(中转机兜底)') }} - - 🔗 查看详情 - ────────────────────────────────── - ${{ github.server_url == 'https://github.com' && 'https://github.com/zqlit/blog' || 'http://23.254.236.47:3001/zqlit/blog' }}/actions/runs/${{ github.run_id }} - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - name: Generate timing summary - id: timing-summary - run: | - end_time=$(date +%s) - start_time=${{ steps.start-finalize.outputs.start_time }} - finalize_duration=$((end_time - start_time)) - - PRECHECK_DUR="${{ needs.pre-check.outputs.duration }}" - BUILD_DUR="${{ needs.build.outputs.duration }}" - EDGEONE_DUR="${{ needs.deploy-edgeone.outputs.duration }}" - EDGEONE_DEPLOYED="${{ needs.deploy-edgeone.outputs.deployed }}" - UPYUN_DUR="${{ needs.build.outputs.upyun_duration }}" - - if [ "$EDGEONE_DEPLOYED" = "true" ]; then - EDGEONE_UPLOAD="${{ needs.deploy-edgeone.outputs.upload_duration }}" - else - EDGEONE_UPLOAD="0" - fi - - UPYUN_DUR_CELL="${UPYUN_DUR}s"; [ -z "$UPYUN_DUR" ] && UPYUN_DUR_CELL="—" - - echo "## 📊 部署报告" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 基本信息" >> $GITHUB_STEP_SUMMARY - echo "- **项目**: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY - echo "- **分支**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY - echo "- **Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY - echo "- **触发方式**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY - echo "- **文件总数**: ${{ needs.build.outputs.file_count }}" >> $GITHUB_STEP_SUMMARY - echo "- **总大小**: ${{ needs.build.outputs.total_size }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - echo "### ⏱️ 耗时统计" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| 阶段 | 总耗时 | 上传 |" >> $GITHUB_STEP_SUMMARY - echo "|------|--------|------|" >> $GITHUB_STEP_SUMMARY - echo "| Pre-check | ${PRECHECK_DUR}s | - |" >> $GITHUB_STEP_SUMMARY - echo "| Build | ${BUILD_DUR}s | - |" >> $GITHUB_STEP_SUMMARY - echo "| EdgeOne | ${EDGEONE_DUR}s | ${EDGEONE_UPLOAD}s |" >> $GITHUB_STEP_SUMMARY - echo "| UpYun 直传 | ${UPYUN_DUR_CELL} | ${UPYUN_DUR_CELL} |" >> $GITHUB_STEP_SUMMARY - echo "| Finalize | ${finalize_duration}s | - |" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - echo "### 🚀 部署状态" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| 目标 | 状态 |" >> $GITHUB_STEP_SUMMARY - echo "|------|------|" >> $GITHUB_STEP_SUMMARY - echo "| EdgeOne Pages | ${{ needs.deploy-edgeone.outputs.deployed == 'true' && '✅ 已部署' || '⏭️ 跳过(无变化)' }} |" >> $GITHUB_STEP_SUMMARY - echo "| UpYun 直传 | ${{ needs.build.outputs.upyun_status == 'success' && '✅ 成功' || (needs.build.outputs.upyun_status == 'skipped' && '⏭️ 跳过(无变化)' || '❌ 失败(中转机兜底)') }} |" >> $GITHUB_STEP_SUMMARY - echo "| CDN 刷新 | ${{ steps.deploy-status.outputs.any_deployed == 'true' && '✅ 已刷新' || '⏭️ 跳过' }} |" >> $GITHUB_STEP_SUMMARY - - # ============================================================ - # Job 6: Failure Notification (失败通知) - # ============================================================ - notify-failure: - runs-on: ubuntu-latest - timeout-minutes: 3 - permissions: - contents: read - needs: [pre-check, build, deploy-edgeone, finalize] - if: always() && (needs.pre-check.result == 'failure' || needs.build.result == 'failure' || needs.deploy-edgeone.result == 'failure' || needs.finalize.result == 'failure') - - steps: - - name: Send Telegram failure notification - env: - TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} - TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} - run: | - if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then - echo "⚠️ Telegram secrets 未配置,跳过失败通知" - exit 0 - fi - - FAILURES="" - if [ "${{ needs.pre-check.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Pre-check" "$FAILURES"); fi - if [ "${{ needs.build.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Build" "$FAILURES"); fi - if [ "${{ needs.deploy-edgeone.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Deploy EdgeOne" "$FAILURES"); fi - if [ "${{ needs.build.outputs.upyun_status }}" = "failure" ]; then FAILURES=$(printf "%s\n ⚠️ UpYun 直传失败(不阻断,国内中转机兜底)" "$FAILURES"); fi - if [ "${{ needs.finalize.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Finalize" "$FAILURES"); fi - - PRECHECK_STATUS=$([ "${{ needs.pre-check.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.pre-check.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) - BUILD_STATUS=$([ "${{ needs.build.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.build.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) - EDGEONE_STATUS=$([ "${{ needs.deploy-edgeone.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.deploy-edgeone.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) - UPYUN_STATUS=$([ "${{ needs.build.outputs.upyun_status }}" = "success" ] && echo "✅ 直传成功" || ([ "${{ needs.build.outputs.upyun_status }}" = "failure" ] && echo "❌ 直传失败(中转兜底)" || echo "⏭️ 跳过(无变化)")) - FINALIZE_STATUS=$([ "${{ needs.finalize.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.finalize.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) - - CURRENT_TIME=$(date +'%Y-%m-%d %H:%M:%S') - - MESSAGE="💥 *部署失败*" - MESSAGE="$MESSAGE"$'\n\n' - MESSAGE="$MESSAGE""📦 *项目*: \`${{ github.repository }}\`"$'\n' - MESSAGE="$MESSAGE""🌿 *分支*: \`${{ github.ref_name }}\`"$'\n' - MESSAGE="$MESSAGE""🔗 *Commit*: \`${{ github.sha }}\`"$'\n' - MESSAGE="$MESSAGE""⏰ *时间*: $CURRENT_TIME"$'\n' - MESSAGE="$MESSAGE""🚀 *触发方式*: ${{ github.event_name }}"$'\n\n' - MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE="$MESSAGE""⚠️ *失败阶段*"$'\n' - MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━$FAILURES"$'\n\n' - MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE="$MESSAGE""📊 *各阶段状态*"$'\n' - MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE="$MESSAGE""🔍 Pre-check: $PRECHECK_STATUS"$'\n' - MESSAGE="$MESSAGE""🏗️ Build: $BUILD_STATUS"$'\n' - MESSAGE="$MESSAGE""🌐 Deploy EdgeOne: $EDGEONE_STATUS"$'\n' - MESSAGE="$MESSAGE""☁️ UpYun 直传: $UPYUN_STATUS"$'\n' - MESSAGE="$MESSAGE""📤 Finalize: $FINALIZE_STATUS"$'\n\n' - if [ "${{ github.server_url }}" = "https://github.com" ]; then - RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - else - RUN_URL="http://23.254.236.47:3001/${{ github.repository }}/actions/runs/${{ github.run_id }}" - fi - MESSAGE="$MESSAGE""🔗 [查看详情]($RUN_URL)" - - curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ - -d chat_id="${TELEGRAM_CHAT_ID}" \ - -d text="$MESSAGE" \ - -d parse_mode="Markdown" \ - -d disable_web_page_preview=true - - - name: Send Feishu failure notification - env: - FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} - run: | - if [ -z "$FEISHU_WEBHOOK_URL" ]; then - echo "⚠️ 飞书 Webhook URL 未配置,跳过失败通知" - exit 0 - fi - - FAILURES="" - if [ "${{ needs.pre-check.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Pre-check" "$FAILURES"); fi - if [ "${{ needs.build.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Build" "$FAILURES"); fi - if [ "${{ needs.deploy-edgeone.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Deploy EdgeOne" "$FAILURES"); fi - if [ "${{ needs.build.outputs.upyun_status }}" = "failure" ]; then FAILURES=$(printf "%s\n ⚠️ UpYun 直传失败(不阻断,国内中转机兜底)" "$FAILURES"); fi - if [ "${{ needs.finalize.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Finalize" "$FAILURES"); fi - - PRECHECK_STATUS=$([ "${{ needs.pre-check.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.pre-check.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) - BUILD_STATUS=$([ "${{ needs.build.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.build.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) - EDGEONE_STATUS=$([ "${{ needs.deploy-edgeone.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.deploy-edgeone.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) - UPYUN_STATUS=$([ "${{ needs.build.outputs.upyun_status }}" = "success" ] && echo "✅ 直传成功" || ([ "${{ needs.build.outputs.upyun_status }}" = "failure" ] && echo "❌ 直传失败(中转兜底)" || echo "⏭️ 跳过(无变化)")) - FINALIZE_STATUS=$([ "${{ needs.finalize.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.finalize.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) - - CURRENT_TIME=$(date +'%Y-%m-%d %H:%M:%S') - - MESSAGE_TEXT="💥 部署失败" - MESSAGE_TEXT="$MESSAGE_TEXT"$'\n\n' - MESSAGE_TEXT="$MESSAGE_TEXT""📦 项目: ${{ github.repository }}"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""🌿 分支: ${{ github.ref_name }}"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""🔗 Commit: ${{ github.sha }}"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""⏰ 时间: $CURRENT_TIME"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""🚀 触发方式: ${{ github.event_name }}"$'\n\n' - MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""⚠️ 失败阶段"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━$FAILURES"$'\n\n' - MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""📊 各阶段状态"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""🔍 Pre-check: $PRECHECK_STATUS"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""🏗️ Build: $BUILD_STATUS"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""🌐 Deploy EdgeOne: $EDGEONE_STATUS"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""☁️ UpYun 直传: $UPYUN_STATUS"$'\n' - MESSAGE_TEXT="$MESSAGE_TEXT""📤 Finalize: $FINALIZE_STATUS"$'\n\n' - if [ "${{ github.server_url }}" = "https://github.com" ]; then - RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - else - RUN_URL="http://23.254.236.47:3001/${{ github.repository }}/actions/runs/${{ github.run_id }}" - fi - MESSAGE_TEXT="$MESSAGE_TEXT""🔗 查看详情: $RUN_URL" - - curl -s -X POST "$FEISHU_WEBHOOK_URL" \ - -H "Content-Type: application/json" \ - -d "$(jq -n --arg text "$MESSAGE_TEXT" '{"msg_type":"text","content":{"text":$text}}')" - - # ============================================================ - # Job 7: 网络链路探测(仅手动触发,不改任何线上数据) - # ============================================================ - # 目的:评估「境外 runner → 国内源站/CDN」各条链路的真实耗时与稳定性, - # 为「境外 EdgeOne + 境内多吉云」架构决策提供数据,而不是拍脑袋。 - # - # 触发方式:workflow_dispatch(手动),绝不随 push/schedule 自动跑。 - # 安全:只做连通性/耗时探测,不下载、不上传任何真实产物,不刷新 CDN。 - # - # 探测项: - # A. 又拍云 API(v0.api.upyun.com) —— 境外直传又拍云的入口,已知会 EOF - # B. 又拍云对象存储上传探针(upx login) —— 登录握手能否成功(不传文件) - # C. 腾讯云 COS(cos.ap-*.myqcloud.com) —— 现状中转,作为稳定对照 - # D. 多吉云 API(api.dogecloud.com) —— CDN 刷新 API 跨境响应耗时 - # E. 多吉云 CDN 边缘节点 —— 国内加速域名的连通耗时 - # ============================================================ - probe-network: - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: read - if: github.event_name == 'workflow_dispatch' - - steps: - - name: 探测结果汇总头 - run: | - echo "## 🌐 境外 Runner → 国内链路探测" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| 探测项 | 结果 | 耗时 |" >> $GITHUB_STEP_SUMMARY - echo "|--------|------|------|" >> $GITHUB_STEP_SUMMARY - - - name: A. 又拍云 API 连通性 - run: | - echo "📡 探测又拍云 v0 API..." - T0=$(date +%s%3N) - CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 20 "https://v0.api.upyun.com/" 2>&1 || echo "FAIL") - T1=$(date +%s%3N) - DUR=$(( (T1 - T0) / 1000 )) - echo " HTTP $CODE,耗时 ${DUR}s" - echo "| 又拍云 v0 API | $([ "$CODE" = "200" ] || [ "$CODE" = "401" ] || [ "$CODE" = "403" ] && echo '✅ 可达' || echo '⚠️ $CODE') | ${DUR}s |" >> $GITHUB_STEP_SUMMARY - - - name: B. 又拍云 upx 登录握手(不传文件) - env: - UPYUN_SERVICE: ${{ secrets.UPYUN_BUCKET }} - UPYUN_OPERATOR: ${{ secrets.UPYUN_OPERATOR }} - UPYUN_PASSWORD: ${{ secrets.UPYUN_PASSWORD }} - run: | - echo "📡 探测又拍云 upx 登录握手..." - if [ -z "$UPYUN_SERVICE" ] || [ -z "$UPYUN_OPERATOR" ] || [ -z "$UPYUN_PASSWORD" ]; then - echo " ⚠️ UpYun secrets 未配置,跳过" - echo "| upx 登录握手 | ⏭️ 无密钥 | — |" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - UPX_VERSION="0.4.9" - curl -fsSL --connect-timeout 10 --max-time 60 -o /tmp/upx.tar.gz \ - "https://github.com/upyun/upx/releases/download/v${UPX_VERSION}/upx_${UPX_VERSION}_linux_amd64.tar.gz" \ - && tar -xzf /tmp/upx.tar.gz -C /tmp && mv /tmp/upx /tmp/upyun-upx && chmod +x /tmp/upyun-upx - T0=$(date +%s%3N) - if timeout 30 /tmp/upyun-upx login "$UPYUN_SERVICE" "$UPYUN_OPERATOR" "$UPYUN_PASSWORD" 2>&1; then - R="✅ 握手成功" - else - R="❌ 握手失败" - fi - T1=$(date +%s%3N) - DUR=$(( (T1 - T0) / 1000 )) - echo " $R,耗时 ${DUR}s" - echo "| upx 登录握手 | $R | ${DUR}s |" >> $GITHUB_STEP_SUMMARY - - - name: C. 腾讯云 COS 连通性(现状对照) - env: - COS_BUCKET: ${{ secrets.COS_BUCKET }} - COS_REGION: ${{ secrets.COS_REGION }} - run: | - echo "📡 探测腾讯云 COS..." - if [ -z "$COS_BUCKET" ]; then - echo " ⚠️ COS secrets 未配置,跳过" - echo "| 腾讯云 COS | ⏭️ 无密钥 | — |" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - HOST="${COS_BUCKET}.cos.${COS_REGION}.myqcloud.com" - T0=$(date +%s%3N) - CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 20 "https://${HOST}/" 2>&1 || echo "FAIL") - T1=$(date +%s%3N) - DUR=$(( (T1 - T0) / 1000 )) - echo " ${HOST} → HTTP $CODE,耗时 ${DUR}s" - echo "| 腾讯云 COS | $([ "$CODE" != "FAIL" ] && [ "$CODE" != "000" ] && echo '✅ 可达' || echo '⚠️ $CODE') | ${DUR}s |" >> $GITHUB_STEP_SUMMARY - - - name: D. 多吉云 API 跨境响应 - env: - DOGECLOUD_ACCESS_KEY: ${{ secrets.DOGECLOUD_ACCESS_KEY }} - DOGECLOUD_SECRET_KEY: ${{ secrets.DOGECLOUD_SECRET_KEY }} - run: | - echo "📡 探测多吉云 API(不带签名,只测连通+延迟)..." - T0=$(date +%s%3N) - CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 20 "https://api.dogecloud.com/cdn/refresh/add.json" 2>&1 || echo "FAIL") - T1=$(date +%s%3N) - DUR=$(( (T1 - T0) / 1000 )) - echo " HTTP $CODE,耗时 ${DUR}s" - echo "| 多吉云 API | $([ "$CODE" != "FAIL" ] && [ "$CODE" != "000" ] && echo '✅ 可达' || echo '⚠️ $CODE') | ${DUR}s |" >> $GITHUB_STEP_SUMMARY - - - name: E. 多吉云 CDN 边缘节点连通性 - run: | - echo "📡 探测多吉云 CDN 边缘节点..." - # 用当前博客域名(国内加速走多吉云)测连通 + 耗时 - T0=$(date +%s%3N) - CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 20 "https://usj.cc/" 2>&1 || echo "FAIL") - T1=$(date +%s%3N) - DUR=$(( (T1 - T0) / 1000 )) - echo " usj.cc → HTTP $CODE,耗时 ${DUR}s" - echo "| 博客站点(多吉云加速) | $([ "$CODE" = "200" ] && echo '✅ 可达' || echo '⚠️ $CODE') | ${DUR}s |" >> $GITHUB_STEP_SUMMARY - - - name: 探测结果汇总尾 - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "> 结论判读:若「upx 登录握手」与「又拍云 v0 API」耗时远高于「COS / 多吉云 API」," >> $GITHUB_STEP_SUMMARY - echo "> 说明境外直传又拍云仍不稳定,国内线路应继续走中转机兜底,不宜改成境外直传。" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/CNB构建落地方案.md b/CNB构建落地方案.md index 97233b3c..2776f2da 100644 --- a/CNB构建落地方案.md +++ b/CNB构建落地方案.md @@ -143,7 +143,7 @@ CNB 只替换「构建 + 搬运」这一段,顺带砍掉广州中转机上那 ## 四、落地骨架 -### 4.0 第一步:仓库迁移与「双远端推送」(CNB 主仓 + GitHub 备份) +### 4.0 第一步:仓库迁移与「双远端推送」(CNB 主仓 + 辅仓备份) **目标形态** @@ -167,7 +167,7 @@ gitea → 自建 Gitea ← 暂时留着当只读参考, | 两个 pushurl 都可用 | ✅ 两个都收到,一次 push 搞定 | | **第一个失败** | ❌ **git 立即中止,第二个根本不会被推** | -也就是说:**CNB 推失败时,GitHub 那份备份也不会更新** —— 备份的意义就没了。 +也就是说:**主仓推失败时,辅仓那份备份也不会更新** —— 备份的意义就没了。 所以拆成两个独立 remote,别名里用 `;` 而不是 `&&`,保证互不阻塞。 **脚本**(已就位,可直接用) @@ -211,12 +211,12 @@ git config --global credential.helper manager # Windows 的 Git Credential Man | `GITEA_SECRETS.md` | Gitea 相关凭据说明 | 已跟踪 | | `content/posts/2024/.../setup-secrets.png` | 疑似密钥配置截图 | 已跟踪 | -**好消息**:已核实 `https://github.com/zqlit/blog` 返回 **Page not found**,仓库不可公开访问 -→ **私钥没有被公开暴露**,无需紧急处置。 +**关于暴露面**:该仓一直是**私有**的(匿名 API 返回 404)。2026-10-06 账号被平台标记后 +仓库更被按 **AUP 合规**清空、本地也移除了 `gh` remote → **这条威胁面已彻底消失**。 -**但两件事建议顺手做掉**: +**但两件事仍建议做掉**: -1. **CNB 仓库建成「私有」** —— 这些文件在私有仓里影响可控 +1. **CNB 仓库保持「私有」** —— 这些文件在私有仓里影响可控 2. **`git rm --cached` 停止继续跟踪**(历史里的删不掉,但至少不再新增): ```bash git rm --cached .env GITEA_SECRETS.md write-server/nginx/ssl/privkey.pem write-server/nginx/ssl/fullchain.pem @@ -226,8 +226,11 @@ git config --global credential.helper manager # Windows 的 Git Credential Man (另:`.gitignore` 里那条 `write\.env` 是写错的 —— gitignore 中 `\` 是转义符, 它实际匹配文件名 `write.env` 而非 `write/.env`;幸好 `write/.gitignore` 里有 `.env*` 兜住了。) -**不建议现在做历史清理**(`git filter-repo`):会重写 955 个提交、让 GitHub 备份随之分叉, -收益(去掉一个未公开的私钥)远小于风险。正确做法是**轮换凭据**而不是重写历史。 +**历史清理(`git filter-repo`)的判断已变**:当初不做的唯一顾虑是「会让备份分叉」—— +2026-10-06 GitHub 那份已消失,**这个顾虑没有了**;且离线 bundle 已加密 +(见 `架构总览.md` §5.6),不受影响。 +所以**现在是彻底抹掉这些文件历史的最佳窗口**。 +代价:全部提交 SHA 改变、自建 Gitea 那份需重推。 --- diff --git a/README.md b/README.md index c498d85c..e0891dde 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,9 @@ Hugo + Ying 主题。`hugo.toml` 控站点信息、永久链接、Artalk 地址 | `add_draft_to_hidden.py` | 构建前把 `status: hidden` 转成草稿 | **CI** | | `refresh_cdn.js` | 刷新多吉云 CDN(零依赖) | **CI** | | `send_mail.js` | 构建结果邮件通知(零依赖 SMTP) | **CI** | -| `setup-cnb-remotes.sh` | 切换/重建 git 远端(CNB 主仓 + GitHub 备份) | 本机 | +| `setup-cnb-remotes.sh` | 切换/重建 git 远端(CNB 主仓 + 辅仓备份) | 本机 | +| `backup-bundle.mjs` | 整仓 bundle → AES-256-GCM 加密 → WebDAV 传中兴 F50(见 架构总览 §5.6) | 本机/计划任务 | +| `backup-task.cmd` | 上面的计划任务入口(每天 03:30;**内容必须全 ASCII**) | 计划任务 | | `optimize_images.js` | 图片批量压缩优化 | 本机 | | `generate_circle_data.js` | 抓友链 RSS 生成朋友圈数据 | 本机 | | `update_link_lite_json.ps1` | 友链 `links.yaml` → JSON | 本机 | diff --git a/架构总览.md b/架构总览.md index 06c934c8..0f5ffbcc 100644 --- a/架构总览.md +++ b/架构总览.md @@ -112,7 +112,8 @@ ### 5.1 推送 ```bash -git pushall # 别名 = git push origin main; git push gitee main +git pushall # 别名 = git push origin main; git push gitee main +git push origin main # ← 当前实际只用这条:gitee remote 尚未建立(见 §6 #2) ``` 用 `;` 而非 `&&` —— **一段失败不影响另一段**(实测:串联 `pushurl` 时第一个失败会中止后续)。 @@ -272,11 +273,11 @@ git clone blog.bundle blog # 或 git fetch blog.bundle 'refs/*:refs/*' | # | 项 | 说明 | |---|---|---| -| 1 | `gh` remote(GitHub) | ✅ **已移除**(2026-10-06)。GitHub 账号被标记、仓库被 AUP 清空,改以 Gitee 为辅仓 | +| 1 | `gh` remote(GitHub) | ✅ **已移除**(2026-10-06)。GitHub 账号被标记、仓库被 AUP 清空 —— **GitHub 已彻底退出本项目的备份体系**:远端、推送别名、发布链路、CI 定义全部清干净。辅仓选型见 #2 | | 2 | **辅仓选型(未定)** | 当前 `pushall` 已写成 `origin + gitee`,但 `gitee` remote **尚不存在** → 每次 `git pushall` 的第二段会报错(**不影响 CNB 那段**)。两条路:**Gitee**(需先解 §5.3 两条硬约束)或**云效 Codeup**(见 §5.3 对照,零改造,但需绑钉钉或选「地域组织」)。定下后再 `git remote add` | | 3 | `bin/linux/hugo` 出库 | 83.1MB,超 Gitee 单文件上限。**只有选 Gitee 才必须做**;选 Codeup 则不必(占其命令行 200MB 上限的 41.6%)。要做的话需改为构建时获取(国内镜像 / CNB 对象存储),并同步改 `.cnb.yml` 的 `by:` 字段 | | 4 | `gitea` remote | 只读保留,稳定几天后 `git remote remove gitea` | -| 5 | GitHub 侧旧 workflow | `.github/workflows/*`(`deploy.yml` 1169 行 + 两个辅助)已随 GitHub 一并退役,可删 | +| 5 | GitHub 侧旧 workflow | ✅ **已删除**(2026-10-06)。整个 `.github/` 目录移除(`deploy.yml` 1169 行 + `aliyun-backup.yml` + `cleanup.yml`)—— **GitHub 已不再是任何环节的依赖**。旧实现可在 git 历史中查 | | 6 | 令牌 scope | 缺 `repo-cnb-history:r`(读构建日志);补上后 agent 可自行排错 | | 7 | Gitea 主机 / 广州中转机 | 发布链路已不依赖;是否退役取决于其它用途 | | 8 | `README.md` | 仍写着旧 GitHub Actions 流程,待更新 | @@ -295,4 +296,5 @@ git clone blog.bundle blog # 或 git fetch blog.bundle 'refs/*:refs/*' **证据出处(现行)**:`.cnb.yml`(284 行)、`deploy/Dockerfile`、`bin/linux/hugo`、 `scripts/send_mail.js`、`scripts/refresh_cdn.js`、`scripts/setup-cnb-remotes.sh`、 `scripts/backup-bundle.mjs`、`scripts/backup-task.cmd`。 -已退役参考:`.github/workflows/deploy.yml`(1169 行)。 +原 GitHub Actions 流水线(`.github/workflows/deploy.yml`,1169 行)已于 2026-10-06 删除, +需要查请翻 git 历史。