From c2045bfe97b360ff2287b19407a8793f092e511f Mon Sep 17 00:00:00 2001 From: longsizhuo Date: Fri, 17 Apr 2026 23:12:43 +0000 Subject: [PATCH 1/2] =?UTF-8?q?ci(sync-uuid):=20=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E5=88=B0=20SSH=20=E8=BF=9B=E8=87=AA=E5=BB=BA=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E8=B7=91=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DB 从 Neon 迁到自建后只绑 127.0.0.1:5432,GH runner 直连不通。最干净的 方案是 SSH 进服务器跑——服务器上有完整 repo clone + 本机能到 PG + 已有 github ssh key 可以 push 回来。 - 用 appleboy/ssh-action@v1.2.0,三件套 secrets:SERVER_HOST / SERVER_USER / SERVER_SSH_KEY - 脚本流程:fetch + reset --hard 到触发 commit → pnpm install + prisma generate → uuid.mjs → backfill-contributors.mjs → 只有 diff 时 commit [skip ci] + push - set -euo pipefail 保证任何一步失败整个 action 失败 - command_timeout 15m 给 backfill 足够余量 配套一次性准备(已完成,不在 CI 里): - 服务器 ~/.ssh/authorized_keys 加 gh-actions-leaderboard 公钥 - ~/involution-hell-project/frontend/.env 的 DATABASE_URL 改指 127.0.0.1 注:scripts/generate-leaderboard.mjs 还在 package.json 的 prebuild 钩子里, Vercel 部署时仍会尝试跑。该问题独立处理:要么挪到本 workflow,要么让 Vercel build 时优雅降级(失败跳过用上次提交的 JSON)。 --- .github/workflows/sync-uuid.yml | 89 +++++++++++++++++---------------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/.github/workflows/sync-uuid.yml b/.github/workflows/sync-uuid.yml index 343b9555..e672407d 100644 --- a/.github/workflows/sync-uuid.yml +++ b/.github/workflows/sync-uuid.yml @@ -1,5 +1,13 @@ name: Docs Backfill (on docs changes) +# 2026-04-17 起从"GH runner 直连 Neon"改为"SSH 进自建服务器跑脚本"。 +# 原因:DB 从 Neon 迁到服务器自建 PG 后只绑 127.0.0.1:5432,不对公网暴露。 +# 设计权衡见 wiki Frontend-Auth-And-Admin / 后端 docs/database.md。 +# +# Secrets 依赖: +# SERVER_HOST / SERVER_USER / SERVER_SSH_KEY — SSH 远程登录三件套 +# (私钥生成方式 + 公钥已写入服务器 ~/.ssh/authorized_keys,见仓库 wiki) + on: push: branches: @@ -21,57 +29,52 @@ concurrency: jobs: backfill: - # 防止 fork、限定 main、并避免机器人循环 + # 防止 fork、限定 main / feat/contributor、并避免机器人循环 if: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/feat/contributor') && github.actor != 'github-actions[bot]' runs-on: ubuntu-latest - permissions: - contents: write - env: - DATABASE_URL: ${{ secrets.DATABASE_URL }} - GITHUB_TOKEN: ${{ secrets.GH_PAT }} # 供脚本调用 GitHub API 提升速率 - DOCS_DIR: app/docs - steps: - - uses: actions/checkout@v4 - - # Enable corepack to ensure the exact pnpm version from package.json is used - - name: Enable Corepack - run: corepack enable - - - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v4 + - name: Run backfill on server via SSH + uses: appleboy/ssh-action@v1.2.0 with: - node-version: 22 - cache: "pnpm" # 顺便启用 pnpm 缓存,加速 + host: ${{ secrets.SERVER_HOST }} + username: ${{ secrets.SERVER_USER }} + key: ${{ secrets.SERVER_SSH_KEY }} + # 超时 15 分钟:backfill-contributors 要遍历所有 docs + 拉 GitHub API, + # 大改动一次跑 3-5 分钟,留足余量 + command_timeout: 15m + # set -euo pipefail + BRANCH 透传,脚本内任何一步失败都让整个 action fail + envs: GITHUB_REF_NAME + script: | + set -euo pipefail + BRANCH="${GITHUB_REF_NAME:-main}" + cd /home/ubuntu/involution-hell-project/frontend - # Verify pnpm version matches package.json packageManager field - - name: Check pnpm version - run: node scripts/check-pnpm-version.mjs + # 1. 同步仓库到触发本次 workflow 的 commit + git fetch --prune origin + git checkout "$BRANCH" + git reset --hard "origin/$BRANCH" - - name: Install deps - run: pnpm install --frozen-lockfile + # 2. 依赖和 Prisma client(frontend .env 里 DATABASE_URL 已指本地 PG) + set -a && . ./.env && set +a + pnpm install --frozen-lockfile + pnpm prisma generate - - name: Generate Prisma Client - run: pnpm prisma generate + # 3. 给 docs 补 docId frontmatter(幂等;没新增就啥都不改) + pnpm exec node scripts/uuid.mjs - - name: Ensure docId frontmatter - run: pnpm exec node scripts/uuid.mjs + # 4. 回填 contributors 并写 generated/doc-contributors.json + pnpm exec tsx scripts/backfill-contributors.mjs - - name: Backfill contributors & sync DB - run: pnpm exec tsx scripts/backfill-contributors.mjs - - - name: Auto-commit doc metadata (if any) - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: "chore(docs): sync doc metadata [skip ci]" # ← 防循环 - file_pattern: "app/docs/**/*.md app/docs/**/*.mdx generated/doc-contributors.json" - - - name: Upload snapshot JSON - uses: actions/upload-artifact@v4 - with: - name: doc-contributors-snapshot - path: generated/doc-contributors.json - if-no-files-found: ignore + # 5. 自动提交 —— 仅当 MDX / JSON 有实际变动时才推 + if ! git diff --quiet -- 'app/docs/**/*.md' 'app/docs/**/*.mdx' generated/doc-contributors.json; then + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add 'app/docs/**/*.md' 'app/docs/**/*.mdx' generated/doc-contributors.json + # [skip ci] 防止自提交再次触发本 workflow 死循环 + git commit -m "chore(docs): sync doc metadata [skip ci]" + git push origin "$BRANCH" + else + echo "No metadata changes to commit." + fi From a70fab947329f2bc3a8ad3af7e0601f51d61b848 Mon Sep 17 00:00:00 2001 From: longsizhuo Date: Fri, 17 Apr 2026 23:33:04 +0000 Subject: [PATCH 2/2] =?UTF-8?q?ci(sync-uuid):=20=E5=8A=A0=203=20=E9=81=93?= =?UTF-8?q?=E9=98=B2=E5=BE=A1=E6=A3=80=E6=9F=A5=E4=BF=9D=E4=BD=8F=E7=BD=B2?= =?UTF-8?q?=E5=90=8D=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 审计后发现原 workflow 有 3 处边缘风险可能让 uuid.mjs/backfill 产出错数据: 1. 脏工作树被 git reset --hard 抹掉 → 可能浪费 docId 或抹掉手工改动 2. DATABASE_URL 缺失时 backfill 隐性降级成本轮快照 → JSON 被覆盖呈现错误累计 3. DB 行数异常低(被意外清库)→ 增量累计从 0 起算,GH API 单文件最多 N 页外的老 commits 永远丢 加三道闸: - git status --porcelain 非空 → fail loud 强制人工介入 - DATABASE_URL 空 → fail(GITHUB_TOKEN 只 warn) - doc_contributors < 200 行 → fail(迁移完成时 ~295 行,保守下限) 附长注释说明每项保护对应脚本里哪个不变量。以后有人看 workflow 能立刻知道每段为啥不能删。 --- .github/workflows/sync-uuid.yml | 86 ++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/.github/workflows/sync-uuid.yml b/.github/workflows/sync-uuid.yml index e672407d..3aabeaf6 100644 --- a/.github/workflows/sync-uuid.yml +++ b/.github/workflows/sync-uuid.yml @@ -4,9 +4,17 @@ name: Docs Backfill (on docs changes) # 原因:DB 从 Neon 迁到服务器自建 PG 后只绑 127.0.0.1:5432,不对公网暴露。 # 设计权衡见 wiki Frontend-Auth-And-Admin / 后端 docs/database.md。 # +# ⚠️ 这个 workflow 维护的是**贡献者署名记录的生命线**——谁写了哪篇文档、 +# 文件改名/移动后署名不丢,全靠底层两个脚本: +# - scripts/uuid.mjs 生成/保持 docId frontmatter(永不改写已有值) +# - scripts/backfill-contributors.mjs 按 docId 累加 GitHub commits 到 DB, +# doc_paths 表维护历史路径并集跨改名追踪 +# 若本 workflow 跑坏了(生成错 docId / 写坏 JSON / 写错 DB),署名可能丢失。 +# 下面每一步前都有防御性检查,出错就 fail loud 不吞错。 +# # Secrets 依赖: # SERVER_HOST / SERVER_USER / SERVER_SSH_KEY — SSH 远程登录三件套 -# (私钥生成方式 + 公钥已写入服务器 ~/.ssh/authorized_keys,见仓库 wiki) +# (私钥生成方式 + 公钥已写入服务器 ~/.ssh/authorized_keys,见 wiki) on: push: @@ -51,28 +59,90 @@ jobs: BRANCH="${GITHUB_REF_NAME:-main}" cd /home/ubuntu/involution-hell-project/frontend - # 1. 同步仓库到触发本次 workflow 的 commit + # ============================================================ + # 0. 脏工作树检查 + # ------------------------------------------------------------ + # 为什么重要:下一步 git reset --hard 会抹掉所有未提交改动。 + # 如果上次 workflow 跑到一半 uuid.mjs 写了 docId 但 backfill 失败没 + # commit,这些"生成了的新 docId"在脏树里。reset 后 uuid.mjs 会生成 + # 不同的新 UUID——不丢署名(因为从未 commit 到 main),但浪费 id + # 且可能造成 docId 分叉留给下一次排查。 + # + # 更严重的场景:如果有人手工在服务器上 hotfix MDX 忘 commit, + # reset 会悄悄抹掉。一律 fail loud 强制人工介入更安全。 + # ============================================================ + if [[ -n "$(git status --porcelain)" ]]; then + echo "::error::服务器 frontend 工作树不干净,拒绝 reset。先人工处理后再重跑。" + git status --short + exit 1 + fi + + # 同步仓库到触发本次 workflow 的 commit git fetch --prune origin git checkout "$BRANCH" git reset --hard "origin/$BRANCH" - # 2. 依赖和 Prisma client(frontend .env 里 DATABASE_URL 已指本地 PG) + # ============================================================ + # 1. 加载环境变量 + 必要字段校验 + # ------------------------------------------------------------ + # DATABASE_URL 缺失时 backfill 会隐性降级(shouldSyncDb=false), + # 生成的 JSON 内容是"本轮快照"而非"DB 累计值",覆盖 commit 回仓 + # 会把累计呈现变成单次快照——**视觉上像数据丢了**但 DB 没动。 + # 直接 fail 不给它走降级路径。 + # ============================================================ set -a && . ./.env && set +a + if [[ -z "${DATABASE_URL:-}" ]]; then + echo "::error::DATABASE_URL 未配置,拒绝运行以免 JSON 降级成本轮快照" + exit 1 + fi + if [[ -z "${GITHUB_TOKEN:-}" ]]; then + echo "::warning::GITHUB_TOKEN 未配置,GitHub API rate limit 60/h 会打爆" + fi + + # ============================================================ + # 2. DB 健康检查 + # ------------------------------------------------------------ + # 如果本地 PG 意外被清库或未迁移完成,doc_contributors 表可能是空的。 + # 从空 DB 起增量累计 → 早期已经超出 GitHub API 单文件 commits 最多 N 页 + # 范围的老 commits 永远拉不回来 → 署名丢失。 + # + # 下限 200 行是保守值:迁移完成后 doc_contributors 约 295 行, + # 低于 200 视为"异常状态",要人工确认后才能继续。 + # ============================================================ + CONTRIB_COUNT=$(docker exec involution-postgres \ + psql -U neondb_owner -d involution_hell -tAc \ + "SELECT count(*) FROM doc_contributors;" 2>/dev/null || echo "0") + if [[ "$CONTRIB_COUNT" -lt 200 ]]; then + echo "::error::doc_contributors 只有 $CONTRIB_COUNT 行(预期 >= 200),DB 状态异常,拒绝运行。" + echo "::error::如果是故意重置 DB,临时把本检查注释掉,一次跑完再恢复。" + exit 1 + fi + echo "DB 健康检查通过:doc_contributors 有 $CONTRIB_COUNT 行" + + # 3. 依赖和 Prisma client pnpm install --frozen-lockfile pnpm prisma generate - # 3. 给 docs 补 docId frontmatter(幂等;没新增就啥都不改) + # ============================================================ + # 4. 跑脚本 + # ------------------------------------------------------------ + # uuid.mjs:幂等补 docId;已有 docId 的文件绝对不改。 + # backfill-contributors.mjs:按 docId 增量累加,doc_paths 表合并历史路径, + # 翻译版跳过,失败即 exit 1 让本 step fail 不进 commit。 + # ============================================================ pnpm exec node scripts/uuid.mjs - - # 4. 回填 contributors 并写 generated/doc-contributors.json pnpm exec tsx scripts/backfill-contributors.mjs - # 5. 自动提交 —— 仅当 MDX / JSON 有实际变动时才推 + # ============================================================ + # 5. 自动提交 + # ------------------------------------------------------------ + # 只 commit MDX frontmatter 改动 + 生成的 JSON,不包含任何其他脏文件。 + # [skip ci] 防止自提交再次触发本 workflow 死循环。 + # ============================================================ if ! git diff --quiet -- 'app/docs/**/*.md' 'app/docs/**/*.mdx' generated/doc-contributors.json; then git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add 'app/docs/**/*.md' 'app/docs/**/*.mdx' generated/doc-contributors.json - # [skip ci] 防止自提交再次触发本 workflow 死循环 git commit -m "chore(docs): sync doc metadata [skip ci]" git push origin "$BRANCH" else