Files
blog/CNB构建落地方案.md
T
zqlit 448b898e40 feat(远端): 恢复自建 Gitea 代码辅仓,OpenList 降为备选备份
用户定案:「自建 gitea 同步一下吧,openlist 也作为备选备份」。
于是把上一轮「撤销全部 git 辅仓」的决定部分回退:在线那一路回到自建 Gitea,
OpenList 上的加密 bundle 从「唯一异地备份」改为「备选异地备份」。

两层不是重复,而是失效模式不同:
  · 自建 Gitea(在线、可增量、可浏览)—— 不受任何第三方平台规则约束
  · 离线 bundle(离线、单一文件、完整历史)—— 平台全挂也能恢复

一、实际同步
- Gitea 位于 23.254.236.47:3001(Gitea 28.0.0),本机直连即可推,
  **不需要广州中转机**(旧结论「沙箱跑不通 git smart HTTP」只针对当时的代理)
- 远端停在 ed38f938(2026-10-04),落后 57 个提交,且是本地 HEAD 的**祖先**
  → 一次 fast-forward 追平,**未强推、未丢历史**
- 同步后 origin 与 gitea 均指向 5f27ad32

二、git 配置
- 恢复 remote `gitea`(凭据编在 URL 里,只落本机 .git/config,不入库)
- `pushall` 回到双推:
    !git push origin main; ec=$?; git remote | grep -qx gitea && git push gitea main; exit $ec
  · 用 `;` 而非 `&&` → 主仓挂了辅仓照样推
  · `exit $ec` → 退出码仍以主仓为准,辅仓成功不掩盖主仓失败
  · gitea 段带存在性前置判断 → 没配辅仓时静默跳过,不白报错

三、发布链路恢复 gitea(前一轮刚去掉,现按新定案加回)
- `deploy/editor-api/bootstrap.sh`
    · 新增 GITEA_URL / GITEA_USER / GITEA_PASS(凭据必须现给,不写死在脚本里)
    · PUSH_REMOTES 默认 → origin,gitea;没给 GITEA_PASS 则降级为 origin
    · 恢复「配 gitea 远端」一节;退役远端名单去掉 gitea(只留 gh / gitee)
      —— 否则新部署会把现行辅仓当成退役远端清掉
    · 结尾补一句「未启用 Gitea 辅仓」的提示
- `docker-compose.editor.yml`、`editor-api/server.mjs` → 默认 origin,gitea
- `editor-api/README.md` → 变量表同步;`editor-api/Dockerfile` → 注释补 http 情形

四、`scripts/setup-cnb-remotes.sh` 改回双远端模式
- 头部沿革、USAGE、凭据段全部重写;GITEA_PASS 走环境变量,不入库
- 退役远端循环只清 gh / gitee;pushall 写同一份定义

五、文档
- `架构总览.md`:§1.2 地址地图加「代码辅仓」行、备份行降为「备选」;
  §2 旅程图改双推;§5.1 推送重写(含别名实际定义与三条取舍);
  §5.2 远端改双行表 + Gitea 接入细节;§5.3 由「不走 git 远端」改为
  「远端与备份的分工」(三层各自的失效模式);§5.6 定位改备选;
  §6 待办 #2/#4/#7/#10 更新
- `CNB构建落地方案.md` §4.0 目标形态与定案块、§4.1 第 4 条
- `README.md` 发布流程图、推送说明、脚本表
- `scripts/backup-run.mjs` 头部注释:不再是「唯一备份」

实测:
  git push gitea main → ed38f938..5f27ad32  main -> main
  git pushall         → origin / gitea 双向同步,退出码 0
  bash -n 校验 bootstrap.sh 与 setup-cnb-remotes.sh 均通过
2026-10-06 21:42:08 +08:00

464 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CNB 构建落地方案:双 CDN 架构不动,砍掉中转链
> 状态:2026-10-04 · 待你确认后落地
> 回答的问题:「CNB 构建的前提下,能不能继续现在的架构(境外 EdgeOne + 境内多吉云 → 又拍云源站)?」
---
## 一句话结论
**能,而且一个字都不用改。** 域名解析、备案、EdgeOne 项目、多吉云配置、又拍云存储全部保持原样。
CNB 只替换「构建 + 搬运」这一段,顺带砍掉广州中转机上那个每分钟轮询的 `sync.sh`。
---
## 零、GitHub 依赖审计:全链路零依赖
回答「GitHub 账号被标记了,还能连上 CNB 吗」——**能,而且这条链路从头到尾不需要 GitHub。**
逐环节核实(来源:腾讯云官方文档「社区版快速入门」「个人中心」、CNB 官方 Wiki):
| 环节 | CNB 的做法 | 碰 GitHub 吗 |
|---|---|---|
| 注册 / 登录 | **微信扫码**,扫码即自动注册,无邮箱验证、无登录密码 | ❌ |
| 实名认证 | 微信扫码授权手机号 | ❌ |
| 建组织 / 仓库 | CNB 内建(无「个人私有仓库」,仓库必须挂在组织下) | ❌ |
| 导入代码 | 标准 git:`git push --mirror <cnb-url>`,源仓可以是**自建 Gitea** | ❌ |
| 拉构建依赖 | CNB 自带「国内外依赖源默认加速」(腾讯云 CDN + Docker/NPM/Maven 镜像) | ❌ |
| hugo 二进制 | **已随仓库携带**(`bin/linux/hugo`),构建时零跨境下载 | ❌ |
| 部署 | EdgeOne API + 又拍云 upx v0 | ❌ |
官方原文:
> 云原生构建默认使用微信扫码注册登录,未注册用户在扫码登录后,将自动完成注册。
> —— 腾讯云文档《社区版快速入门》
> 云原生构建为微信扫码登录注册,没有登录密码。
> —— 腾讯云文档《个人中心》
### 唯一会碰到 GitHub 的两个场景,你都不走
1. **用 CNB 的「从 GitHub 导入」一键迁移工具** —— 那个需要 `GITHUB_TOKEN`。
**但你从自建 Gitea 迁,用 `git push --mirror`,不需要它。**
2. **构建时在线拉 GitHub 资源** —— 这正是**已经提前拆掉**的雷:hugo 二进制改为随仓库携带。
### 顺带纠正一个概念
**「账号被标记」≠「IP 被墙」。** 标记是账号级限制;匿名 HTTP 访问 github.com 的公开资源不受影响
(实测国内节点 `curl` hugo release 仍返回 302 正常)。所以即便真要在构建里拉 GitHub,也只是**慢**,不是**不通**。
### 你真正要做的三件准备(都与 GitHub 无关)
1. 微信扫码注册 CNB
2. 微信扫码完成**实名认证**(官方说明:未实名用户禁止写行为 —— 创建仓库 / 推送代码)
3. 先建一个**组织**,仓库建在组织下
---
## 一、决定性发现:答案你自己早就写在代码里了
`deploy.yml` 第 290 行起,那段被 `if: false` 停用的 `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 删掉即可恢复。
```
**当初被迫上中转机,唯一的原因是「runner 在境外」。**
链路是这样被逼出来的:
| 环节 | 为什么存在 |
|---|---|
| runner 在境外 | Gitea 跑在境外 VPS |
| 直传又拍云失败 | 境外 → 又拍云 v0 接口,**跨境 + 并发敏感 → 70 秒 EOF,必败** |
| COS 中转 | 境外的产物,国内机器得有个地方取 |
| 广州中转机 + sync.sh | 只有它在国内,能直传又拍云 |
**CNB 的构建节点就在国内(腾讯云)。** 那句注释的复活条件直接成立 —— 中转机、COS、artifact 传递全都失去了存在理由。
---
## 二、目标架构
```
写作 / 本地
│ git push
▼
CNB(腾讯云,构建节点在国内 4 核 8 GiB)
│ 一次构建,三条出口
├──► 又拍云存储(境内源站) upx sync + upx purge
│ │ 多吉云回源自动跟随
│ └──► 多吉云 CDN 刷新(API 精确刷新)
│
└──► EdgeOne Pages(境外) npx edgeone pages deploy --area overseas
```
**保留不动**(零改动):
- 又拍云存储 `imzql`(境内源站)
- 多吉云 CDN(境内解析,回源又拍云)
- EdgeOne Pages 项目 `hugo-blog`(境外解析,`--area overseas`)
- 域名解析、ICP 备案、Ying 主题、content 内容
**替换掉**:
- Gitea Actions `build` job → CNB 流水线
- act_runner → 不需要
- COS / Gitea artifact 传递 → 不需要(含那串 v3/v4 坑)
- 广州中转机 `sync.sh` 轮询 → 不需要
- `deploy.yml` 里 8 处 `github.server_url` 兼容分支 → 不需要(另写 `.cnb.yml`)
---
## 三、CNB 能力核对(全部官方文档核实)
| 能力 | 值 | 对本项目的意义 |
|---|---|---|
| 构建节点 | 默认 8 核 16 GiB(**内存 = CPU 核数 × 2**) | 全面优于广州机 2 核 1.9 G |
| 免费额度 | **160 核时/月**(0.125 元/核时超出) | 见下方换算 |
| 构建最大时长 | 20 小时;单任务无输出 10 分钟 / 超 1 小时触发超时(可声明,上限 12 小时) | 传 372MB 绰绰有余 |
| 自定义环境 | `docker.image` 或 `docker.build`(Dockerfile) | 可固定 hugo 0.128.2 extended |
| 密钥管理 | 密钥仓库 + `imports` 导入环境变量 | 又拍云/多吉云/EdgeOne 凭据不落仓库 |
| 定时任务 | 支持 cron,最小间隔 5 分钟,时区 Asia/Shanghai | 可替代每日定时构建 |
| **出网限制** | **无白名单限制**(官方:出口 IP 动态,明确不建议白名单) | 又拍云 / 多吉云 / EdgeOne API 都可直连 |
| Docker-in-Docker | 支持(`services: docker`) | 备用 |
| 官方集成 | 腾讯云官方有 **CNB → EdgeOne** 的部署文档示例(`npx edgeone makers deploy`) | 已验证路径 |
### 额度换算(关键决策数字)
| 规格 | 内存 | 每次构建耗(按 10 分钟) | 160 核时可用次数 |
|---|---|---|---|
| 8 核 | 16 GiB | 1.33 核时 | **120 次/月** |
| **4 核** | **8 GiB** | **0.67 核时** | **240 次/月** |
| 2 核 | 4 GiB | 0.33 核时 | 480 次/月 |
你的触发频率是「每天 1 次定时 + 每次发文」≈ **60–90 次/月**。
**建议用 4 核 8 GiB** —— 240 次/月的余量,即使构建耗时翻倍到 20 分钟也还有 120 次。hugo 构建吃 CPU(4 核够并行编译),`upx sync` 吃网络不吃 CPU。
---
## 四、落地骨架
### 4.0 第一步:仓库迁移与远端配置(CNB 主仓 + 自建 Gitea 辅仓)
**目标形态**
```
origin → https://cnb.cool/<组织>/<仓库> ← **主仓**(fetch + push),CNB 构建由它触发
gitea → http://23.254.236.47:3001/zqlit/blog ← **代码同步辅仓**(只推不拉,不参与构建)
```
> **2026-10-06 定案:CNB 主仓 + 自建 Gitea 辅仓,另加一份离线 bundle 备份。**
>
> | remote | 地址 | 角色 |
> |---|---|---|
> | `origin` | `https://cnb.cool/zqlit/blog.git` | **主仓** —— 唯一的 fetch 源,推送即触发 CNB 构建 |
> | `gitea` | `http://23.254.236.47:3001/zqlit/blog.git` | **代码同步辅仓** —— 只作代码留档与找回,不参与构建 |
>
> - **GitHub**(原 `gh` remote):账号被平台标记、仓库被按 AUP 清空 → 已彻底移除,**别再往回加**
> - **Gitee**:单仓 500MB / 单文件 50MB 两道硬门槛过不去(本仓 `bin/linux/hugo` 83.1MB)→ 未采用
>
> **备选异地备份 = 「整仓加密 bundle → 中兴 F50 上的 OpenList」**,与 git 远端无关。
> 它和 Gitea 辅仓不是一回事、也不能互相替代:
> 辅仓保「随时能 `clone` 回来」,bundle 保「平台全挂/账号被封时仍有一份完整历史」。
> 选型依据见 `架构总览.md` §5.3,实现与恢复流程见 §5.6。
`git pushall` 别名 = `git push origin main; git push gitea main` ——
用 `;` 而非 `&&`,主仓失败时辅仓照样推;退出码以主仓为准(辅仓失败不阻断发布)。
**★ 为什么不用「一个 remote 挂多个 pushurl」**(这条教训仍然有效)
早期的 `origin` 就是那种写法(pushurl 同时挂了 GitHub 和自建 Gitea)。但它有个被忽视的缺陷,我做了实测:
| 场景 | 结果 |
|---|---|
| 两个 pushurl 都可用 | ✅ 两个都收到,一次 push 搞定 |
| **第一个失败** | ❌ **git 立即中止,第二个根本不会被推** |
也就是说:**主仓推失败时,辅仓那份备份也不会更新** —— 备份的意义就没了。
所以当时才拆成两个独立 remote,别名里用 `;` 而不是 `&&`,保证互不阻塞。
**现行方案正是回到这个写法**:`origin`(CNB) 与 `gitea`(自建) 各自独立成 remote,
别名 `;` 串联、退出码取主仓 —— 这就是现在的 `git pushall`。
**脚本**(已就位,可直接用)
```bash
bash scripts/setup-cnb-remotes.sh https://cnb.cool/<组织>/<仓库>
```
它会先把你现有的远端配置(含明文凭据)备份到 `.workbuddy-backup/git-remotes.<时间戳>.txt`,
再重建 `origin`、把 `pushall` 设为 `git push origin main; git push gitea main`,
并顺手清掉 `gh` / `gitee` 这两个已退役的远端 —— 全程可回退。
> Gitea 的凭据**不写死在脚本里**,要现给:
> `GITEA_PASS='<Gitea 密码或访问令牌>' bash scripts/setup-cnb-remotes.sh <CNB_URL>`
> 没给就只配 `origin`(`pushall` 里的 gitea 段会被自动跳过)。
**★ CNB 不支持 SSH(官方明确)**
> SSH Not Supported —— 平台不支持 SSH 协议访问。
> 认证方式:用户名**固定填 `cnb`**,密码填**访问令牌**。
> —— docs.cnb.cool《Git Address and Authentication Instructions》
这跟 GitHub 的 SSH 不同,所以两段要各用各的认证:
```bash
git config --global credential.helper manager # Windows 的 Git Credential Manager
# 首次 push 时:用户名 cnb,密码 = 访问令牌(个人设置 → 访问令牌 → 勾「代码仓库/读写」)
```
仓库地址直接用仓库页地址即可(`https://cnb.cool/组/仓`),带不带 `.git` 都支持。
**建仓顺序不能乱**(官方要求)
1. 微信扫码注册 → 2. 微信扫码**实名认证**(未实名禁止建仓/推码)→ 3. 先建**组织**(CNB 无「个人私有仓库」,仓库必须挂在组织下)→ 4. 建仓库 → 5. 跑上面的脚本 + push
### ⚠️ 推 CNB 之前必须先知道的敏感文件(已审计)
这些文件**当前就被 git 跟踪**,会在首次 push 时连**全部历史**一起进 CNB:
| 文件 | 内容 | 状态 |
|---|---|---|
| `write-server/nginx/ssl/privkey.pem` | **真实 TLS 私钥**(EC P-256,`BEGIN PRIVATE KEY`),2026-06-22 起在库 | 已跟踪 |
| `.env` | `SILICONFLOW_API_KEY`、`ZHIPU_API_KEY` | 已跟踪 |
| `GITEA_SECRETS.md` | Gitea 相关凭据说明 | 已跟踪 |
| `content/posts/2024/.../setup-secrets.png` | 疑似密钥配置截图 | 已跟踪 |
**关于暴露面**:该仓一直是**私有**的(匿名 API 返回 404)。2026-10-06 账号被平台标记后
仓库更被按 **AUP 合规**清空、本地也移除了 `gh` remote → **这条威胁面已彻底消失**。
**但两件事仍建议做掉**:
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
```
⚠️ 注意:`.gitignore` 里其实**已经有** `.env` / `.env.*` 规则 —— 它们失效的原因不是规则写错,
而是**忽略规则对「已跟踪」文件无效**,所以必须显式 `git rm --cached`。
(另:`.gitignore` 里那条 `write\.env` 是写错的 —— gitignore 中 `\` 是转义符,
它实际匹配文件名 `write.env` 而非 `write/.env`;幸好 `write/.gitignore` 里有 `.env*` 兜住了。)
**历史清理(`git filter-repo`)的判断已变**:当初不做的唯一顾虑是「会让备份分叉」——
2026-10-06 GitHub 那份已消失,**这个顾虑没有了**;且离线 bundle 已加密
(见 `架构总览.md` §5.6),不受影响。
所以**现在是彻底抹掉这些文件历史的最佳窗口**。
代价:全部提交 SHA 改变、自建 Gitea 那份需重推。
---
### 4.1 `deploy/Dockerfile`(构建环境)
CNB 默认镜像不含 hugo / upx,写一个专属镜像固定版本:
```dockerfile
FROM node:22-bookworm-slim
# 换国内 apt 源(CNB 只明示加速 Docker/NPM/Maven,apt 默认源在国内节点可能慢)
RUN set -eux; \
for f in /etc/apt/sources.list /etc/apt/sources.list.d/debian.sources; do \
if [ -f "$f" ]; then \
sed -i -e 's|deb.debian.org|mirrors.aliyun.com|g' \
-e 's|security.debian.org|mirrors.aliyun.com|g' "$f"; \
fi; \
done; \
apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl tar zstd python3 xz-utils git \
&& rm -rf /var/lib/apt/lists/*
# Hugo extended 0.128.2(linux/amd64)——★ 随仓库携带,构建时零跨境下载
COPY bin/linux/hugo /usr/local/bin/hugo
RUN chmod +x /usr/local/bin/hugo && hugo version
# 又拍云 upx
RUN set -eux; \
curl -fsSL -o /tmp/upx.tgz \
"https://collection.b0.upaiyun.com/softwares/upx/upx_0.4.9_linux_amd64.tar.gz"; \
tar -xzf /tmp/upx.tgz -C /usr/local/bin upx; \
chmod +x /usr/local/bin/upx; \
rm /tmp/upx.tgz
# EdgeOne CLI
RUN npm i -g edgeone@latest
CMD ["hugo", "version"]
```
> ✅ 已实测(2026-10-04):
> 1. **hugo 二进制改为随仓库携带**(`bin/linux/hugo`,84MB,进 git 后仅 24MB)。实测国内节点直连 GitHub 拉这个 release 要 **127 秒**(本机只要 7 秒),而每次构建都要付这个成本 → 自带更稳。
> 2. 又拍云 `upx` 的下载源是 `collection.b0.upaiyun.com`(国内 CDN),直连稳定,保留在线安装。
> 3. **架构不能混用**:`E:\Hugo\hugo.exe` 是 `windows/amd64`(本地预览用),CNB 构建节点是 `linux/amd64`(`bin/linux/hugo`),两者互不替代。
> 4. ~~若以后主仓改选 Gitee(单仓 500MB),这 84MB 会挤占配额~~ —— **2026-10-06 定案:不走 Gitee**(它卡单文件 50MB / 单仓 500MB 两道门槛)。现行辅仓是**自建 Gitea**(自己的机器,没有配额这回事,见 §4.0),离线备份走 OpenList 上的加密 bundle(`架构总览.md` §5.6)。这条约束自然消失,`bin/linux/hugo` 继续随仓库携带即可。
> 5. **apt 源改阿里云镜像**:CNB 官方只明示加速「Docker / NPM / Maven 镜像」,Debian 默认 apt 源不在承诺范围内,而构建节点在国内 → 换阿里云兜底,避免构建卡在 `apt-get update`(首轮构建时留意这一步耗时)。
### 4.2 `.cnb.yml`(流水线)
```yaml
main:
push:
- name: 构建并发布(境内外双线路)
imports:
# CNB「密钥仓库」(私有;禁止 clone、只能 Web 编辑),存放 10 项凭据。
# 已在 .cnb.yml 里填好:zqlit/blog-secrets,本地草稿见 cnb-secrets.yml(已 gitignore)
- https://cnb.cool/zqlit/blog-secrets/-/blob/main/secrets.yml
runner:
cpus: 4 # 4 核 → 8 GiB
docker:
build: deploy/Dockerfile
stages:
- name: Hugo 构建
script: |
set -euo pipefail
hugo version
python3 scripts/add_draft_to_hidden.py
rm -rf public resources/_gen
hugo --minify --gc
BUILD_HASH=$(find public -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum | awk '{print $1}')
echo "$BUILD_HASH" > public/.build_hash
echo "$BUILD_HASH" > public/build_hash.txt
echo "$BUILD_HASH" > .build_hash.out
echo "✅ 构建完成 hash=${BUILD_HASH:0:12} 文件数=$(find public -type f | wc -l)"
- name: 同步到又拍云(境内源站)
script: |
set -euo pipefail
upx login "$UPYUN_SERVICE" "$UPYUN_OPERATOR" "$UPYUN_PASSWORD"
upx sync public/ / -w 5 --delete
upx put public/.build_hash /__build_hash || true
echo "✅ 又拍云同步完成"
- name: 刷新又拍云 CDN
script: |
set -euo pipefail
# ★ 必须刷固定入口页:只刷首页会让 sitemap.xml / rss.xml /
# archives.html 一直发 CDN 里的旧副本(2026-10-01 实测复现)
{ echo "https://usj.cc/";
for p in sitemap.xml rss.xml archives.html posts.html comment.html links.html; do
echo "https://usj.cc/$p"
done
} > /tmp/purge.txt
upx purge --list /tmp/purge.txt \
|| echo "⚠️ purge 失败,降级不影响源站已更新"
- name: 刷新多吉云 CDN
script: |
set -euo pipefail
python3 - <<'PY'
import hmac, hashlib, json, os, urllib.request
AK, SK = os.environ['DOGE_AK'], os.environ['DOGE_SK']
SITE = 'https://usj.cc'
P = '/cdn/refresh/add.json'
urls = [SITE + '/', SITE + '/sitemap.xml', SITE + '/rss.xml',
SITE + '/archives.html', SITE + '/posts.html']
def refresh(rtype, u, label):
body = json.dumps({'rtype': rtype, 'urls': json.dumps(u)})
sig = hmac.new(SK.encode(), (P + '\n' + body).encode(), hashlib.sha1).hexdigest()
req = urllib.request.Request(
'https://api.dogecloud.com' + P, data=body.encode(),
headers={'Authorization': 'TOKEN %s:%s' % (AK, sig),
'Content-Type': 'application/json'}, method='POST')
with urllib.request.urlopen(req, timeout=30) as r:
print('%s -> %s' % (label, r.read().decode()[:200]))
refresh('path', [SITE + '/'], '目录刷新')
refresh('url', urls, '精确刷新 %d 条' % len(urls))
PY
- name: 部署 EdgeOne(境外线路)
script: |
set -euo pipefail
test -n "$EDGEONE_API_TOKEN" || { echo "缺 EDGEONE_API_TOKEN"; exit 1; }
npx edgeone pages deploy ./public -n hugo-blog -t "$EDGEONE_API_TOKEN" --area overseas
- name: 上报部署状态
script: |
set -euo pipefail
H=$(cat .build_hash.out)
curl -s --max-time 20 -X POST \
"https://api.200181.xyz/api/deploy-status?token=${RSS_API_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"hash\":\"$H\",\"phase\":\"built\"}" || true
curl -s --max-time 20 -X POST \
"https://api.200181.xyz/api/deploy-notify?token=${RSS_API_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"hash\":\"$H\"}" || true
```
---
## 五、两个必须复刻的细节(`sync.sh` 里的血泪)
### 5.1 purge 不能只刷首页
`sync.sh` 的注释记录了一个实测坑:
> 历史写法只有 `upx purge "$SITE/"`,即只刷首页 —— `sitemap.xml` / `rss.xml` /
> `archives.html` / `posts.html` 这些「老路径」会一直发 CDN 里的旧副本。
> 2026-10-01 实测复现:中转机产物 sitemap.xml 已含新文章(22812B),
> CDN 上那份却仍是 7/24 的 22592B;手动 purge 后立刻变新。
所以骨架里固定刷「首页 + 6 个入口页」。**进阶做法**(完整复刻 `sync.sh` 的精确刷新):
把文件清单存到又拍云 `/__manifest.txt`,每次 CNB 拉下来与本次产物比对,得出「变化 URL」精确刷新。
好处是新增文章时只刷那几页;代价是多维护一个清单文件。**建议先跑固定入口版,观察 1–2 周再决定要不要加。**
### 5.2 又拍云并发参数
`sync.sh` 用 `-w 10 --strong`。当时失败是因为**跨境**(境外 runner 直传),国内直传理论无此问题。
但建议首次仍保守用 `-w 5`(去掉 `--strong`,它会把请求量放大),实测稳定后再往上调。
---
## 六、砍掉清单
| 砍掉 | 说明 |
|---|---|
| act_runner | 不再需要自建 runner |
| Gitea artifact 传递链 | 含 `upload-artifact@v3/v4` 那串坑 |
| 腾讯云 COS | 中转存储不再需要 |
| 广州中转机 `sync.sh` + 1Panel 定时任务 | **注意:只停任务,机器上还有 mysql/openresty 等 7 个容器** |
| `deploy.yml` 的 8 处 `github.server_url` 补丁分支 | `.cnb.yml` 重新写 |
| 两套冗余通知(TG/飞书/邮件三选一) | 独立小优化 |
**发布链路:7 环节 → 3 环节**(写作 → CNB 构建 → 双线上线)
**自维护构建节点:1 台 → 0 台**
---
## 七、前置动作与待核实项
### 必须先确认(按重要性)
1. **★ EdgeOne 从国内上传 372MB 的耗时**。现在部署是从境外 runner 上传(同区域快),改到 CNB(国内)后是跨境上传到 EdgeOne 国际站。**这是本方案最大的未知数**,必须实测一次。
2. **★ CNB 构建节点能否顺畅拉取 `github.com` 的 hugo release**。不行就换国内镜像源,或把 hugo 二进制预置进 CNB 制品库。
3. **又拍云 v0 接口从 CNB 出口的真实表现**。国内直传预期无碍,但需实测确认(这是整个方案的立论基础,务必第一次就验)。
4. 主仓放哪:
- **CNB 当主仓** → 最简,Gitea 可退役,写作后台改 `origin`
- **Gitea 当主仓 + push mirror 到 CNB** → 写作零改动,代价是多留一台机器
5. 多吉云刷新配额(`sync.sh` 里有 200 条截断逻辑,CNB 版本固定入口页不触及)。
### 建议的验证步骤(不动任何现有东西)
1. 建一个 CNB 测试仓库(或就用一个分支),放最小化 `.cnb.yml` + Dockerfile
2. 跑通「hugo build」→ 确认镜像与构建时长
3. 单独测「`upx sync` 到一个测试 bucket」→ 确认国内直传可行
4. 单独测「`edgeone pages deploy` 到测试项目」→ 拿到跨境上传耗时
5. 四项全绿,才动正式仓库
---
## 八、与之前几份方案的关系
| 方案 | 结论 | 与本方案的关系 |
|---|---|---|
| `代码源与构建平台选型.md` | CNB 首选(100GiB 仓库 + 160 核时) | 本方案是它的落地细化 |
| `Gitee方案评估.md` | 可用,但需瘦身 588MB → 500MB | **用 CNB 就不用瘦身** |
| `EdgeOne双区域方案评估.md` | 建议 `-a global` 或双项目 | 双区域照旧,只是构建方换成 CNB |
| `阿里云ESA评估.md` | 2000 文件上限卡死 | 不适用 |
| `精简方案-只留CF和Hugo.md` | 只留 CF 会丢又拍云+多吉云 | **本方案保留双 CDN,复杂度收益同样大** |
**核心区别**:之前几份在讨论「CDN 选谁」;本方案证明**你不需要换 CDN,只需要换掉「构建 + 搬运」那一层**。而这一层恰恰是复杂度的大头。