-
Notifications
You must be signed in to change notification settings - Fork 45
206 lines (191 loc) · 11.1 KB
/
sync-uuid.yml
File metadata and controls
206 lines (191 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
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。
#
# ⚠️ 这个 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)
on:
push:
branches:
- main
- feat/contributor
paths:
# 2026-05 i18n URL 段化:mdx 内容从 app/docs/ 迁到 content/docs/
- "content/docs/**"
- "scripts/uuid.mjs"
- "scripts/backfill-contributors.mjs"
- "package.json"
- "pnpm-lock.yaml"
- ".github/workflows/sync-uuid.yml"
- "generated/doc-contributors.json"
workflow_dispatch: {}
concurrency:
group: backfill-${{ github.ref }}
cancel-in-progress: true
jobs:
backfill:
# 防止 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
steps:
- name: Run backfill on server via SSH
id: ssh_backfill
uses: appleboy/ssh-action@v1.2.0
with:
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}"
# SSH 非交互 shell 不加载 ~/.bashrc / ~/.zshrc,nvm + pnpm 的 PATH
# 要手动 source 进来。否则 pnpm: command not found 直接卡住后面所有步骤。
# -s 开头表示"if file exists" 兼容 nvm 没装的极端情况。
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
cd /home/ubuntu/involution-hell-project/frontend
# ============================================================
# 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"
# ============================================================
# 1. 加载环境变量 + 必要字段校验
# ------------------------------------------------------------
# DATABASE_URL 缺失时 backfill 会隐性降级(shouldSyncDb=false),
# 生成的 JSON 内容是"本轮快照"而非"DB 累计值",覆盖 commit 回仓
# 会把累计呈现变成单次快照——**视觉上像数据丢了**但 DB 没动。
# 直接 fail 不给它走降级路径。
#
# DATABASE_URL 合并策略:
# .env 文件里可能只有分量(PGHOST/PGUSER/PGPASSWORD/PGDATABASE/PGPORT),
# 不一定有合并好的 DATABASE_URL(Prisma 格式)。
# 优先用 .env 里的 DATABASE_URL;缺失时从分量动态拼出来。
# 分量都在 .env 里,所以 source 之后两种路径都能走。
# ============================================================
set -a && . ./.env && set +a
if [[ -z "${DATABASE_URL:-}" ]]; then
# 从 PG 分量拼 Prisma connection string
# 格式:postgresql://user:password@host:port/database?sslmode=disable
if [[ -n "${PGUSER:-}" && -n "${PGPASSWORD:-}" && -n "${PGHOST:-}" && -n "${PGDATABASE:-}" ]]; then
export DATABASE_URL="postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}:${PGPORT:-5432}/${PGDATABASE}?sslmode=${PGSSLMODE:-disable}"
echo "DATABASE_URL 从 PG 分量拼出:postgresql://${PGUSER}:***@${PGHOST}:${PGPORT:-5432}/${PGDATABASE}"
else
echo "::error::DATABASE_URL 未配置,且 PG 分量(PGUSER/PGPASSWORD/PGHOST/PGDATABASE)不完整,拒绝运行"
exit 1
fi
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
# ============================================================
# 4. 跑脚本
# ------------------------------------------------------------
# uuid.mjs:幂等补 docId;已有 docId 的文件绝对不改。
# backfill-contributors.mjs:按 docId 增量累加,doc_paths 表合并历史路径,
# 翻译版跳过,失败即 exit 1 让本 step fail 不进 commit。
# ============================================================
pnpm exec node scripts/uuid.mjs
pnpm exec tsx scripts/backfill-contributors.mjs
# ============================================================
# 4.5 清 resolver 缓存
# ------------------------------------------------------------
# backfill 更新了 path_current 后,后端 Caffeine 的 doc-resolve 缓存
# 可能持有旧的 canonical(TTL 10min)。doc mv 后最多 10min 内旧 URL
# 还是 301 到旧路径——自愈失效。
# 直接重启 backend 容器是最简可靠的清缓存方式:无状态 JVM,
# docker restart 约 10s 内重新 up,不影响正在处理的请求(Caddy 会重试)。
# ============================================================
echo "Restarting backend to evict doc-resolve cache..."
docker restart involution-hell-backend
# 等待后端重新 healthy(最多 30s),避免 workflow 结束后请求打到启动中的容器
for i in $(seq 1 6); do
STATUS=$(docker inspect --format='{{.State.Health.Status}}' involution-hell-backend 2>/dev/null || echo "unknown")
if [[ "$STATUS" == "healthy" ]]; then
echo "Backend healthy after ${i}×5s"
break
fi
sleep 5
done
# ============================================================
# 5. 自动提交
# ------------------------------------------------------------
# 只 commit MDX frontmatter 改动 + 生成的 JSON,不包含任何其他脏文件。
# [skip ci] 防止自提交再次触发本 workflow 死循环。
# ============================================================
if ! git diff --quiet -- 'content/docs/**/*.md' 'content/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 'content/docs/**/*.md' 'content/docs/**/*.mdx' generated/doc-contributors.json
git commit -m "chore(docs): sync doc metadata [skip ci]"
git push origin "$BRANCH"
else
echo "No metadata changes to commit."
fi
# 失败时在 Actions summary 里打印醒目错误,避免静默 rot。
# 此前两轮失败都因为没有失败告警而被遗漏直到人工检查。
- name: Notify on failure
if: failure()
run: |
echo "::error title=Docs Backfill 失败::sync-uuid workflow 在 ${{ github.sha }} 上失败。"
echo "::error::DB 自本次 push 起将不再同步,doc_paths / path_current 将漂移。"
echo "::error::请检查 Actions 日志,修复后手动触发 workflow_dispatch 补跑。"
echo ""
echo "触发 commit:${{ github.sha }}"
echo "触发分支:${{ github.ref_name }}"
echo "触发 actor:${{ github.actor }}"