docs: 迁 CNB 前的架构决策文档 + 构建骨架

- 决策文档:架构总览 / 代码源与构建平台选型 / CNB构建落地方案 /
  EdgeOne双区域 / Gitee / 阿里云ESA / 精简方案
- CNB 构建骨架:deploy/Dockerfile、bin/linux/hugo
  (84MB,linux/amd64 extended 0.128.2,供 CNB 容器 COPY 用)
- scripts/setup-cnb-remotes.sh:双远端切换(CNB 主仓 + GitHub 备份)
- .gitignore 补 .workbuddy/(工作数据不入库)
This commit is contained in:
zqlit committed 2026-10-04 12:40:17 +08:00
1 parent 1f568bbfd1
commit e0218b42bb
14 files changed
+1993 -4

No files matched your search

+2
View File
@@ -11,3 +11,5 @@ write\.env
# Claude Code 临时 worktree(曾被误当 submodule 提交) # Claude Code 临时 worktree(曾被误当 submodule 提交)
.claude/worktrees/ .claude/worktrees/
.workbuddy-backup/ .workbuddy-backup/
# WorkBuddy 工作数据(记忆/日志/会话),不入库
.workbuddy/
+436
View File
@@ -0,0 +1,436 @@
# 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 主仓 + GitHub 备份)
**目标形态**
```
origin → https://cnb.cool/<组织>/<仓库> ← 主仓(fetch + push),CNB 构建由它触发
gh → git@github.com:zqlit/blog.git ← 备份(push),只存档不参与构建
gitea → 自建 Gitea ← 暂时留着当只读参考,链路稳定后手工删
```
一条命令推两段:`git pushall`(等价于 `git push origin main; git push gh main`)。
**★ 为什么不用「一个 remote 挂多个 pushurl」**
现有 `origin` 就是那种写法(pushurl 同时挂了 GitHub 和 自建 Gitea)。但它有个被忽视的缺陷,我做了实测:
| 场景 | 结果 |
|---|---|
| 两个 pushurl 都可用 | ✅ 两个都收到,一次 push 搞定 |
| **第一个失败** | ❌ **git 立即中止,第二个根本不会被推** |
也就是说:**CNB 推失败时,GitHub 那份备份也不会更新** —— 备份的意义就没了。
所以拆成两个独立 remote,别名里用 `;` 而不是 `&&`,保证互不阻塞。
**脚本**(已就位,可直接用)
```bash
bash scripts/setup-cnb-remotes.sh https://cnb.cool/<组织>/<仓库>
```
它会先把你现有的远端配置(含明文凭据)备份到 `.workbuddy-backup/git-remotes.<时间戳>.txt`,
再重建 `origin`/`gh` 并注册 `pushall` 别名 —— 可回退。
**★ 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` | 疑似密钥配置截图 | 已跟踪 |
**好消息**:已核实 `https://github.com/zqlit/blog` 返回 **Page not found**,仓库不可公开访问
→ **私钥没有被公开暴露**,无需紧急处置。
**但两件事建议顺手做掉**:
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`):会重写 955 个提交、让 GitHub 备份随之分叉,
收益(去掉一个未公开的私钥)远小于风险。正确做法是**轮换凭据**而不是重写历史。
---
### 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(git 内 24MB)会挤占配额,届时改用 CNB/又拍云对象存储托管该二进制,别放仓库。
> 5. **apt 源改阿里云镜像**:CNB 官方只明示加速「Docker / NPM / Maven 镜像」,Debian 默认 apt 源不在承诺范围内,而构建节点在国内 → 换阿里云兜底,避免构建卡在 `apt-get update`(首轮构建时留意这一步耗时)。
### 4.2 `.cnb.yml`(流水线)
```yaml
main:
push:
- name: 构建并发布(境内外双线路)
imports:
# 密钥仓库(私有),存放 UPYUN_* / DOGE_* / EDGEONE_API_TOKEN
- https://cnb.cool/<你的组织>/<密钥仓库>/-/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,只需要换掉「构建 + 搬运」那一层**。而这一层恰恰是复杂度的大头。
+234
View File
@@ -0,0 +1,234 @@
# EdgeOne 双区域方案评估
> 2026-10-04 · 针对「境外用 EdgeOne 国际站、境内改用 EdgeOne 国内站」的构想
> 以及「write-server 拆成后端服务 + 前端与 blog-admin/Worker 合并」的可行性
## 一、结论先说
**方案成立,而且比 Cloudflare 方案好。** 三个理由:
1. **不违反任何服务条款** —— CF 要加速国内只能走「优选 IP」那条明文违规的路,EdgeOne 是正规产品能力。
2. **国内速度是量级差异** —— 备案域名的 EdgeOne 大陆节点实测 **50–90ms**;你现在 CF 后端实测 **750ms**(差 8–10 倍)。
3. **你已经在用了** —— `deploy-edgeone` job 现在就部署在这上面(`edgeone pages deploy --area overseas`),迁移成本接近零。
**而且我发现了你可能没注意到的一件事**:EdgeOne Pages 有个 `--area` 参数,你现在用的是 `--area overseas`(**主动排除了中国大陆**)。这可能意味着你**只要改一个参数**就能达到目的,而不必新建一个项目。详见第三章。
---
## 二、你的备案状态(关键前提,已核实)
- 站点页脚:**鄂ICP备2022015735号-1**
- 官方文档明确:*当项目的加速区域为「中国大陆可用区」或「全球可用区(含中国大陆)」时,添加的域名必须完成 ICP 备案*
- **你的域名已备案 → 具备启用大陆节点的资格** ✅
这一条是整个方案成立的地基。反过来说,如果没备案,这条路直接堵死(只能用海外节点,国内 670–1400ms)。
---
## 三、★ 关键发现:`--area` 参数
EdgeOne Pages 的 CLI 部署命令带一个区域参数:
| 参数 | 含义 |
|---|---|
| `--area overseas` | 只覆盖境外,**不含中国大陆** ← **你现在用的就是这个** |
| `-a global` | 覆盖全球 **+ 中国大陆**(需备案域名绑自定义域名) |
也就是说,你现在这个 `hugo-blog` 项目**是刻意把大陆排除在外的**。于是有两条路:
### 路 1(最省):单项目改参数
把 CI 里那一行改成 `-a global`,一个项目、一套配置、一个 token,同时覆盖境内外。
- 好处:**零新增组件**,改动只有一个参数
- 风险:**境外质量可能下降**。有多位站长实测反馈,global 模式下「海外就差点意思,只有一个节点,走 Anycast 网络」——这可能正是你当初选 `overseas` 的原因
- **必须实测**:改完之后用境外节点测一次,别只看国内变快了
### 路 2(你提的):双项目
- **国际站项目**(`console.intl.cloud.tencent.com`,免备案)→ `--area overseas`,保境外质量
- **国内站项目**(`console.cloud.tencent.com`,需实名+备案)→ `-a global`,保大陆速度
- 两个控制台、两套 token、两次部署
> ⚠️ **一个待实测的疑点**:官方文档里,国际站的 Pages 与国内站的 Pages 对「大陆节点」的支持描述**互相矛盾**(国际站文档说 `-a global` 含大陆,产品介绍又说国际站不含国内节点)。**结论不明,必须自己测一次**。测法见第七章。
---
## 四、EdgeOne Pages 免费额度(官方定价页)
| 项目 | 免费额度 | 你的用量 | 够吗 |
|---|---|---|---|
| 安全加速流量 | **不限** | — | ✅ |
| 安全加速请求数 | **不限** | — | ✅ |
| 构建次数 | **500 次/月** | 约 60–90 次 | ✅ |
| Edge Functions 请求 | 300 万/月 | — | ✅ |
| Cloud Functions 请求 | 100 万/月 | — | ✅ |
| KV 存储 | 1 GB | — | ✅ |
| 站点数 | 1 个主域名 + 200 子域名 | — | ✅ |
| 超出用量 | **收费版推出前不中断服务** | — | ✅ |
**唯一的硬限制(重要)**:
> **单文件、单线程限速 4 Mbps(约 500 KB/s)** —— 多位站长实测确认,官方未披露。
要正确理解它:
- 是**单文件单线程**限速,**不是整站带宽限速**。并发请求互不影响
- 你的 `zql-v2.woff2` 字体 1.2 MB → 首次加载约 **2.4 秒**(之后走缓存,不再走网络)
- 那 7 个 mp4 里最大的 12.8 MB → 首次加载约 **25 秒**(这是这个方案最实际的痛点)
- 腾讯云自家 CDN 也有这个限制,属地是「免费套餐的取舍」,不是 bug
**如果视频是核心内容**,这一条要认真权衡;如果只是少量点缀,影响可控。
---
## 五、能砍掉什么
对比**现在的架构**(EdgeOne 国际 + 又拍云 + 多吉云 + 广州中转机 + sync.sh 轮询):
| 组件 | 现架构 | EdgeOne 双区域 |
|---|---|---|
| 境外 CDN | EdgeOne 国际站 | EdgeOne 国际站(不变) |
| 境内 CDN | 又拍云 + 多吉云 | **EdgeOne 国内站** |
| 产物搬运 | 广州中转机 + `sync.sh` 每分钟轮询 | **删除** |
| 产物中转 | 腾讯云 COS | 已砍(进行中) |
| 凭据面 | 又拍云密码 + 多吉云 SK(明文写在 sync.sh) | **删除** |
| 厂商数 | EdgeOne + 又拍云 + 多吉云 + 腾讯云 COS | **腾讯云一家** |
**净收益**:少 2 家厂商、少 1 台常驻机器、少 1 个每分钟跑的轮询脚本、少 2 套硬编码凭据。
**发布链路从 7 环节降到 4 环节**(写作 → 构建 → 双区域部署 → 通知)。
---
## 六、write-server 拆分评估(你问的第二件事)
### 6.1 结论:技术可行,而且**必须拆**才能上边缘平台
这不是"能不能"的问题,而是**只能这样**。EdgeOne Pages(和 CF Workers 一样)的运行时限制是硬的:
- **无持久化文件系统** —— 无法读写 `content/` 下的 `.md`
- **不能执行 git** —— 无法 `git pull/push`
- 单次函数执行有超时(通常 30s)
而 `write-server` 的核心恰恰是这两件事:**读写本地 md 文件 + git 操作**。
### 6.2 现状的耦合点(已扫描)
```
write-server/src/
app/{edit,login,posts,images,links,feeds,comments,users,bot,wechat}/ ← 页面(可上前端)
app/api/ ← 23 个 API 路由
lib/
posts.ts ← fs 读写 md ⛔ 必须留后端
git.ts ← child_process git ⛔ 必须留后端
hugo.ts ← 调 hugo 二进制 ⛔ 必须留后端
recycle.ts ← fs ⛔ 必须留后端
auth.ts ← fs(用户表) ⛔ 必须留后端
bot/* ← 子进程轮询 ⛔ 必须留后端
ai.ts / artalk.ts / rssapi.ts / wechat.ts / fetch.ts ← 纯 HTTP(可两边)
```
**好消息**:页面与 API 已经分得比较清楚,`src/lib` 里哪些能上边缘、哪些必须留后端,界限是清晰的。这不是一个烂摊子。
### 6.3 拆分后的形态
```
前端(EdgeOne Pages / CF Worker)
React SPA(由现有页面改造)+ 调用后端 API
│
▼
后端(一台有文件系统的服务器)
Node 服务 + 复用现有 src/lib(几乎不用改)
→ 读写 md、跑 git、调 hugo、发 RSS
```
必须改的四件事:
1. **认证**:现在是同域 session/cookie,分离后要改成 token(Bearer / JWT)+ CORS
2. **数据获取**:服务端组件直读 md → 改成客户端 fetch API(多一次往返,但要加 loading 态)
3. **图片上传**:写本地磁盘 → 改成对象存储(又拍云 / R2 / EdgeOne KV)
4. **API 地址配置化**:`src/lib/config.ts` 里加后端 base URL
### 6.4 ⚠️ 但先问一句:**为什么要拆?**
如果目的是**"一个统一的管理入口"**(体验问题),那拆分会**让架构变复杂**——从 1 个应用变成 2 个(前端 + 后端),组件数不减反增。这跟你要的"简化"是反方向。
**更省的做法(零重构)**:用**一个域名 + 路径分流**把两个后台挂在一起。
```
admin.usj.cc/ → blog-admin(CF Worker,评论/RSS 管理)
admin.usj.cc/write/ → write-server(写作后台)
```
三种实现,任选:
- **OpenResty 反代**(你国内机已经有):加几行 `location /write/ { proxy_pass ...; }`,**成本最低**
- **EdgeOne 规则引擎**:用 URL 重定向 / 路径规则做分流
- **CF Worker 薄代理层**:能做,但 CF 国内慢,不适合做入口
**判断标准**:
- 只要"一个入口" → **反代,别重构**
- 真的需要"前端独立部署到边缘、后端瘦成纯 API" → 才做拆分,且要接受工作量和组件数增加
---
## 七、建议的推进顺序
### 第 0 步(今天就能做):一次实验,验证单项目可行性
这是**成本最低、信息量最大**的一步,只需改一个参数:
```bash
# 用已有的 token,把产物部署到 global 区域(覆盖大陆)测一次
npx edgeone pages deploy ./public -n hugo-blog -t "$EDGEONE_API_TOKEN" -a global
```
然后用国内机实测(可复用我之前写的 `probe_speed.py` 那套):
- 大陆解析到的 IP 是不是国内 IP
- TTFB 是否降到 100ms 级
**如果单项目 `-a global` 就达标 → 方案收敛为「改一个参数」,收工。**
如果不达标或影响境外 → 再走双项目。
### 第 1 步:确定区域策略
根据第 0 步结果,二选一:
- 单项目 `-a global`
- 双项目(国际站 + 国内站,两套 token)
### 第 2 步:接上大陆域名
国内站的 Pages 项目绑定自定义域名,走 ICP 备案校验(你已有备案号)。
### 第 3 步:砍掉旧链路
确认 EdgeOne 双区域稳定运行 **1–2 周**后,再依次停掉:
- 广州中转机的 `sync.sh` 定时任务(1Panel 里 `id=15` 那个每分钟的)
- 又拍云、多吉云的同步逻辑
- `deploy.yml` 里对应的通知与检查步骤
**不要跳步**,也不要和"write-server 重构"混在同一次变更里。
### 第 4 步(可选,独立评估):write-server
先回答 6.4 那个问题。如果要拆,它是一次独立的重构工程,单独排期。
---
## 八、待核实项(我标出来,不装作确定)
| # | 事项 | 状态 | 怎么验 |
|---|---|---|---|
| 1 | 国际站 Pages 的 `-a global` 是否真能启用大陆节点 | **文档矛盾,待实测** | 第七章第 0 步 |
| 2 | `--area overseas` 当初是否是刻意选择(境外质量考虑) | 待你确认 | 问你自己 |
| 3 | 500KB/s 单文件限速对 7 个 mp4 的实际影响 | 已知限制,待评估 | 测一个最大视频的首字节 |
| 4 | 国内站的备案接入校验是否需要变更接入商 | 待核实 | 绑定域名时会暴露 |
| 5 | EdgeOne Pages 的 Git 集成是否支持 Gitee | 有第三方提到,**未官方确认** | 若支持,可再砍掉 Gitea + act_runner |
> 第 5 条如果成立,价值很大:**Gitee 是国内平台**,用它的 Git 集成自动构建,
> 就能把自建 Gitea + act_runner + 中转机整条链一起砍掉。但需要先确认支持情况。
---
## 九、一句话总结
**你的方向是对的,而且比 CF 方案好——不违规、快 8–10 倍、你已经在用了。**
**但在动手建第二个项目之前,先花十分钟做第七章那个「改一个参数」的实验。**
它可能直接告诉你:**这件事根本不需要两个项目。**
+156
View File
@@ -0,0 +1,156 @@
# Gitee 方案评估
> 回答:**「Gitee 算不算一个替代方案?」**
>
> **结论**:算,而且它是唯一一个**官方支持作为 EdgeOne Pages 代码源**的国内平台。
> 但有一道硬门槛 —— **Gitee 免费版单仓库上限 500MB,你的 `.git` 实测 588MB,超了 17.6%**。
> 好消息是:**这道门槛跨得过去**(见第四节)。真正要权衡的是另一件事 —— **内容审查**。
---
## 一、实测数据(刚量的,不是估算)
| 指标 | 实测值 |
|---|---|
| `.git` 总体积 | **588 MB** |
| `git count-objects` size-pack | **580 MB**(对象净体积,说明已经打包过了) |
| 提交总数 | 955 |
| **HEAD 中被跟踪文件合计** | **372.9 MB / 2427 个文件** |
| HEAD 中 >1MB 的文件 | 65 个,合计 143.9 MB |
**关键推论**:`580MB(pack) − 373MB(HEAD) ≈ 207MB` 是**历史里的旧版本 / 已删除对象**。
也就是说,**光留当前状态只需要 373MB** —— 这一条决定了瘦身可行。
---
## 二、四条硬指标逐项核对
Gitee 官方配额(help.gitee.com/account/usage-quota):
| Gitee 免费版限制 | 官方值 | 你的情况 | 判定 |
|---|---|---|---|
| 单仓库容量 | **≤ 500 MB** | 588 MB | ❌ **超限 17.6%** |
| 单文件大小 | ≤ 50 MB | 最大 12.8 MB(mp4) | ✅ 安全 |
| 用户总仓库容量 | 5 GB | 588 MB(瘦身后 373 MB) | ✅ 安全 |
| 私有仓库协作人数 | 5 人 | 个人 | ✅ 无关 |
| 仓库数量 | 1000 个 | 1 个 | ✅ 无关 |
**只有一条不过:仓库体积。** 而且它刚好卡在一条"能解"的线上。
---
## 三、一个被大多数人算错的点:Gitee 方案**不消耗** Gitee Go 的额度
Gitee Go 免费额度很小(单仓库 200 分钟永久 + 每月 500 分钟)。**但这跟你的方案无关** ——
因为你走的是 **EdgeOne Pages 的 Git 集成**:代码放 Gitee,**构建跑在 EdgeOne 侧**(4 核 6GB,500 次/月)。
Gitee 在这里**只当代码网盘**,一次构建都不消耗它的 CI 分钟。
> ⚠️ 同理:**不要用 Gitee Pages**。它的构建能力和额度都远不如 EdgeOne,
> 而且 2022 年那次全站审查的触发点之一就是 Pages。
---
## 四、唯一的硬伤与解法:588MB → 500MB 以下
`.git` 588MB,其中 **207MB 是历史垃圾**(旧版本 + 已删除文件)。三种解法:
### 方案 A:只保留当前状态(最彻底,推荐)
重建一个只有 HEAD 的仓库 → `.git` 降到 **≈ 373MB**,**留 25% 余量**。
代价:**955 个提交历史会丢**。
但这在你这儿几乎无损失 —— 历史仍在自建 Gitea 那边完整保留,Gitee 只当"构建用的镜像"。
```bash
# 思路(务必先在副本上试,不要直接动主仓库)
git checkout --orphan clean-tmp
git add -A && git commit -m "squash: 当前站点状态"
git branch -D main && git branch -m main
git reflog expire --expire=now --all && git gc --prune=now
```
### 方案 B:保留历史,只清理历史中的大文件
`git filter-repo --analyze` 先定位占空间的历史大文件,再针对性剔除。
体积介于 373–580MB 之间,**需要实测才知道能不能压到 500MB 以下**,不保证成功。
### 方案 C:升级 Gitee 付费版
标准版单仓库 ≤ 1GB,1298 元/年起 —— **为了一个博客每年花 1300,不值得**。
**结论:只有方案 A 是确定可行的,而且可接受。**
---
## 五、真正要权衡的:内容审查(这条比 500MB 更重要)
Gitee 不是中立的文件存储,它有**实名认证 + 内容审查体系**。有实锤的历史:
- **2022-05-19,Gitee 把全部约 1000 万个公开仓库临时私有化,做强制审核**,合规的才恢复。
开发者必须逐个提交申请、人工复核。有用户 24 个仓库里被恢复了 22 个。
- 审查范围包括仓库名、注释、变量名 —— 有开发者抱怨"写代码时还要想这个词会不会触发敏感词列表"。
- 常见封号原因:仓库含违规内容(赌博/诈骗/侵权/涉政)、批量建仓被判滥用、开 Pages 发不合规内容。
- Gitee 数据全部境内存储,实名是硬要求(这是它拿政府/信创订单的前提)。
### 但落到你身上,风险评估是**低**的
理由三条:
1. **你的内容是技术博客** —— Android 模块化魔改、K3s、Docker、自建服务。这类内容不在敏感区间。
2. **把仓库设为私有**。审查压力的大头在**公开仓库**上(2022 那次动的就是公开仓库)。
你要用 EdgeOne 构建,私有仓库完全可以(授权访问即可)。
3. **国内平台在"政策风险"上对你反而是加分项** —— 你 GitHub 账号被标记,
正说明境外平台对你有不确定性。Gitee/CNB 的规则是**明确、可预期**的:
别传违禁内容就不会出事,而不是"不知道哪天被风控"。
---
## 六、三个国内/境外代码源横向对比
| | **Gitee** | **CNB(cnb.cool)** | GitLab.com |
|---|---|---|---|
| 仓库容量 | 500 MB ⚠️ **需瘦身** | **100 GiB** ✅ 不用动 | 2 GB ✅ |
| EdgeOne Pages 官方支持 | ✅ 明确支持 | ✅(有官方插件文档) | ✅ 明确支持 |
| 构建额度消耗 | 不消耗(构建在 EO 侧) | 不消耗 / 或用它自己的 160 核时 | 不消耗 |
| 登录 | 实名注册 | 微信扫码 | 邮箱 |
| 国内推送速度 | 快 | 快 | 跨境,588MB 首次推送慢 |
| 内容审查 | ⚠️ 有实锤事件 | ⚠️ 官方明写"平台级内容安全审查" | 无(但美国平台) |
| 政策风险 | 低(国内合规) | 低 | ⚠️ **与 GitHub 同源风险** |
| 与 EdgeOne 的距离 | 第三方 | **同为腾讯体系** | 第三方 |
### 排序建议
1. **CNB** —— 100GiB 不用瘦身,零改造,和 EdgeOne 同属腾讯、有官方集成文档
2. **Gitee** —— 官方支持、国内快,但**要先做一次历史瘦身**(一次性工作,可接受)
3. **GitLab.com** —— 容量够,但**美国平台,和 GitHub 同类风险**,且首次推送跨境 588MB 很慢
**Gitee 输给 CNB 的唯一一点就是那 500MB**,如果你不想动仓库历史,选 CNB;
如果你更信任 Gitee 这个老牌子、也愿意做一次瘦身,Gitee 完全可用。
---
## 七、Gitee 方案的完整形态
```
write-server / 本地 hugo → push Gitee(私有仓库)
→ EdgeOne Pages Git 集成自动构建(4核6G,500 次/月,不消耗 Gitee CI 额度)
→ EdgeOne 国际站(境外解析) + EdgeOne 国内站 Makers(境内解析,绑备案域名)
```
砍掉:act_runner、广州中转机、sync.sh、COS、Gitea artifact 链、8 处 Gitea 兼容分支、
又拍云、多吉云、两套冗余通知。
**3 台机器 → 0 台;7 环节 → 3 环节。**
---
## 八、前置动作(按顺序)
1. **先备份**:整仓 `cp -r` 一份(或 `git bundle create` 全量)
2. **在副本上做瘦身**,验证 `.git` 能压到 500MB 以下
3. 注册 Gitee,建**私有**仓库,首次推送验证体积
4. 在 EdgeOne Pages 里接 Gitee 仓库,跑一次构建
5. 确认无误后,才把主仓 remote 切过去
**第 1、2 步不做,其余都别动。**
---
*本文档基于 2026-10-04 实测数据与官方文档。仓库体积为 `git count-objects -vH` 实测,非估算。*
BIN
View File
Binary file not shown.
+47
View File
@@ -0,0 +1,47 @@
# CNB 构建环境:固定 hugo 0.128.2 extended(与现网一致)+ 又拍云 upx + EdgeOne CLI
#
# ★ 为什么 hugo 二进制随仓库携带,而不是构建时在线下载:
# 实测国内节点直连 github.com 拉这 22MB 的 release 需要 127 秒(本机仅 7 秒),
# 而这是每次构建都要付的成本。随仓库携带后构建零跨境下载、100% 可复现。
# 二进制体积 84MB,进 git 后仅 24MB(zlib),对 CNB 100GiB 免费额度可忽略。
#
# ★ 注意架构:CNB 构建节点是 linux/amd64。
# 本机 E:\Hugo\hugo.exe 是 windows/amd64,在容器里跑不了(会报 cannot execute
# binary file),所以必须是 linux 版。文件在 bin/linux/hugo。
FROM node:22-bookworm-slim
# ★ 换国内 apt 源:CNB 官方只明示加速「Docker / NPM / Maven 镜像」,
# Debian 的默认 apt 源(deb.debian.org)不在承诺范围内,而构建节点在国内,
# 直连默认源可能慢或超时 → 换成阿里云镜像兜底。
# bookworm 镜像存在两代源文件格式(旧 /etc/apt/sources.list 与
# 新 deb822 /etc/apt/sources.list.d/debian.sources),两个都改,避免格式差异踩空。
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(源在国内 CDN,直连稳定,保留在线安装)
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; \
upx --version | head -1
# EdgeOne CLI(用于境内外两个 Pages 项目部署)
RUN npm i -g edgeone@latest
WORKDIR /workspace
CMD ["hugo", "version"]
+165
View File
@@ -0,0 +1,165 @@
# 迁移前检查清单(2026-10-04 实测)
> 目标:把境外机 `23.254.236.47` 上的 Gitea + runner 搬到**国内机**(当前中转机
> `119.29.215.187`,Ubuntu 24.04)。
> 下面是**照着做完再动手**的清单。带 ★ 的是会**直接卡住迁移**的硬伤。
---
## 一、★ 硬伤:compose 里 pin 的 Gitea 镜像 tag 不存在
`docker-compose.yml:29`
```yaml
image: gitea/gitea:1.28.0-rootless # ❌ 这个 tag 不存在
```
**原因**:**Gitea 28.0.0 起去掉了历史 `1.` 前缀** —— 原本的 `1.28.0` 现在就叫 `28.0.0`。
境外那台 `/api/v1/version` 实测返回 `{"version":"28.0.0"}`。
**已在目标机实测(`docker pull`,决定性)**:
| 镜像 tag | 结果 |
|---|---|
| `gitea/gitea:1.28.0-rootless` | ❌ `not found` |
| `gitea/gitea:28.0.0-rootless` | ✅ 可拉取 |
| `gitea/gitea:latest` | ✅ 可拉取 |
| `gitea/act_runner:0.2.11` | ✅ 可拉取 |
| `gitea/act_runner:4.0.0` | ❌ `not found` |
**改**:`gitea/gitea:28.0.0-rootless`(建议 pin 死版本,别用 `latest`,免得下次再被动升级)。
`28.0.0-rootless` 与 `latest` **已经拉到目标机上了**,迁移时省一次等待。
---
## 二、★ `.gitignore` 没忽略 `backups/`
`scripts/backup.sh` 的产物落在 `backups/`:
```
backups/gitea-dump-<时间戳>.zip # gitea dump,含 secrets(加密但仍是敏感物)
backups/data-<时间戳>.tar.gz # 完整 data 卷:gitea.db / app.ini / runner 凭据 / upyun 目录
```
而 `.gitignore` 里**只有** `.env` / `data/` / `*.log` —— **没有 `backups/`**。
跑一次备份,这两个文件就出现在仓库里了,`git add .` 一下全进历史。
**改**:`.gitignore` 加一行 `backups/`。
---
## 三、凭据散落情况(迁移时容易漏)
| 位置 | 内容 | 风险 |
|---|---|---|
| `gitea-backup/.env.example` | `UPYUN_BUCKET=imzql`、`UPYUN_OPERATOR=1770186415` | **真实值**,不是占位符 |
| `write-server/nginx/ssl/privkey.pem` | SSL 私钥 | **已被 git 跟踪**(仓库历史里有) |
| 中转机 `/opt/upyun-sync/sync.sh` | 又拍云密码 + 多吉云 AK/SK **明文硬编码** | 该文件会被整体搬进 `data/upyun-sync/` |
| 本地 `.git/config` 的 `gitea` remote | `http://用户:密码@...` | 明文写在 remote URL 里 |
容器化后 `upyun-sync` 是用 `.env` 注入凭据的,但 `sync.sh` 里**又硬编码了一份** ——
两套并存,容易改一处漏一处。建议统一成只读环境变量。
---
## 四、容器网络:脚本要走服务名,别绕公网
`upyun-sync` 容器与 `gitea` 同在 `gitea-net` 这个 bridge 网络里。
如果脚本里继续用 `https://gitea.usj.cc`,会**出到公网 DNS 再绕回来**(还可能撞上 CDN)。
**改**:`sync.sh` 里 `GITEA` 默认值改成 `http://gitea:3000`(compose 服务名 + 容器内端口)。
脚本已经写成 `GITEA="${GITEA:-...}"`,用环境变量覆盖即可,不必改脚本。
---
## 五、反向代理要补一条
目标机上 **OpenResty 已占用 80/443**(1Panel 托管的站点)。
`gitea` 容器映射的是宿主 `3001`(HTTP)/ `2222`(SSH),**这两个端口在目标机是空闲的**
(已实测)。
要让 `https://gitea.usj.cc` 生效,需在 1Panel 的 OpenResty 里加一个反代站点
→ `127.0.0.1:3001`,并配好证书。DNS 也要把 `gitea.usj.cc` 指到目标机。
> ⚠️ Gitea 28 起**不再读取 `[server] DOMAIN`**,实例域名(含默认 SSH 域名)全部来自 `ROOT_URL`。
> compose 里两个都填了,所以没问题;但改域名时**只需改 `ROOT_URL`**。
---
## 六、资源评估:内存是短板
目标机现状(实测):
| 项 | 值 |
|---|---|
| CPU | **2 核** |
| 内存 | **1.9 G**(已用 665 M,free 126 M,buff/cache 1369 M) |
| **Swap** | 1987 M,**已用 832 M** ← 已经在吃 swap |
| 磁盘 | 50 G,已用 17 G,**剩 31 G** |
| 已在跑 | 1Panel(3721)、mysql 8.4、certimate、OpenResty(80/443)、frps、alist、vaultwarden |
要再加进:**Gitea + act_runner + 一次 hugo 构建**(构建要跑 npm ci、hugo --minify、
打包 ~380 MB 产物)。这台机器现在就已经在换页了,**2 G 内存很紧**。
磁盘侧估算(够用但不宽裕):Gitea 数据(仓库全历史,含大量图片)+ artifact
(~380 MB/次 × 保留 7 天)+ `data/upyun-sync/` 里的产物副本(~750 M)≈ **6–10 G**。
**建议**:迁移前把内存加到 **4 G**;或将 `mysql` 等非必需容器停掉腾资源。
---
## 七、★ 迁移后会变的架构(决定要不要保留中转机)
`deploy.yml` 里 `Upload to UpYun` 那一步现在写死 `if: false`,注释原话:
> 国内线路由腾讯云广州中转机兜底……**将来若把 runner 挪到国内,把下面的 `if: false` 删掉即可恢复。**
也就是说 **runner 一旦在国内,又拍云直传就可行**(当初失败是因为境外出口被 GSLB 调度到坏节点)。
那时整条链路可以简化成:
```
runner(国内) → build → 又拍云直传 → purge → 多吉云刷新
```
**「中转机 + sync.sh + Gitea artifact 给中转机」这一层就可以整个删掉。**
但有两个代价要权衡:
1. **EdgeOne Pages 部署会变成跨境**(境内 runner → 境外 EdgeOne)。目前 runner 在境外,
这一步是零跨境的。需要实测耗时是否可接受。
2. 又拍云直传要补 **`upx purge`**(现在这一步在 sync.sh 里,deploy.yml 的直传步骤没有)。
多吉云刷新 finalize 里已有(`upyun_status == success` 时才刷)。
> 建议:**先按「保留 artifact 传递」把迁移做完**(artifact 在 build → deploy-edgeone
> 之间是必需的,无论如何都要留),跑通之后再单独决定要不要砍中转机。
> 别把「迁移」和「架构简化」两件事混在一次变更里。
---
## 八、迁移顺序(照做)
1. **先修本文档第一节的镜像 tag**,`docker compose up` 才不会卡在第一屏
2. 补 `.gitignore` 的 `backups/`
3. 目标机:`mkdir -p data/gitea data/runner data/upyun-sync`
4. 旧机器 rsync 数据(沿用 README 的路径):
- `/opt/gitea/data/` → `data/gitea/`
- `/opt/upyun-sync/` → `data/upyun-sync/`
- ⚠️ 用 `rsync -a` 保留属主;Gitea rootless 镜像按 **uid/gid 1000** 跑,
属主不对会起不来
5. `cp .env.example .env` 并填好(`ROOT_URL` 必填)
6. **停掉 1Panel 的计划任务「又拍云同步(国内中转)」`id=15`** —— 否则
容器版 `upyun-sync` 与它**同时**同步,双写又拍云
7. `docker compose up -d` → `docker compose ps` 三个服务 healthy
8. Gitea 后台重新拿 runner 注册 token → 填 `.env` → `up -d --force-recreate runner`
9. OpenResty 加反代站点 + DNS 切 `gitea.usj.cc`
10. 验证:`git push` 一次,看 Actions 是否正常构建部署
---
## 九、尚未核实的两点
- `scripts/backup.sh` 里 `gitea dump --config ... -R -S`:这两个参数的语义需核对
(担心是「跳过仓库」之类,导致 dump 不完整)。**建议实跑一次备份,检查 zip 里有没有
`repositories/`**。
- 旧机器上的 `docker-compose.yml` 里 Gitea 用的到底是哪个 tag(`latest` 还是 pin 的版本)——
直接把 `:latest` 的数据目录搬到一个 pin 死的旧版本上会有兼容风险,**版本必须 ≥ 旧机**。
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env bash
# 把主仓从「GitHub + 自建 Gitea 双推」切换成「CNB 主仓 + GitHub 备份」
#
# 为什么不用「一个 remote 挂多个 pushurl」:
# 实测(2026-10-04)git 对多个 pushurl 是「顺序推、遇错即停」——
# 第一个失败,后面的远端一个都不会推。那就失去备份意义了。
# 故拆成两个 remote + 一个 alias,两段各自独立、互不阻塞。
#
# 用法:bash scripts/setup-cnb-remotes.sh https://cnb.cool/<组织>/<仓库>
# 附带令牌(可直接把凭据写进系统凭据管理器):
# CNB_TOKEN='你的令牌' bash scripts/setup-cnb-remotes.sh https://cnb.cool/<组织>/<仓库>
# 回退:脚本会把改动前的配置备份到 .workbuddy-backup/git-remotes.<时间戳>.txt
set -euo pipefail
CNB_URL="${1:-}"
GH_URL="${GH_URL:-git@github.com:zqlit/blog.git}"
if [ -z "$CNB_URL" ]; then
cat <<'USAGE'
用法:bash scripts/setup-cnb-remotes.sh https://cnb.cool/<组织>/<仓库>
提示:
· CNB 仓库地址直接用仓库页地址,带不带 .git 都行
· CNB 不支持 SSH,只能 HTTPS + 访问令牌(用户名固定填 cnb,密码填令牌)
· 令牌在「个人设置 → 访问令牌」创建,勾选「代码仓库 → 读写」
USAGE
exit 1
fi
case "$CNB_URL" in
https://cnb.cool/*) ;;
*) echo "地址看起来不是 CNB 的:$CNB_URL"; exit 1 ;;
esac
ROOT="$(git rev-parse --show-toplevel)"
cd "$ROOT"
STAMP="$(date +%Y%m%d-%H%M%S)"
BACKUP=".workbuddy-backup/git-remotes.$STAMP.txt"
mkdir -p .workbuddy-backup
{
echo "# 改动前的 git 远端配置($STAMP)"
echo "# 用途:回退参考。本文件含明文凭据,勿提交、勿外传。"
echo
echo "## git remote -v"
git remote -v
echo
echo "## 原始 remote.* 配置"
git config --get-regexp '^remote\.' || true
} > "$BACKUP"
echo "原配置已备份:$BACKUP"
# 1) origin = CNB(主仓,fetch + push)
git remote remove origin 2>/dev/null || true
git remote add origin "$CNB_URL"
# 2) gh = GitHub(备份)
git remote remove gh 2>/dev/null || true
git remote add gh "$GH_URL"
# 3) 自建 Gitea 暂时留着当只读参考。确认新链路稳定后再手工执行:
# git remote remove gitea
# 4) 一条命令推两段。用「;」而不是「&&」——CNB 失败时照样把 GitHub 推上去
git config alias.pushall '!git push origin main; git push gh main'
# 5) 凭据:CNB 只认 HTTPS + 令牌(用户名固定 cnb)
#
# 本机全局 helper 是 GCM(git-credential-manager.exe)。实测它对 cnb.cool 这类
# 第三方 HTTPS 远端会**每次都弹窗**,而且 GCM_INTERACTIVE=never +
# GIT_TERMINAL_PROMPT=0 都压不住 —— git ls-remote 直接挂死(timeout 25s 未返回)。
# → 本仓显式改用 wincred:同样写 Windows 凭据管理器(加密),但没有 UI 弹窗。
# (另:PortableGit 未打包 git-credential-store,exec-path / mingw64/bin 都没有)
if [ -n "${CNB_TOKEN:-}" ]; then
git config --local credential.helper ""
git config --local credential.https://cnb.cool.helper wincred
printf 'protocol=https\nhost=cnb.cool\nusername=cnb\npassword=%s\n\n' "$CNB_TOKEN" \
| git credential-wincred store
echo "凭据已写入 Windows 凭据管理器(wincred,无弹窗);本仓已屏蔽 GCM"
else
echo "未提供 CNB_TOKEN,已跳过凭据写入。需要时重跑:"
echo " CNB_TOKEN='你的令牌' bash scripts/setup-cnb-remotes.sh $CNB_URL"
fi
echo
echo "=== 当前远端 ==="
git remote -v | sed -E 's#://[^@/]*@#://***@#g'
echo
echo "=== 令牌权限检查(CNB 常见坑)==="
echo " · 令牌须在「个人设置 → 访问令牌」勾选 repo-code(读写)"
echo " 只勾了其它 scope 会报 403:token does not have the repo-code:r scope"
echo " · 使用范围(Scope of Use)要涵盖本仓库/"
echo " · 用户名固定 cnb,密码填令牌(不支持 SSH)"
echo
echo "=== 下一步 ==="
echo " git push origin main # 推到 CNB(首次会传全量历史,约 588MB)"
echo " git push gh main # 推到 GitHub 备份"
echo " git pushall # 两段一起"
+51
View File
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>人机验证 · 真实评论区预览</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#0f1115;color:#e8eaed;font:14px/1.6 -apple-system,'PingFang SC','Microsoft YaHei',sans-serif}
.bar{position:sticky;top:0;z-index:9;background:#171a20;border-bottom:1px solid rgba(255,255,255,.08);
padding:10px 14px;display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.bar b{font-size:14px;margin-right:6px}
.bar .hint{font-size:12px;color:#98a2b3;margin-left:auto}
button{font:inherit;font-size:13px;padding:6px 12px;border-radius:8px;cursor:pointer;
border:1px solid rgba(255,255,255,.16);background:transparent;color:#e8eaed}
button:hover{border-color:#07c160}
button.primary{background:#07c160;border-color:#07c160;color:#fff}
iframe{width:100%;height:calc(100vh - 58px);border:0;background:#fff;display:block}
</style>
</head>
<body>
<div class="bar">
<b>真实评论区预览</b>
<button class="primary" onclick="call('run')">跑一次真实验证</button>
<button onclick="call('none')">正常读者(无控件)</button>
<button onclick="call('click')">需要点一下</button>
<button onclick="call('captcha')">需要图形验证码</button>
<span class="hint">控件出现在评论输入框上方(iframe 里是真的 Artalk 评论区)</span>
</div>
<iframe id="fr" src="/comment.html"></iframe>
<script>
/* 同源 iframe:直接调用评论页里暴露的 window.__humanCheck 钩子。
- run:真实跑一遍(领挑战 → 浏览器算 PoW → 交信号 → 服务端发通行证)
- none/click/captcha:只看控件外观,不请求接口
今天 D1 额度已用尽,评论列表会加载失败(红字报错),但输入框和控件位置照样能看到。 */
function api() {
var w = document.getElementById('fr').contentWindow;
return w && w.__humanCheck;
}
function call(kind) {
var h = api();
if (!h) { alert('评论区还没加载完,等一两秒再点'); return; }
if (kind === 'run') { h.drop(); h.run(false); }
else if (kind === 'none') { h.drop(); }
else if (kind === 'click') { h.drop(); h.showClick(); }
else if (kind === 'captcha') { h.drop(); h.showCaptcha(); }
}
</script>
</body>
</html>
+199
View File
@@ -0,0 +1,199 @@
# 代码源与构建平台选型
> 回答的问题:**「想简化是不是只能走 EdgeOne?」**
>
> **结论先行**:不是"只能",但在你当前的约束下,**EdgeOne 是最省事的那一个**。
> 而且真正要换的不是 CDN —— 是**「托管构建」**。EdgeOne 恰好一条顶两条(构建 + 分发),
> 这是 Cloudflare 和阿里云 ESA 都做不到的。
>
> 另外有一个**会改变结论的数字**:Gitee 免费版单仓库上限 **500MB**,而你的 `.git` 是 **588MB**。
> 所以国内代码源的第一选择不是 Gitee,而是 **CNB**。
---
## 一、把问题重新定义一次
你说「之前是 GitHub Actions 部署,用得很稳,但账号被标记了」。
那么现在这一整套东西,本质是**你自己复刻了一套 GitHub Actions**:
| GitHub Actions 原来的角色 | 你现在用自建件替代 |
|---|---|
| github.com(代码托管) | 自建 Gitea |
| GitHub 的 runner(构建执行) | act_runner |
| `upload-artifact`(产物传递) | Gitea artifact → COS → 中转机 |
| Pages 直连(产物落地) | sync.sh → 又拍云 → 多吉云 |
**所以要"简化回去",正确的问题是:找一个托管的构建服务,把这四个自建件一起替掉。**
CDN 选谁(EdgeOne / CF / ESA / 多吉云)是**另一个问题**,它不解决构建复杂度。
---
## 二、三条硬约束(都核实过,不是印象)
| 约束 | 实测/官方值 | 影响 |
|---|---|---|
| 你的仓库体积 | `.git` **588MB** / 955 提交;工作区 803MB | 决定代码源能不能装下 |
| 你的产物体积 | `public` 424MB / **3267 文件**,最大单文件 12.8MB | 决定托管平台的文件数/单文件门槛 |
| Hugo 版本 | 0.128.2 | 托管构建需能指定版本 |
| 备案 | **鄂ICP备2022015735号-1** | 境内加速的前提,你**已具备** |
---
## 三、★ 三个关键发现
### 发现 1:EdgeOne Pages **官方支持 Gitee / GitLab 作为 Git 源**
官方文档原文:*「Pages 目前支持 GitHub, GitLab, Gitee 等 Git 提供商的接入」*。
第三方 Nitro 部署文档也写:*「EdgeOne supports deployments from GitHub, GitLab, Gitee, and CNB」*。
**这条直接决定了「能不能找回 GitHub Actions 那种简单感」—— 能。**
代码推到国内托管平台 → EdgeOne 自动拉取、自动构建、自动部署。**不需要你自己的 runner。**
### 发现 2:Gitee 免费版装不下你的仓库
Gitee 官方配额页(help.gitee.com/account/usage-quota):
| 项 | 社区版免费额度 |
|---|---|
| 单仓库容量 | **≤ 500 MB** |
| 单文件 | ≤ 50 MB |
| 用户总仓库容量 | 5 GB |
**你的 `.git` 是 588MB → 超了 17.6%。** 推上去会被锁定推拉服务。
(Gitee 提供 `git-repo-clean` 瘦身工具可以降到 500MB 以下,但那是额外工作。)
### 发现 3:CNB 是更合适的国内代码源 —— 而且和 EdgeOne 有官方集成
**CNB(Cloud Native Build,cnb.cool)** 是腾讯的 AI Native Git 平台,社区版免费:
| 项 | 免费额度 |
|---|---|
| 仓库存储 | **100 GiB**(你的 588MB 毫无压力) |
| 对象存储 | 100 GiB |
| 云原生构建 | **160 核时/月** |
| 云原生开发 | 1600 核时/月 |
| 登录方式 | 微信扫码 |
**160 核时换算**:4 核跑 10 分钟 = 4 × (10/60) ≈ 0.67 核时 → 一个月能跑 **约 240 次构建**。你今天按每月 60–90 次算,**用掉不到 40%**。
而且 EdgeOne 官方专门写了 **CNB 插件文档**(`pages.edgeone.ai/document/using-cnb-plugin`):
> 在 CNB 流水线里 `npm run build` → `npx edgeone pages deploy -n <项目名> -t $EDGEONE_API_TOKEN`
**这条路官方背书,100% 可行。**
---
## 四、四种组合对比
| | 代码源 | 构建在哪 | 产物去向 | 自维护机器 | 免费额度 | 卡点 |
|---|---|---|---|---|---|---|
| **① CNB + EdgeOne Pages** ★ | CNB | **CNB 流水线**(托管) | EdgeOne 国际 + 国内 | **0 台** | 100GiB + 160 核时/月 + 500 构建/月 | 需绑微信/腾讯云账号 |
| ② Gitee + EdgeOne Pages | Gitee | EdgeOne Git 集成(4核6G) | EdgeOne 国际 + 国内 | **0 台** | 5GB / 500 构建/月 | **仓库必须先瘦身到 500MB 以下** |
| ③ GitLab.com + CF Pages | GitLab.com | CF 侧 | **仅 CF**(产物拿不出来) | 0 台 | CF 500 次/月 | 境内无解;CF 国内访问慢 3 倍 |
| ④ 保留 Gitea + EdgeOne Pages | 自建 Gitea(mirror→CNB) | CNB | EdgeOne | **1 台**(Gitea) | 同 ① | 没砍干净;但写作后台零改动 |
**为什么推荐 ①**:唯一一个同时满足「代码源在国内且装得下仓库」+「构建托管」+「境内外都能落」+「免费」的组合。
---
## 五、推荐形态
```
写作侧 构建(托管) 分发(托管)
┌────────────────────┐ ┌──────────────────────────┐ ┌──────────────────────────┐
│ write-server │ │ CNB(cnb.cool) │ │ EdgeOne 国际站 Pages │
│ (本地/服务器) │ push │ 免费 100GiB / 160 核时/月 │ │ → usj.cc 境外解析 │
│ 直接写 .md + git ├─────────▶│ │ │ │
│ 只改 remote URL │ │ .cnb.yml: │──▶│ EdgeOne 国内站 Makers │
└────────────────────┘ │ hugo --minify │ │ → 备案域名 境内解析 │
│ edgeone pages deploy │ │ │
或本地 hugo 后手动 push ─────▶│ (境外 + 境内 两个项目)│ └──────────────────────────┘
└──────────────────────────┘
```
### `.cnb.yml` 骨架(复刻你现在 deploy.yml 做的事)
```yaml
main:
push:
stages:
- name: 构建 Hugo 站点
image: klakegg/hugo:0.128.2-ext
script: |
hugo --minify --gc
- name: 部署境外
image: node:20
script: |
npx edgeone pages deploy ./public \
-n hugo-blog-overseas -t $EDGEONE_TOKEN_INTL -a overseas
- name: 部署境内
image: node:20
script: |
npx edgeone pages deploy ./public \
-n hugo-blog-cn -t $EDGEONE_TOKEN_CN -a global
```
> ⚠️ `-a global`(含中国大陆)目前只在**国际站**文档里明确,且官方文档自相矛盾。
> 另一条稳妥路径是**国内站 Makers 单独建一个项目**(账号体系与国际站独立),
> 绑你的备案域名。这个需要实测一次定论。
---
## 六、砍掉 / 保留
### 砍掉(9 项)
| 砍掉 | 原因 |
|---|---|
| act_runner | CNB 托管构建取代 |
| 广州中转机 | 产物直接落在 EdgeOne,不需要中转 |
| `sync.sh` 轮询 | 同上 |
| 腾讯云 COS | 同上(砍 COS 这件事本身也随之结束) |
| Gitea artifact 链 | 那串 `v3/v4` 兼容坑一起消失 |
| 8 处 `github.server_url` 兼容分支 | 不再需要伪装成 GitHub |
| 又拍云 | EdgeOne 国内站直接 serve 境内 |
| 多吉云 | 同上(境内 CDN 层) |
| 三套通知里的两套 | 保留一套即可 |
**发布链路:7 环节 → 3 环节**(写作 → 构建 → 上线)
**自维护机器:3 台 → 0 台**
**厂商:Gitea自建 + COS + 又拍云 + 多吉云 + EdgeOne + 失效的 GitHub → CNB + EdgeOne(同为腾讯体系)**
### 保留
- **Hugo + content + themes** —— 完全不动
- **blog-admin(artalk-cf)** —— 本来就跑在 CF Workers,一行不改
- **write-server** —— **几乎不用改**,只把 `origin` 的 remote URL 指向 CNB。
CNB 是标准 git + 提供统一 HTTPS Token,`lib/git.ts` 的 `git pull --rebase` / `git push` 逻辑照常工作
---
## 七、待确认(3 条,按重要性排序)
1. **EdgeOne 免费版的流量额度**。官方「限制与配额」页**没有列「流量」这一项**;第三方说法互相矛盾(一处说 50GB/月,一处说不限量)。
按你的站点规模估算:单页 1–3MB,50GB/月 ≈ 2–5 万次浏览 —— 个人博客大概率够,
但**那个 12.8MB 的 mp4 如果被反复播放会吃掉不少**。这一条开工前必须先确认。
2. **CNB 是否可直接作为 EdgeOne Pages 的 Git 源导入**。第三方文档说支持,官方只在插件路径里明确。
不影响可行性(走插件路径必然可行),只影响配置方式。
3. **境内用国际站 `-a global` 还是国内站 Makers 独立项目**。一次实验可定论。
---
## 八、最小验证(成本极低,建议先做)
不用改任何现有东西,**一次性验证整条链路是否成立**:
1. 建一个 CNB 仓库(微信扫码,2 分钟)
2. 往里放一个最小 Hugo 站 + 上面的 `.cnb.yml`(或先放个 hello world)
3. 拿一个 EdgeOne API Token(你已经有),跑一次 push
4. 看三件事:**CNB 能不能构建** → **能不能部署到 EdgeOne** → **国内机实测访问延迟**
这一步跑通,等于确认了整条路;跑不通,损失是 20 分钟,不是 30 天。
---
*本文档基于 2026-10-04 核实的官方文档与实测数据。所有配额均引自官方页面,第三方来源已标注。*
+173
View File
@@ -0,0 +1,173 @@
# 优世界博客 · 架构总览与复杂度审计
> 梳理时间:2026-10-04
> 目的:把「复杂在哪、为什么复杂、哪些是自己加的、哪些能砍」讲清楚,供架构决策。
> 方法:以代码/配置为证据,不用推测。凡属推测的地方都标了「待确认」。
---
## 0. 一句话结论
**这不是「一个博客」,而是四个独立系统,绑在一条 7 环节、跨境的、需要自己运维的发布链路上。**
前三个系统都很轻、也很健康;**复杂度几乎全部来自第四个 —— 为了支撑前三个而自建的基础设施**(Gitea + act_runner + 中转机),以及为了绕过跨境链路而打的一层层补丁。
---
## 1. 全景:哪里有什么
### 1.1 四个子系统
| # | 子系统 | 技术栈 | 跑在哪 | 健康度 |
|---|---|---|---|---|
| **1** | **内容系统** | Hugo 0.128.2 + Ying 主题,138 篇 md,产物 424MB / 3267 文件 | 产物分发到 3 个 CDN | 主体,正常 |
| **2** | **评论系统** | `blog-admin/` = artalk-cf(Artalk v2 兼容服务端)+ RSS 机器人 | Cloudflare Workers + D1 + KV → `api.200181.xyz` | ✅ 已上线,**零服务器零运维**,架构最干净的一块 |
| **3** | **写作系统** | `write-server/`(Next.js 16 + Docker,`post.usj.cc`)<br>`write/`(本地 Windows 前端,带 vbs/bat 自启动) | 独立 Docker 主机(**待确认**)/本机 | ⚠️ **两套前端功能重叠** |
| **4** | **基础设施** | Gitea 28.0.0 + act_runner + 1Panel 中转机 + 各类备份脚本 | 境外 VPS + 腾讯云广州 | ⚠️ 正在迁移,**复杂度的主要来源** |
### 1.2 机器与服务地图
| 标识 | 地址 / 位置 | 承担什么 |
|---|---|---|
| **Gitea 主机** | `23.254.236.47:3001`(境外) | Git 仓库 + **Gitea Actions / act_runner** + artifact 存储 |
| **国内中转机** | `119.29.215.187`(腾讯云广州) | 1Panel + OpenResty + mysql + alist + vaultwarden(7 个容器)+ `sync.sh` 每分钟轮询同步 |
| **EdgeOne Pages** | 项目 `hugo-blog`,`--area overseas` | **境外线路**的托管与 CDN(腾讯) |
| **又拍云** | 对象存储 | **国内源站** |
| **多吉云** | CDN,回源又拍云 | **国内加速** |
| 腾讯云 COS | `cos.ap-*.myqcloud.com` | 曾是「境外构建产物 → 国内中转机」的中转层,**正在砍** |
| Cloudflare | `api.200181.xyz` | 评论后端 + RSS 机器人 |
| 通知渠道 | Telegram / 飞书 / QQ 邮件 | 三套并行,内容 90% 重复 |
> 关键点:**境外线路和国内线路是完全独立的两套。** 境外走 EdgeOne,国内走 多吉云 → 又拍云。任一篇文章上线,两条链路上的东西都得各自到位。
---
## 2. 一次发文的完整旅程
```
① 写作 write-server (web) 或 write/ (本地 Windows)
│
② 提交 git push ──► Gitea(自建,跨境) ← 你自己维护的机器 1
│
③ 构建 Gitea Actions / act_runner ← 你自己维护的 runner 2
│ ├─ hugo --minify --gc (要的)
│ ├─ 图片优化 (sharp, 482 张) (可砍,见 §3-C)
│ └─ 草稿隐藏处理
│
④ 传递 424MB 产物 → 打 tar.zst → Gitea artifact ← 踩过 3 个坑才通
│
┌────┴─────────────────────────────┐
▼ ▼
⑤ 境外线路 ⑥ 国内线路
EdgeOne Pages 广州中转机 sync.sh(每分钟轮询) ← 你自己维护的机器 3
(runner 直接 deploy) └─ 跨境下载 376MB
└─ upx sync → 又拍云
└─ upx purge
│
⑦ 多吉云 CDN 刷新 + TG / 飞书 / 邮件 三套通知
```
**对照**:一个普通 Hugo 博客 + 托管 CI 的发布链路是 **3 步**(push → 构建 → 上线)。
你这里是 **7 步,中间有 3 个环节是你自己在运维的服务器/服务**。
这就是「复杂」最直观的度量。功能一点都不多,**多的是中继环节**。
---
## 3. 复杂度归因:三层,性质完全不同
### A 层 · 外部约束 —— 不是你的选择,但躲不掉
| 约束 | 连带出来的复杂度 |
|---|---|
| **GitHub 账号被标记** | 自建 Gitea → 连带 act_runner 自运维、artifact API 不合规、缓存服务跨网段不可达 |
| **跨境链路不稳** | 境外 runner 直传又拍云 v0 API 稳定 EOF → 必须有境内兜底 |
| **国内 / 境外访问质量不同** | 被迫维护两条独立线路(EdgeOne + 多吉云/又拍云) |
| **EdgeOne Pages 国际站必须 `--area overseas`** | 部署动作只能从境外发起 |
> A 层是**根因**。B、C 两层的大部分,都是 A 层长出来的。
### B 层 · 为绕开 A 打的补丁 —— 半被迫,可以简化但不能直接删
| 补丁 | 为什么存在 | 代价 |
|---|---|---|
| 广州中转机 `sync.sh` 每分钟轮询 | 境外直传又拍云不通 | 多一台机器要维护;每分钟一次轮询;产物要跨境搬一次 |
| COS 中转层(正在砍) | 给「境外产物 → 国内源站」当缓冲区 | 存储 + 流量账单 |
| 一套 workflow 硬塞进 Gitea | 原本为 GitHub 写 | **8 处** `github.server_url == 'https://github.com'` 分支判断 |
| artifact 单文件 tar 化 | v4 不支持 / v3 传目录 finalize 500 | 额外的打包/解包步骤 |
| 备份脚本三套(aliyun / gitea / cleanup) | 自建基础设施的必然成本 | — |
> ⚠️ **一个需要你注意的点**(推测,建议实测):砍掉 COS 之后,中转机变成**直接跨境从境外 Gitea 拉 376MB**。跨境传输这一步并没有消失,只是从「境外 → 境内 COS(一次跨境上传)」变成「境内 ← 境外 Gitea(一次跨境下载)」。**除非实测新链路更快更稳,否则「砍 COS」省的是账单,不是复杂度。** 这条直接关系到你在做的改造是不是真的划算。
### C 层 · 自己加的 —— 可以直接砍,收益最大
| # | 冗余 | 证据 | 能砍吗 |
|---|---|---|---|
| 1 | **图片优化塞进 CI** | `Optimize Images` + `Commit Optimized Images` + `Push Image Optimizations` 三步 + `npm ci` 装 sharp(~50MB)。扫 482 张图,其中 **15 张 2021–2023 的老图永远处理不掉**,每轮重扫重试 | ✅ **最该砍**。图片在写作时本地处理,CI 就永远不需要 sharp |
| 2 | **CI 反向 commit 回仓库** | `Commit Optimized Images` 把结果 commit,`Push Image Optimizations` 再 rebase push | ✅ 砍掉图片优化后,这两步一起消失 |
| 3 | **三套通知** | `Send Telegram` ×2 + `Send Feishu` ×2 + `Send email` ×1 = **5 个 step,约占 deploy.yml 的 1/3(~400 行)**,同一份内容写 3 遍 | ✅ 留一套(TG 或飞书),其余删 |
| 4 | **永久跳过的死代码** | `Upload to UpYun` 标了 `if: false`,但 **~100 行代码仍在**(upx 下载、登录重试、sync 重试) | ✅ 要么删,要么挪进中转机 |
| 5 | **脚本重复** | `add_draft_to_hidden` 有 `.ps1`(96行) 和 `.py`(110行) 两份,README 写的是 ps1、CI 用的是 py;`deploy_*.sh` 有 4 个共 225 行 | ✅ 各留一份 |
| 6 | **两套写作前端** | `write/`(本地 Windows)与 `write-server/`(网页)功能重叠 | ⚠️ 看你的使用习惯,可合并 |
---
## 4. 量化:复杂度有多少
| 指标 | 值 |
|---|---|
| `deploy.yml` 总行数 | **1169 行** |
| job 数 | 7 个(含 1 个手动触发的网络探测 job) |
| 单个 job 的 step 数(finalize) | 12 个 |
| 通知类 step | 5 个(TG×2 / 飞书×2 / 邮件×1),约 400 行 |
| `github.server_url` 兼容分支 | 8 处 |
| `if: false` 死代码 | 约 100 行 |
| 需要自己运维的机器 / 服务 | **3 个**(Gitea 主机、act_runner、广州中转机) |
| 发布链路环节数 | **7 步**(理想 3 步) |
| 涉及的 CDN / 存储 | 4 个(EdgeOne / 又拍云 / 多吉云 / COS) |
| 构建产物体积 | 424MB / 3267 文件 |
| 真正发布所需的机器数(理想) | **1 台**(构建 + 推送)或 0 台(托管 CI) |
**一句话**:功能是一个静态博客 + 评论 + 写作后台,但支撑它的是 **7 个 job、4 个 CDN、3 台自维护机器、1169 行流水线**。
---
## 5. 四条可选的收敛方向
| | 方向 A · 就地瘦身 | 方向 B · 构建源外包 | 方向 C · 构建下移境内 | 方向 D · 取消 CI |
|---|---|---|---|---|
| **做什么** | 砍 C 层:图片优化移出 CI、合并通知、删死代码、合并脚本 | 引入 gitlab.com(GitHub 已被封)或用 CF Pages 构建 | Gitea + runner 迁到广州机,构建发生在境内 | 写作后台直接构建 + 部署,Gitea 退化为纯代码托管 |
| **产物落地** | 不变 | gitlab.com 共享 runner | 境内直接推又拍云 ✓ | 写作后台直接推又拍云 + EdgeOne |
| **跨境传输** | 不变 | 视方案 | **只剩 EdgeOne 一步** | 同上 |
| **新增依赖** | 无 | gitlab.com 账号 + 跨境 push | 无 | 无 |
| **主要风险** | 只是变短,环节数不变 | **GitLab 免费版仅 400 分钟/月**(你月需约 400–900 分钟,踩线) | 广州机**内存 1.9G / 已跑 7 个容器**,hugo 构建未必跑得动 | 写作后台所在机器算力是否够;构建与发布耦合 |
| **代价** | 最低,纯收益 | 月额度可能超 | 需要瘦身后实测内存 | 需要改造写作后台 |
| **建议** | ✅ **先做,是所有方案的前置** | 备选 | 与 D 二选一,实测决定 | 与你「一键直达」的偏好最契合 |
### 推荐路径
1. **先做方向 A**(无风险、纯收益、且是 B/C/D 的前置)。做完后 `deploy.yml` 预计能从 1169 行降到 600 行以内,构建里只剩 `hugo build`。
2. **再实测两件事**:① 广州机跑一次 `hugo` 的峰值内存与耗时;② 国内机跑一次 `hugo` 的耗时(对比 CI)。
3. **依据实测二选一**:
- 广州机跑得动 → **方向 C**(Gitea 迁境内,跨境只剩 EdgeOne)
- 写作后台机器跑得动 → **方向 D**(连 CI 都取消,最符合「一键直达」)
- 都跑不动 → **方向 B**(但要接受 GitLab 400 分钟/月的紧箍咒)
---
## 6. 需要你决定的三件事
1. **那 15 张 2021–2023 的老图能清吗?** 能清的话,我本地转 WebP + 修引用,然后 `Optimize Images` 这一步整个从 CI 删掉 —— 这是砍掉 C 层最大块的钥匙。
2. **构建最终放在哪一侧?** 境外(现状)/境内(方向 C/D)/外包(方向 B)。这决定后面所有改造的走向。
3. **通知留哪一套?** TG / 飞书 / 邮件 —— 留一个,其余删。另外邮件通知那份的长文本(约 50 行模板)可以整个去掉。
---
## 附:本次梳理的证据出处
- `.github/workflows/deploy.yml`(1169 行,全文通读)
- `blog-admin/README.md`、`write-server/README.md`、`write/`(模块定位)
- `hugo.toml`(`baseURL = https://usj.cc`)
- `gitea-backup/README.md`、`docker-compose.yml`(迁移目标与坑)
- `scripts/` 目录清单与行数统计
- 线上实测:Gitea `/api/v1/version` = `28.0.0`;广州机 `docker pull gitea/gitea:28.0.0-rootless` 可拉、`1.28.0-rootless` 不存在
+34 -4
View File
@@ -4,10 +4,40 @@
> 改成「build → Gitea artifact →(中转机拉取)→ 又拍云 → 多吉云」, > 改成「build → Gitea artifact →(中转机拉取)→ 又拍云 → 多吉云」,
> 砍掉腾讯云 COS 这一层,省下 COS 存储/流量账单。 > 砍掉腾讯云 COS 这一层,省下 COS 存储/流量账单。
> 状态(2026-10-04): > 状态(2026-10-04 更新):
> - ✅ `deploy.yml` 已改完(本地,未推送) > - ✅ `deploy.yml` 已改完并**已推送**
> - ⏸️ `sync.sh`(国内中转机)待改,**卡在需要一个 Gitea 只读 token** > - ✅ 中转机 `sync.sh.new`(artifact 版)已上传到 `/opt/upyun-sync/sync.sh.new`,**尚未生效**
> - ⚠️ 两者必须一起改一起推,否则国内博客会停更(见下方「为什么不能只推一半」) > - ✅ Gitea 只读 token 已写入中转机 `/opt/upyun-sync/.gitea_token`(600 root:root)
> - ❌ 链路**尚未跑通**:run #55 倒在 v4 不支持,run #56 倒在 v3 的 finalize 500
> - ⏸️ 最新修复(单文件 tar,commit `1f568bbf`)**本地已提交、待推送**
> - ⚠️ `deploy.yml` 与 `sync.sh` 必须一起生效,否则国内线路停更(见下)
---
## 零、实测踩到的两个坑(都已修,记录备用)
### 坑 1:`upload-artifact@v4` 在 Gitea 上直接失败(run #55)
```
::error::@actions/artifact v2.0.0+, upload-artifact@v4+ and download-artifact@v4+
are not currently supported on GHES.
```
Gitea 被 v4 识别为 GHES,但它没实现新版 artifact API → **降级 v3**(commit `ed38f938`)。
### 坑 2:v3 传「目录」时 Gitea finalize 返回 500(run #56,耗时 30 分钟)
v3 收目录是**逐文件上传**:3216 个 blob / 376MB 全部传完(日志可见 `Processed file #3216`),
但最后的 finalize 调用被 Gitea 拒绝:
```
Finalize artifact upload - Attempt 1..5 of 5 failed with error: Request timeout
::error::Finalize artifact upload failed: Artifact service responded with 500
```
**结论:Gitea 扛不住「文件数多」的 artifact。** 改法(commit `1f568bbf`):
build 侧先把 `public/` 打成**单个** `public.tar.zst`,只上传这 1 个文件。
副作用是好的——产物结构从此完全确定,不再有「解出来会不会多一层 `public/`」的歧义。
--- ---
+228
View File
@@ -0,0 +1,228 @@
# 精简方案:只留 Cloudflare + Hugo
> 2026-10-04
> 目标:砍掉全部自建基础设施,只保留 **Hugo**(构建)和 **Cloudflare**(一切服务端)。
> 文中所有平台限制均来自官方文档核实,非推测。
---
## 一、决定一切的硬约束
**Cloudflare 唯一不能替你做的是「代码托管」。** 它的两条部署路线直接决定架构形态:
| 模式 | 代码源 | 构建在哪 | 适用场景 |
|---|---|---|---|
| **Git 集成** | **只认 github.com / gitlab.com**<br>(官方原文:*does not currently support connecting self-hosted instances*) | Cloudflare 侧自动构建 | 要「push 即上线」 |
| **Direct Upload** | 不需要 git | 不构建,你本地构建好推产物 | 要「零第三方依赖」 |
> ⚠️ **两种模式在项目创建时定死,事后不能互转**(官方明说)。选错只能重建项目 —— 这是最容易踩的坑。
> 变通办法:建 **Git 集成**项目,之后在 `Settings → Builds` 关掉「自动部署」,再用 `wrangler` 直传 —— 这样两种方式都能用(**反过来不行**)。
### 关键结论
**GitHub 被封 ≠ 必须自建 Gitea。** 你不需要「自建 git 托管」这种复杂度,只需要回答一个问题:
> **你要不要「不开电脑也能发文」?**
---
## 二、两条路线
### 形态 A · 本地构建直传(最简,零第三方)
```
你的电脑 Cloudflare
┌─────────────────────┐ ┌──────────────┐
│ 写 md → hugo build │──wrangler─▶│ Pages (静态站) │
└─────────────────────┘ pages ├──────────────┤
deploy │ Workers (评论/RSS) │
└──────────────┘
```
- **组件数**:1 台自己的电脑 + Cloudflare
- **第三方依赖**:0(连 git 托管都不要)
- **发布链路**:3 步,全在本机
- 代码备份:本地仓库即可,要冗余可推任意 git,或存 CF R2
- **代价**:发文必须开电脑
### 形态 B · GitLab.com 当代码源 + CF 自动构建
```
写 md ──push──▶ gitlab.com ──自动构建──▶ Cloudflare Pages ──▶ 上线
Cloudflare Workers (评论/RSS)
```
- **组件数**:gitlab.com + Cloudflare
- **★ 关键**:构建跑在 **Cloudflare 侧**(免费 500 次/月、单次 20 分钟超时),**不消耗 GitLab 的 400 分钟**
→ 所以之前担心的「GitLab 免费版 400 分钟/月不够用」在这套组合下**根本不成立**
- 额度核对:本站 3267 文件 / 最大单文件 12.8MB → CF 上限 20,000 文件 / 25MiB ✅ 完全够
- **代价**:多一个外部账号
---
## 三、可以整个删掉的(9 项)
| # | 删掉的 | 原本的作用 |
|---|---|---|
| 1 | **Gitea** | 自建 git 托管(GitHub 被封后的替代品) |
| 2 | **act_runner** | 自建 CI runner |
| 3 | **广州中转机 + `sync.sh`** | 每分钟轮询、把产物搬去又拍云 |
| 4 | **又拍云** | 国内源站 |
| 5 | **多吉云** | 国内 CDN,回源又拍云 |
| 6 | **EdgeOne Pages** | 境外线路托管 |
| 7 | **腾讯云 COS** | 境外产物 → 国内的中转层 |
| 8 | **artifact 传递链** | 跨 job 传 424MB(含 v3/v4 那串坑) |
| 9 | **兼容分支与冗余通知** | `github.server_url` 判断 ×8、TG/飞书/邮件三套、`if:false` 死代码约 100 行 |
**发布链路从 7 环节降到 3 环节;需要自己运维的机器从 3 台降到 0 台。**
---
## 四、Cloudflare 侧能白捡留下的
**`blog-admin/`(artalk-cf 评论后端 + RSS 机器人)本来就是 CF Workers + D1 + KV —— 一行都不用改。**
这是你现有架构里唯一「已经完全符合目标形态」的部分:
- 评论系统 → `api.200181.xyz`,照常
- RSS 订阅抓取、友链朋友圈数据 → 照常
- 后台管理(`/admin/` 与 `/sidebar/`)→ 照常
**换句话说:你的「后端」其实早就已经是 Cloudflare 了,复杂的只是「发布管线」。**
---
## 五、必须接受的代价(诚实说)
### 1. 国内访问会变慢 ← 最大的一笔
Cloudflare 免费版在中国大陆走**境外节点**(没有 ICP 备案就用不了大陆节点,China Network 要企业版)。
现在这套「又拍云 + 多吉云」的存在意义**就是为了这个**。
**2026-10-04 国内机实测**(腾讯云广州,见「五·补」章):CF 首字节 **0.61–0.89s**,
而现在的国内 CDN 是 **0.18–0.32s** —— 即 **CF 比国内 CDN 慢约 2–4 倍**。
能开、不算快,对个人博客通常可接受,但你要心里有数。
### 2. 写作后台(`post.usj.cc`)会没有
那套 Next.js 直接读写本地 `.md` 文件、依赖文件系统,**CF 上跑不了**。要么:
- 重写成 CF Worker(内容改存 R2/D1,图片存 R2)—— 你会写 Worker,技术可行,但是工作量
- 退回本地写作(配合形态 A 正好)
### 3. Telegram bot 发文、公众号发布会断
同理,需要重写成 CF Worker 才能保留。
### 4. 构建前的脚本要处理
`check_links.js`(友链检查)、`generate_circle_data.js`(朋友圈数据)、`add_draft_to_hidden.py` 这些:
- 要么塞进 CF Pages 的构建命令里(需要 `npm ci`,会拖慢构建、且 CF 构建环境要能装依赖)
- 要么去掉(友情链接检查其实没必要每次构建都做)
---
## 五·补、国内访问能不能救?(2026-10-04 国内机实测)
用国内那台机器(腾讯云广州,`119.29.215.187`)实测,每项 3 次取代表值:
| 目标 | 首字节 TTFB | 连接建立 | 走哪个节点 |
|---|---|---|---|
| `usj.cc`(现状:EdgeOne **国内**节点) | **0.18–0.32s** | 0.01–0.04s | 成都 / 浙江 |
| `api.200181.xyz`(你自己的 CF Workers) | **0.61–0.89s** | 0.20–0.45s | CF Anycast(IPv6) |
| `cravatar.cn`(头像是 CF 的) | 0.65–0.71s | 0.23s | CF Anycast |
| `spst2.com`(页首统计脚本) | 0.087s | 0.011s | 有国内节点,**不是问题** |
**两条硬结论:**
1. **CF 免费版在国内比国内 CDN 慢约 2–4 倍**(TTFB 0.6–0.9s vs 0.2–0.3s)。
2. **实测发现 CF 的连接走了 IPv6**(`2606:4700::…`),握手要 0.20–0.45s;
同一时刻 IPv4 握手只要 **0.058s**。→ **IPv6 到 CF 的路由在国内明显更差**,
这是一个可以单独利用的抓手。
### 能加速的三条路
**路 A · 官方(走不通)**
Cloudflare China Network 需要:Enterprise 企业版 + 单独订阅 + **每个主域名持有效 ICP 备案** +
京东云内容审核。个人博客没戏。
**路 B · 社区「优选 IP」(有效,但违反 ToS)**
原理:CF 用 Anycast 广播,大陆解析到的 IP 段路由差。做法是**让大陆用户的 DNS 解析结果
指向另一段「对大陆友好」的 CF IP**。
- 预期效果:按本机实测基线,合理预期是 **0.6s → 0.2–0.3s**(社区常说的「5s→2s」是针对更差的默认线)
- ⚠️ **Pages 不能直接改 CNAME**(会返回 `1001`)→ 必须「转成 Worker」或「交给第三方 DNS 分线路解析」
- ⚠️ **明文违反 CF 服务条款 2.2.1(b)**:*causing traffic for your Cloudflare-proxied domain
to be sent to an IP address that was not assigned by Cloudflare for the domain*。
官方保留封号权;社区共识是「用别怕,怕别用」,建议**账号隔离**(主站一个号,优选另开小号)
- ⚠️ **要维护**:优选 IP/域名会失效,得定期更换;个别优选域名还会被地方屏蔽
**路 C · 不违规的优化(零风险,先做)**
- ✅ **已做**:字体自托管(`/font/zql-v2.woff2`,无 Google Fonts)、CSS 合并成单文件、图片 WebP
- 头像源 `cravatar.cn` 走 CF(0.65s)→ 可换国内源(站内已有 `q1.qlogo.cn` 这类)
- 加 **Cache Rules** 长缓存 + **Early Hints** + **Tiered Cache**(免费可用)→ 改善重复访问
- 若走第三方 DNS(路 B 顺带),**只下发 A 记录、不下发 AAAA** → 直接规避上面那条差的 IPv6 路由
### 一句话结论
**你自己的实测就是答案:国内 CDN 快 2–4 倍,而且完全合规。**
CF 免费版在国内的「加速」本质是**可用的灰色技巧 + 持续维护成本**。
- 如果「只留 CF」是硬目标 → 走 **Worker + 第三方 DNS 只给 IPv4**(比优选 IP 干净,且顺带修 IPv6 问题)
- 如果真正的目标是「砍复杂度」→ 记住:**复杂度的大头在「构建与搬运」**(Gitea / act_runner / 中转机 / COS / artifact),
**不在 CDN 的数量**。多留一个国内 CDN 不会让架构变复杂;砍掉那串搬运链才会。
---
## 六、我的建议
**先回答那个问题:需不需要「不开电脑也能发文」?**
| 你的答案 | 选 | 理由 |
|---|---|---|
| **不需要**(本来就在电脑上写) | **形态 A** | 真正的「只要 CF + Hugo」:0 台服务器、0 个第三方托管 |
| **需要**(手机/TG 发文) | **形态 B** | GitLab 只当「代码网盘 + 触发器」,构建和托管全在 CF |
**两条都不要再去自建 Gitea** —— 那正是你想摆脱的复杂度。
**一个必须提前想清楚的点**:CF Pages 的 Git 集成与 Direct Upload **不能互转**,项目创建时就要定。
如果不确定,**先建 Git 集成项目、之后关掉自动部署用 wrangler 直传**,把两条路都留着。
---
## 七、如果还想更彻底(建议不要)
把内容也搬进 CF:文章存 R2/D1,写作后台写成 CF Worker。但 ——
**Hugo 构建需要文件系统 + 执行二进制,跑不进 Worker。** 所以要么放弃 Hugo(改成运行时渲染),要么保留一个构建执行者(那就回到形态 B 了)。**复杂度会绕回来,不建议。**
---
## 附 A:落地顺序(以形态 B 为例)
1. GitLab.com 建**私有**项目 → 现有仓库推上去(`.git` 588MB,首次跨境推送需要时间)
2. CF Pages 新建项目 → **Connect to Git → 选 GitLab**
- 构建命令:`hugo --minify`
- 输出目录:`public`
- 环境变量:`HUGO_VERSION=0.128.2`(CF 构建镜像默认是 0.147.7,要锁到你的版本)
3. 绑定自定义域名 `usj.cc`
4. `blog-admin` 保持不动(本来就在 CF)
5. 停掉 Gitea / act_runner / 中转机的同步计划任务;DNS 切到 Cloudflare
6. **观察国内访问速度** —— 这是唯一的实质性风险点
## 附 B:形态 A 的落地顺序
1. 本机装 `hugo` 0.128.2 + `wrangler`(Node 环境已有)
2. CF Pages 新建 **Direct Upload** 项目(**注意:一旦选它,以后不能改成 Git 集成**)
3. 本机 `hugo --minify && npx wrangler pages deploy public --project-name=<项目名>`
4. 绑定域名,完成
5. 其余全部关停
---
## 附 C:本方案的证据出处
- Cloudflare 官方文档 *Git integration*:「Pages offers support for GitHub and GitLab」「does not currently support connecting self-hosted instances of GitHub or GitLab」
- 同上:「You cannot switch to Direct Upload later」
- Cloudflare 官方文档 *Direct Upload*:Wrangler 上传上限 **20,000 文件 / 25 MiB**;拖拽上限 1,000 文件
- Cloudflare 官方文档 *Workers Billing and Limitations*:静态资源请求**免费且不限量**,20,000 文件 / 25 MiB
- Cloudflare Blog:*Cloudflare Pages 现已提供对 GitLab 的支持*
- CF Pages 免费额度:500 次构建/月、1 并发、单次 20 分钟超时
- 本站实测:`public/` = 424MB / 3267 文件,最大单文件 12.8MB
+169
View File
@@ -0,0 +1,169 @@
# 阿里云 ESA 评估(对比 EdgeOne)
> 2026-10-04 · 问题:「阿里云也有个跟 EdgeOne 很像的服务,能不能考虑一下?」
> 现状:`usj.cc` 域名 **境外解析在 EdgeOne、境内解析在多吉云**(DNS 分线路)
## 一、结论先说
**能考虑,但对你这个站有一条硬伤,直接卡住:ESA Pages 每个项目最多 2000 个文件,你的产物是 3267 个。**
而 EdgeOne Pages 是 **20000 个**——你只用了 16%。
所以结论是:**换阿里云 ESA 不是"简化",是"降级到刚好装不下"。**
---
## 二、阿里云对标的到底是什么
| 腾讯云 | 阿里云 |
|---|---|
| EdgeOne(边缘安全加速平台) | **ESA(Edge Security Acceleration)** —— 就是原来的 **DCDN 改名** |
| EdgeOne Pages(Git 导入 + 自动构建 + 静态托管) | **ESA Pages**(也叫「函数和 Pages」) |
| Edge Functions / Cloud Functions | **ESA 边缘函数**(V8 Isolate,只支持 JS) |
| 节点 | 阿里云 3200+ 边缘节点 |
**能力确实是同级的**——都免费、都支持 ICP 备案、都能绑备案域名走大陆节点、都是「Git 导入 + 自动构建 + 全球分发」一套。你问得没错,这是对标产品。
**但两者的尺寸门槛差了 10 倍,这是分水岭。**
---
## 三、★ 决定性的一张表:文件数门槛
我逐个查了官方配额页(不是二手说法):
| 项目 | **ESA Pages** | **EdgeOne Pages** | Cloudflare Pages |
|---|---|---|---|
| **单项目文件数** | **2000** ❌ | **20000** ✅ | 20000 ✅ |
| 单文件大小 | 25 MB | 25 MB | 25 MiB |
| 源码包大小 | 1024 MB | — | — |
| 总存储容量 | — | 5 GB | — |
| 构建次数 | — | 500 / 月 | 500 / 月 |
| 构建超时 | — | 20 分钟 | 20 分钟 |
| 构建算力 | — | **4 核 6 GB** | — |
| 自定义域名 | — | 200 个 | — |
| **你的站** | **3267 → 超限 63%** | 3267 → 占 16% | 占 16% |
来源:阿里云官方文档《函数和 Pages 使用限制》——*"Pages 文件数 2000 个:每个 Pages 项目最多可上传 2000 个静态文件"*;EdgeOne 官方《限制与配额》——*"单项目文件数 20000"*。
**这一条把 ESA 从候选名单里筛掉了,其他指标都不用比了。**
> 附带发现:**EdgeOne Pages 的构建环境是 4 核 6 GB** —— 比你那台广州中转机(2 核 1.9 G)强得多。如果走它的 Git 集成自动构建,hugo 构建完全不需要你自己的机器。
---
## 四、你的 3267 个文件是怎么构成的
```
1337 html ← 文章页 + 分类/标签/归档页
1211 png ┐
295 webp │
254 jpg ├─ 图片/视频类合计 1778 个(占 54%)
7 mp4 │
6 jpeg │
3 svg ┘
66 xml ← sitemap / rss
59 js
3 woff2 / 3 woff / 3 mp3 / 3 css / 6 json / 2 txt
────────────────
3267 总计
```
**纯文本类(html/xml/css/js/json)只有 1473 个** —— 这个数字很关键,它意味着「如果非要用 ESA」还有一条窄路(见第六章)。
---
## 五、ESA vs EdgeOne:真实差异(去掉重复项之后)
免费套餐**大部分能力是重复的**,真正有差别的是这几条:
| 维度 | ESA | EdgeOne | 谁赢 |
|---|---|---|---|
| **文件数** | 2000 | **20000** | **EdgeOne(决定性)** |
| 大陆节点 | 有(需备案) | 有(需备案) | 平 |
| 流量 / 请求 | 不限 | 不限 | 平 |
| **开通大陆加速的门槛** | ⚠️ **默认不含大陆,要发帖解锁** | 直接可用 | **EdgeOne** |
| 免费套餐数量 | 每账号限 **1 个** | 站点 1 主域 + 200 子域 | EdgeOne |
| 构建算力 | 未披露 | 4 核 6 GB | EdgeOne(已知值) |
| 单文件限速 | 官方未披露;社区称"不限速",另有称峰值 5 Mbps —— **存疑** | 单文件单线程 4 Mbps(≈500 KB/s) | **ESA 可能赢** |
| WebSocket | 免费版**不支持**(第三方实测) | — | EdgeOne |
| 速度(第三方实测) | 大陆节点覆盖**更多**、网络速度**更高** | 加速效果更佳、全球覆盖更广(Anycast) | 各有说法 |
| 迁移成本 | 要重接一次 | **你已经在用** | **EdgeOne** |
### 值得注意的两点
1. **ESA 免费版默认不含中国大陆加速** —— 官方原文:*"By default, this plan does not include Chinese mainland acceleration."* 要解锁得**在任意社交/博客平台发一篇 30 字以上的推荐帖**(带 `#AlibabaCloudESA #ESAPages` 标签 + 官方图),再进群提交链接。你写博客顺手能完成,但这是个**额外操作**,跟"简化"反向。
2. **ESA 免费版的「不限速」有矛盾说法** —— 一方说无限速适合大流量,一方说峰值带宽约 5 Mbps。如果你的痛点是那个 12.8 MB 的 mp4(EdgeOne 下首载约 25 秒),这一条**值得实测**,但它是 ESA 唯一可能赢的地方。
---
## 六、如果非要用 ESA:唯一的一条路
把 **1778 个图片/视频剥离到对象存储**(OSS / 又拍云 / 多吉云存储),Pages 只托管 1473 个文本文件 → 落回 2000 以内。
**但代价是**:多一个存储服务、多一套域名与缓存配置、构建产物要改写图片链接。**这是"增加复杂度"换"换一家厂商",跟你的目标正好相反。**
不建议。
---
## 七、ESA 唯一值得拿的东西(互补用法)
**如果只是想用阿里云,不要用 ESA Pages,用 ESA 的 CDN 加速能力。**
ESA 免费版(Entrance plan)本质是**一个不限流量的 CDN**,可以回源到任意源站。它能做的是:
- 回源到你的又拍云 / OSS 存储 → 只加速,不托管,**没有 2000 文件限制**
- 用 ESA 的**边缘函数**收编现在的 `blog-admin`(CF Workers)
这确实有个价值:**阿里云一家能同时给你 CDN + Pages + 边缘函数 + 对象存储**,比「EdgeOne + CF Workers + 又拍云 + 多吉云」更统一。
但这又引入了**第四家厂商**,而你现在的问题恰恰是厂商太多。
---
## 八、而且你现在的双线路,其实已经是"简化过"的形态
你说的「境外 EdgeOne、境内多吉云」,本身就是**分线路 DNS 调度**的结果:
```
usj.cc
├─ 境外线路 → EdgeOne(Pages 项目,--area overseas)
└─ 境内线路 → 多吉云 CDN(融合 CDN,70+ 节点,20 GB/月免费)
```
**这已经做到了"境内外分流"** —— 跟 EdgeOne 双区域方案想达成的效果是一样的。所以真正的问题不是「CDN 选阿里云还是腾讯云」,而是:
> **要不要把这两条线合并成一家?**
- **合并到 EdgeOne**:改一个参数(`-a global`)或建国内站项目 → 砍掉多吉云 ✅
- **合并到 ESA**:从零接一次,还撞上 2000 文件上限 ❌
- **维持现状**:多一条线,但**不增加架构复杂度**——CDN 数量不等于复杂度
**注意最后一条**:你架构的复杂度大头在**构建与搬运**(Gitea、act_runner、中转机、COS、artifact 链),不在 CDN 数量。多留一个多吉云,不会让架构变复杂;砍掉那条搬运链才会。
---
## 九、建议
1. **别换 ESA Pages** —— 2000 文件上限是硬伤,你的站 3267,装不下。
2. **想收敛到一家,就走 EdgeOne** —— 你已经在用,同一个控制台、同一个项目(`-a global` 实验),或者建个国内站项目。迁移成本近零。
3. **多吉云可以留着** —— 它免费 20 GB/月、融合 CDN(依托大厂节点)、速度好,而且它是**独立的国内线路**,跟 EdgeOne 互为备份。不构成复杂度问题。
4. **如果哪天站点瘦身到 2000 文件以内**(比如把 1778 个图片全挪到对象存储),ESA 会重新变成候选 —— 但那时 EdgeOne 也一样能用,还是没必要换。
---
## 十、待核实项(不装作确定)
| # | 事项 | 状态 | 怎么验 |
|---|---|---|---|
| 1 | 多吉云当前的回源源站是谁(又拍云存储 / EdgeOne / 其他) | **待确认** | 多吉云控制台看回源配置 |
| 2 | ESA 免费版是否真的"不限速"(vs 峰值 5 Mbps) | **说法矛盾** | 需实测,但没必要为它迁移 |
| 3 | ESA Pages 是否支持 Gitee 作为代码源 | 官方只列 GitHub,第三方提到 Gitee | 若成立,对简化链路有价值 |
| 4 | EdgeOne Pages 的 Git 集成是否支持 Gitee | 上次标的第 5 条,仍未确认 | 若支持可砍掉自建 Gitea + act_runner |
---
## 十一、一句话总结
**阿里云 ESA 确实是对标 EdgeOne 的产品,能力同级——但 ESA Pages 限 2000 文件,你的站 3267 文件,直接在门口就被拦下。**
**换厂商解决不了你的问题;你的问题在构建与搬运链,不在 CDN 选谁。**