用户四问:「把项目文件全部理一遍 还有readme文件」「gitea 后面可能还要迁移 帮我写一个迁移文档」
「证书管家做完了吗」「感觉现在架构还是复杂了」
一、文档梳理:根目录 15 个 md → 2 个
根目录只留 README.md(入口)+ 架构总览.md(现状唯一事实源),其余全部归位:
docs/
├── README.md ← 新增:文档地图(入口 / 当前有效 / 历史归档 三层)
├── 证书管家.md ← 原「函数版证书管家-方案.md」改名
│ (它早已是「现状+沿革」文档,标题名不副实 —— 签发早已不在 CF Worker)
├── Gitea迁移指南.md ← 新增
├── 架构精简候选.md ← 新增
├── CNB构建落地方案.md / GITEA_SECRETS.md
└── archive/{平台与选型,功能与修复,主题与内容}/ ← 32 份
- 32 个归档文档统一加 `> 📦 本文档已归档` banner,并指向架构总览
—— 这个仓库历史文档里全是「权威口径」,混看很容易拿废弃结论当现状
- gitea-backup/ → deploy/gitea/(原名「backup」不准,它是部署包;与其它 deploy 单元并列)
- 新增 md 链接校验(python 脚本,中文路径用 sed 不安全)→ 首轮 18 处断链
(多一层目录要让 banner 里的相对路径补一个 ../)→ 修正后 0 断链
二、新增 docs/Gitea迁移指南.md(★ 有死线:境外 VPS 11 月到期)
★ 核心价值是「指出迁移面已大幅缩小」:仓库原有的 deploy/gitea/迁移前检查清单.md
(2026-10-04)是为「Gitea + act_runner + 又拍云同步 *整套* 搬迁」写的,**那个前提已经不存在**——
构建归 CNB,runner / upyun-sync / 中转机 / COS 都不需要了。迁移实际只剩「搬数据卷 + 改地址」。
★★ 全文最重要:本仓有 9 处写死了旧地址,按易漏程度排序(1-3 本机,4-5 线上)
1 本机 .git/config 的 gitea remote
2 scripts/setup-cnb-remotes.sh 的 GITEA_URL 默认值
3 deploy/editor-api/bootstrap.sh 的 GITEA_URL 默认值
4 ★ /srv/editor-api/.env 的 GITEA_URL ← 漏了会「持续报错但只警告不阻断」,没人发现
5 ★ /srv/blog 的 gitea remote ← 同上
6 docs/GITEA_SECRETS.md
7-9 架构总览.md / README.md / editor-api/README.md
(不用改:docker-compose.editor.yml、server.mjs、pushall 别名 —— 只引用 remote 名字)
另含:建议新实例改用域名而非 IP(以后搬机器只改 DNS;⚠️ Gitea 28 起只读 ROOT_URL 不读
[server] DOMAIN);rsync 而非 dump(属主必须 uid/gid 1000);切换顺序「先建新的→验证→再拆旧的」;
验证清单强调 `git push gitea --dry-run`;第九节给出「干脆不留这个辅仓」的选项与判断依据。
顺带修掉两个硬伤:
- deploy/gitea/docker-compose.yml 的镜像 tag `1.28.0-rootless` 根本不存在
(Gitea 28 起去掉 1. 前缀)→ 改 28.0.0-rootless(pin 死,不用 latest)
- deploy/gitea/.gitignore 漏了 backups/(跑一次备份就会把含 secrets 的 dump 写进历史)
三、证书管家核实(结论:已上线运行,2 个遗留)
线上实测:容器 Up (healthy);/preflight 7/7 全过;3 组域名;下次自动续期 04:10。
遗留 ① writeapi.usj.cc 证书没纳管(真问题):nginx 配置在 /www/conf.d/writeapi.usj.cc.conf,
不在 /www/sites/ 管理树里 → 1Panel 的 ssl/upload+sslID 物化碰不到它。
实测仍是 RSA / CN=usj.cc / 到期 2026-12-07,本项目续期不会更新 → 12 月会断。
遗留 ② dnsapi.usj.cc 没上 HTTPS。
(澄清假问题:200181.xyz 公网看到的 LE 证书是 CF 自家边缘证书,与本项目无关)
四、新增 docs/架构精简候选.md(回应「架构还是复杂了」)
结论:复杂度不在件数,在「跨 6 个环境,其中 4 个要自己维护」。国内机 9 个容器里属于本项目的
只有 3 个,能动的只有 2 个。5 个候选 + 建议执行顺序:
1 停 certimate 容器(工作流已全停用)——先 stop 观察,别急着删
2 cn-dns-helper 大概率已是遗留(「Worker 侧签发」时代产物;现在签发在国内机且自带 dnsprovider,
/preflight 显示 CF 凭据可读;Worker 侧 dnsremoted.ts 已无任何引用)
3 Gitea:迁 or 不留
4 两套写作前端收敛(本轮不动,先看使用频率)
5 writeapi.usj.cc 证书纳管(12 月死线)
并明确列出 6 项「必要复杂度,不建议动」。
五、架构总览补齐证书管家子系统(★ 之前完全缺席)
一个完整子系统在「事实源文档」里一个字都没有 —— 这本身就是文档债。补:
- §0 一句话(四→五个子系统)、§1.1 子系统表、§1.2 地址地图两行
- **新增 §5.7 证书管家**:职责划分 / 为什么非要这么分(Worker 免费版 CPU 10ms)/
三条不能破的红线(Worker 不得签发 · 证书路由只认 Bearer 会话 · 角色必须正着枚举放行)/
纳管域名表 / 与 certimate 的关系 / 已知遗留
- .cnb.yml 行数 284 → 337(数字漂了);头部更新时间 → 2026-10-06;附录表改指 docs/
- §6 待办:#13(证书遗留)、#14(复杂度盘点)新增;#7 改写为「Gitea 11 月到期,★ 有死线」
六、记忆
.workbuddy/memory/MEMORY.md 新增「文档结构」「Gitea 迁移死线」两节;证书管家节补实测与遗留。
464 lines
23 KiB
Markdown
464 lines
23 KiB
Markdown
# 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` | 已跟踪 |
|
||
| `docs/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 docs/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,只需要换掉「构建 + 搬运」那一层**。而这一层恰恰是复杂度的大头。
|