用户定案:「自建 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 均通过
25 KiB
优世界博客 · 架构总览
更新时间:2026-10-04(CNB 迁移完成当日) 状态:迁移已落地并跑通(四线路全绿,单次发布约 3.5 分钟) 本文档只描述现状;迁移前的复杂度审计与选型过程见文末「相关文档」。
0. 一句话
四个子系统,一条 3 步的托管发布链路。构建与发布已从「自建 Gitea + act_runner + 广州中转机」迁到 腾讯云 CNB(cnb.cool) —— 需要自己运维的机器从 3 台降到 0 台。
1. 全景
1.1 四个子系统
| # | 子系统 | 技术栈 | 跑在哪 | 状态 |
|---|---|---|---|---|
| 1 | 内容系统 | Hugo 0.128.2 extended + Ying 主题,138 篇 md;产物约 3200 文件 / 424MB | 产物分发到 3 个 CDN | 正常 |
| 2 | 评论系统 | blog-admin/ = artalk-cf(Artalk v2 兼容服务端)+ RSS 机器人 |
Cloudflare Workers + D1 + KV → api.200181.xyz |
✅ 零服务器零运维 |
| 3 | 写作系统 | write-server/(Next.js 16,post.usj.cc)write/(本地 Windows 前端) |
独立 Docker 主机/本机 | 可用(两套前端功能重叠) |
| 4 | 发布基础设施 | CNB 流水线(.cnb.yml)+ 又拍云 + 多吉云 + EdgeOne |
腾讯云 CNB(托管) | 新建,已跑通 |
1.2 服务与地址地图
| 角色 | 地址 / 位置 | 说明 |
|---|---|---|
| 代码主仓 | cnb.cool/zqlit/blog |
CNB,私有;构建由它触发;唯一 fetch 源 |
| 代码辅仓 | 自建 Gitea 23.254.236.47:3001/zqlit/blog |
只推不拉,纯代码留档;不受第三方平台规则约束(见 §5.2) |
| 异地备份 | 中兴 F50 上的 OpenList(/本地/备份/blog-bundle/) |
备选异地备份:整仓加密 bundle,计划任务每天 03:30 自动跑,不依赖任何 git 服务(见 §5.6) |
| 构建 + 发布 | CNB 流水线(腾讯云国内节点,4 核 8G) | 托管,0 元(免费额度内) |
| 密钥仓库 | cnb.cool/zqlit/blog-secrets |
CNB「密钥仓库」类型,10 项变量经 imports 注入 |
| 境内源站 | 又拍云对象存储 | 由 CNB 国内节点直传(原为 if: false 的死代码) |
| 国内加速 | 多吉云 CDN | 回源又拍云 |
| 境外线路 | EdgeOne Pages(项目 hugo-blog,--area overseas) |
腾讯 EdgeOne 国际站 |
| 评论后端 | api.200181.xyz |
Cloudflare Workers |
| 通知 | QQ 邮件(imql@qq.com) |
已收敛为单一通道;同时承担备份失败告警(§5.6) |
关键点:境内、境外仍是两条独立线路,但由同一条流水线一次推完 —— 这是本次迁移最大的结构性改善。
2. 一次发布的完整旅程(3 步)
① 写作 write-server(网页) 或 write/(本地 Windows)
│
② git push git pushall = git push origin main ; git push gitea main
│ ├─ origin → cnb.cool/zqlit/blog (**主仓**,触发构建)
│ └─ gitea → 23.254.236.47:3001/zqlit/blog(自建 Gitea,代码同步辅仓)
│ ★ 备选异地备份不走 git 远端 —— 由本机计划任务每天 03:30
│ 跑整仓加密 bundle 传到 F50 上的 OpenList(§5.6)
▼
③ CNB 流水线(国内节点,约 3.5 分钟,7 个 stage 顺序执行)
├─ 1. Hugo 构建 草稿隐藏预处理 → hugo --minify --gc → 算构建哈希
├─ 2. 同步到又拍云 upx sync(境内直传,非阻断但如实上报)
├─ 3. 刷新又拍云 CDN purge 首页 + sitemap/rss/archives/posts 等固定入口
├─ 4. 刷新多吉云 CDN node scripts/refresh_cdn.js
├─ 5. 部署 EdgeOne edgeone pages deploy --area overseas
├─ 6. 上报部署状态 POST api.200181.xyz/api/deploy-status
└─ 7. 邮件通知 成功 → stages 末尾;失败 → failStages
对照:迁移前是 7 步,中间有 3 个环节是你自己运维的服务器。
3. 流水线细节(.cnb.yml,284 行)
| 触发 | 条件 | 说明 |
|---|---|---|
| push | 推送到 main |
主链路 |
| crontab | 0 9 * * *(Asia/Shanghai) |
每天 09:00,与 push 共用同一组 stage(YAML 锚点) |
| 机制 | 实现 |
|---|---|
| 构建镜像 | deploy/Dockerfile,hugo 二进制随仓库携带(bin/linux/hugo,linux/amd64) |
| ⚠️ docker 上下文 | by: [bin/linux/hugo] —— CNB 的 docker build 只看得见 Dockerfile + by 列出的文件,漏写会报 not found |
| 密钥注入 | imports: https://cnb.cool/zqlit/blog-secrets/-/blob/main/secrets.yml |
| stage 间传状态 | 落盘 .ci_status(原版靠 needs.*.outputs) |
| 并发控制 | lock.cancel-in-progress(对应原 concurrency) |
| 非阻断 | 又拍云三步 + 状态上报 + 邮件用 allowFailure: true,失败仍写状态如实上报 |
| 资源 | cpus: 4(4 核 8G) |
4. 迁移前后对比
| 维度 | 迁移前(Gitea 自建) | 迁移后(CNB) |
|---|---|---|
| 发布链路环节 | 7 步 | 3 步 |
| 自维护机器 / 服务 | 3 台(Gitea 主机 + act_runner + 广州中转机) | 0(构建托管) |
| 流水线定义 | GitHub Actions 1169 行 | .cnb.yml 284 行 |
| 产物传递 | 打 tar.zst → artifact → 中转机跨境下载 |
同一 pipeline 共享工作目录,无需传递 |
| 又拍云直传 | if: false(境外 runner 必挂,从未跑通) |
已跑通(国内节点直传) |
| COS 中转层 | 需要(存储 + 流量账单) | 已砍 |
github.server_url 兼容分支 |
8 处 | 0 |
| 通知 | 3 套(TG / 飞书 / 邮件),约 400 行 | 1 套(邮件) |
| 图片优化 + 反向 commit | CI 内每轮白跑(15 张老图处理不掉) | 未迁(如需保留,照搬 scripts/optimize_images.js) |
| 构建环境 | 境外 VPS | 腾讯云国内节点 4 核 8G |
| 费用 | VPS 月租 | 0 元(100GiB 仓库 + 160 核时/月) |
| 单次发布耗时 | — | 约 3.5 分钟 |
原链路里那些补丁(COS 中转 /
sync.sh每分钟轮询 / artifact 打包 / 兼容分支) 唯一根因是「runner 在境外」。CNB 节点在国内,这一整层随之消失。
5. 运维要点
5.1 推送
git pushall # CNB 与 Gitea 都推 —— 发布用这个
git push origin main # 只推 CNB(触发构建)
git push gitea main # 只推 Gitea 辅仓
git pushall 的实际定义:
!git push origin main; ec=$?; git remote | grep -qx gitea && git push gitea main; exit $ec
本仓有两个 git 远端:origin(CNB 主仓)+ gitea(自建 Gitea 代码同步辅仓)。
演变路径:GitHub 双推 →(GitHub 被 AUP 清空)→ 一度只剩 origin → 现在回到双远端。
注意 Gitee 从头到尾没真正建起来过(卡体积门槛,见 §5.3),别把它记成一代。
三条设计上的取舍:
- 不用「一个 remote 挂多个 pushurl」 —— 实测 git 是「顺序推、遇错即停」, 第一个失败后面的都不推,备份意义就没了。所以用「独立 remote + 别名」。
- 别名用
;串联而非&&,正是为了让一段失败不影响另一段(主仓挂了辅仓照样推); 再exit $ec把退出码还给主仓,免得辅仓成功掩盖主仓失败。 - gitea 那一段带
git remote | grep -qx gitea前置判断 —— 没配辅仓时静默跳过,而不是白报一行「'gitea' does not appear to be a git repository」。
5.2 远端
| remote | 地址 | 角色 | fetch | push |
|---|---|---|---|---|
origin |
https://cnb.cool/zqlit/blog.git |
主仓(CNB) | ✅ 唯一 fetch 源 | ✅ 触发 CNB 构建 |
gitea |
http://23.254.236.47:3001/zqlit/blog.git |
代码同步辅仓(自建,Gitea 28.0.0) | ⛔ 只推不拉 | ✅ 代码留档,失败不阻断 |
- Gitea 凭据编在远端 URL 里(
http://zqlit:<密码>@…):容器/脚本环境没有交互终端、 也没有可用的凭据助手,这是让git push gitea免交互跑通的唯一简单写法。 明文只落在本机.git/config(以及服务器上容器内的同名文件),不进仓库、不随之推送。 ⚠️ 口令里若含#或&会被拼 URL 的sed吃掉 —— 那种情况请改用访问令牌。 - 自建 Gitea 不参与构建:
.github/已删、Actions 早已停用,它纯粹是代码副本。 当前版本28.0.0,走 HTTP + 3001 端口,本机直连即可(不需要广州中转机, 与当初「本机跑不通 git smart HTTP」的旧结论不同 —— 那次是沙箱代理的问题)。 - 接入时的同步基线:Gitea 停在
ed38f938(2026-10-04),落后 57 个提交, 且是本地 HEAD 的祖先 → 一次 fast-forward 就追平,无需强推、不丢历史。
已移除的远端(改动前的完整配置快照留在 .workbuddy-backup/git-remotes.*.txt):
| remote | 地址 | 移除于 | 原因 |
|---|---|---|---|
gh |
github.com/zqlit/blog |
2026-10-06 | 账号被标记、仓库被按 AUP 清空(见下),别再往回加 |
gitee |
(从未真正建立过) | — | 两条硬门槛过不去(§5.3) |
⚠️ GitHub 退役记录(2026-10-06)
起因:GitHub 账号被平台标记,随后仓库被按 AUP 合规条款清空 —— 远端留下一条孤立提交(无父提交):
c21d5669 2026-10-06 20:08:32 +0800 zqlit <zqlit@users.noreply.github.com> chore: remove repository content (AUP compliance)同时远端
main的历史与本地完全分叉(两边只共享 2024-07-11 的Initial commit, 之后每个提交 SHA 都不同;远端那份是 989 提交、剥离了.env/私钥/大二进制的变体)。 教训:别把「备份」寄托在会对内容做合规处置的平台上; 且一旦某次历史被重写,SHA 从此再也对不上。本项目随后清掉了 GitHub 的全部痕迹:remote、推送别名、发布链路里 4 处硬编码 (
bootstrap.sh/docker-compose.editor.yml/server.mjs/README.md)、 以及 CI 定义本身(整个.github/目录,1268 行)。
5.3 远端与备份的分工(2026-10-06)
结论:git 辅仓用「自建 Gitea」,备份用「整仓加密 bundle → OpenList」。两者都要,各管一段。
| 层 | 载体 | 保住什么 | 依赖 |
|---|---|---|---|
主仓 origin |
CNB | 源码 + 触发构建 | CNB 平台 |
代码辅仓 gitea |
自建 23.254.236.47:3001 |
随时 clone 回来、按提交追溯 |
自己的机器 |
| 离线备份 | F50/OpenList 上的加密 bundle(§5.6) | 完整历史 + 所有对象,平台全挂也能恢复 | 家里的局域网 |
两层的区别不是「多一份」,而是失效模式不同:辅仓是在线、可增量、可浏览的副本, 但仍在别人的磁盘上;bundle 是离线、离线介质、单一文件的全量快照, 连 git 服务都没有也能恢复。所以辅仓不能替 bundle,bundle 也不如辅仓顺手。
为什么不选「另一家托管平台」当辅仓(Gitee / 云效 Codeup / GitLab): 把副本放到另一家同样性质的托管平台上,只是把鸡蛋从左边口袋挪到右边口袋 —— GitHub 那次 AUP 清空就是活证。自建 Gitea 是唯一「不受第三方规则约束」的在线选项, 它早在建站初期就在跑,本就该留着。
下面这些实测数据保留下来 —— 下次再冒出「换一家托管平台当辅仓」的念头时, 这几张表就是答案。
体积分布(HEAD,实测 457.4 MB / 2509 文件):
| 目录 | 体积 | 说明 |
|---|---|---|
content/(含 posts/) |
≈ 308 MB | 博客正文与媒体(mp4/mp3/大图),搬不走 |
bin/linux/hugo |
83.1 MB | CNB 构建用的 hugo 二进制,可移出 git |
static/ |
≈ 38 MB | 站点静态资源 |
themes/ |
20.3 MB | Ying 主题 |
| 其余全部 | ≈ 8 MB | 代码、文档、脚本 |
Gitee 的两条硬约束 —— 都不满足:
| 约束 | Gitee 免费版 | 本仓库当前 | 判定 |
|---|---|---|---|
| 单仓库容量 | ≤ 500 MB | .git 620 MB |
❌ 超 24% |
| 单文件大小 | ≤ 50 MB | bin/linux/hugo 83.1 MB |
❌ 必删 |
| 用户总仓库容量 | 5 GB | 620 MB | ✅ |
| 私有仓协作人数 | 5 人 | 个人 | ✅ 无关 |
bin/linux/hugo 是硬门槛:不管怎么瘦身历史,只要它还跟踪在 HEAD 里,Gitee 一律拒收。
→ 移出后 HEAD ≈ 374 MB,才有一份余量。
对照:阿里云效 Codeup 基础版 —— 两条硬约束一条都不存在:
| 项 | Gitee 免费版 | 云效 Codeup 基础版 |
|---|---|---|
| 单库容量 | ≤ 500 MB | Git 5 GiB + LFS 5 GiB |
| 单文件 | ≤ 50 MB | Web 50 MB / 命令行 200 MB |
620 MB 的 .git |
超 24%,推不上 | 占 12.1% ✅ |
| 83.1 MB 的 hugo | 超 66%,一律拒收 | 占 41.6% ✅ |
| 价格 | 免费 | 0 元/人/年,不限人数、不限仓库数 |
→ 若选 Codeup,bin/linux/hugo 不必出库,deploy/Dockerfile 与 .cnb.yml 的 by:
字段一行都不用改(首推 620 MB 也在其 60 分钟推送超时内)。
注意两点:① 默认「中心组织」只支持阿里云 RAM 账号登录、且必须绑钉钉,
不接受的可选 2025-12 上线的「地域组织」(华东2上海,支持自建账号密码);
② 基础版无「删库保护」,容量到 90% 提醒、超限直接禁止写操作(连删文件都做不了)。
已有的阿里云 OSS AccessKey 不能用来建 Codeup 仓库 —— git 推送走独立的克隆账号密码或 SSH Key。
★ 这些都只是「换一个篮子」,不是容灾。 CNB 与 Gitee/Codeup 同属国内大区;GitHub/GitLab 又执行同一套合规逻辑 (本轮 GitHub 被按 AUP 清空即为例证)。 所以托管平台这条路一个都没采用 —— 在线辅仓回到自建 Gitea(§5.2, 不受第三方规则约束),另加一层不依赖任何 git 服务的离线备份(§5.6)。
顺带一个副作用:bin/linux/hugo「出库」这件事不用做了。
那个 83.1 MB 二进制之所以成为问题,只因为它卡在 Gitee 的单文件 50 MB 上限上。
既然不走 Gitee,deploy/Dockerfile 的取 hugo 方式与 .cnb.yml 的 by: 字段
一行都不用改。
5.4 凭据
- 令牌存仓库外:
~/.workbuddy/secrets/cnb-token - 本仓
credential.helper指向一个自定义脚本;.git/config无明文 - ⚠️ 本机全局 helper 是 GCM(会弹窗、对第三方 HTTPS 远端还会挂死);wincred 对 cnb.cool 有过「幽灵记录」删不掉 → 故用自定义 helper 绕开
5.5 密钥
- 修改密钥 → 编辑 CNB 密钥仓库的
secrets.yml(网页编辑,禁 clone) - 本地
cnb-secrets.yml只是粘贴草稿(已 gitignore,不入库) - ⚠️ CNB 的
imports是一层映射(key 就是变量名本身);又拍云服务名在 CNB 里必须叫UPYUN_SERVICE(旧 Gitea 里叫UPYUN_BUCKET,照抄会「变量未定义」)
5.6 整仓离线备份(中兴 F50 / OpenList)★ 备选异地备份
定位:2026-10-06 起这一层是备选异地备份 —— 在线那一路已有自建 Gitea 代码辅仓(§5.2)。 但它的地位并没有因此变轻:它是唯一一层「不依赖任何 git 服务」的备份, 平台全挂、账号被封、服务器被回收都影响不到它。所以它的可靠性与失败可见性 依然是最要紧的:一旦悄悄失效,能挡住「平台级事故」的就只剩它。 失败告警因此是必备件,不是加分项。
目标介质:中兴 F50 5G CPE 内置 256 GB 存储,上面跑 OpenList
(http://192.168.0.1:5244,WebDAV 端点必须带 /dav 前缀)。7×24 常开、走局域网、
不依赖任何 git 平台账号 —— 这是它比「再选一个代码托管平台」更值钱的地方。
存放位置:/本地/备份/blog-bundle/ —— 专属子目录,不是 /本地/备份/ 根下。
根目录由用户自己在用(放着 github-zqlit-*、local-repos-* 等手工备份),
把文件混进去既容易被误删,也让「清理旧份」多一层风险。
(早期版本确实直接写在根目录下,实测发现文件已被清掉,故改为子目录隔离。)
为什么是 bundle 而不是直接推 git:WebDAV 不支持原子的 rename/lock,
把 bare repo 挂上去直接 git push 会让对象写坏 —— 表面成功、实际随机损坏,
可能几个月后才发现。bundle 是单文件顺序写,没有这个问题。
为什么加密:本仓库历史里含 .env、TLS 私钥、GITEA_SECRETS.md。
介质是一台随身设备的内部存储,明文等于把密钥放在可能丢失 / 刷机 / 送修的设备上。
OpenList 的登录只保护访问通道,不保护存储介质 —— F50 丢了拆开就能读。
为什么不用 gpg:本机 gpg 2.4.9 在 Windows 下已损坏(反复 removing stale lockfile,
node spawn 直接 EBUSY)。改用 Node 内置 crypto 的 AES-256-GCM:
零外部依赖、带认证标签(能检测篡改/截断)、可流式处理 600 MB 不爆内存。
加密文件布局:magic(8) | salt(16) | iv(12) | 密文(...) | GCM tag(16),
密钥由 scrypt(N=32768, r=8, p=1) 从口令派生。
用法(scripts/backup-bundle.mjs,零 npm 依赖):
# 推荐入口:带失败告警的包装器(计划任务用的就是它)
node scripts/backup-run.mjs # = backup-bundle 的默认参数
node scripts/backup-run.mjs --verify --keep 7 # 计划任务的默认组合
node scripts/backup-run.mjs --dry --notify-success # 试跑并强制发一封成功邮件
# 底层脚本(backup-run.mjs 就是转发到它)
node scripts/backup-bundle.mjs # 加密 + 上传 + 比对字节数
node scripts/backup-bundle.mjs --verify # 额外下载回来比对 sha256(端到端最可靠)
node scripts/backup-bundle.mjs --dry # 只打包加密,不上传
node scripts/backup-bundle.mjs --keep 7 # 远端保留最近 7 份(默认值)
node scripts/backup-bundle.mjs --list # 列出远端现有备份
node scripts/backup-bundle.mjs --decrypt <文件> [--out x.bundle] # 恢复用
配置(按序查找,先找到生效):环境变量 → ~/.openlist-backup.env
→ .workbuddy-backup/openlist-backup.env(已在 .gitignore 内)。
| 键 | 用途 |
|---|---|
OPENLIST_URL / OPENLIST_USER / OPENLIST_PASS |
OpenList 地址与账号 |
OPENLIST_DIR |
远端目录,当前 /本地/备份/blog-bundle |
BACKUP_PASSPHRASE |
bundle 加密口令 |
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_TO |
失败告警发信(复用 CNB 流水线那套 QQ 邮箱授权码) |
⚠️
BACKUP_PASSPHRASE是恢复 bundle 的唯一钥匙,必须另存进密码管理器。 它不构成单点:CNB 主仓与自建 Gitea 辅仓都还是明文副本,口令丢了只是少一份备份。
失败告警(scripts/backup-run.mjs)—— 这是「离线那一层」的必备件。
跑完备份后,退出码非 0 就通过 scripts/send_mail.js 发一封告警邮件
(主题带 ★,正文附日志尾部与常见原因清单)。成功时默认不发(避免每天一封的邮件疲劳),
加 --notify-success 才发。
为什么非做不可:「每天自动跑」的任务最典型的失败模式恰恰是静默的 —— F50 被带出门、换了网段、OpenList 重启后没起来、WebDAV 口令改过…… 没人会主动发现,直到真要用备份那天。 发信逻辑独立于备份逻辑:发信失败不改判备份的退出码,通知不该掩盖真正的故障。
定时任务:Windows 计划任务 Blog-BundleBackup,每天 03:30(本地)
执行 scripts/backup-task.cmd(默认 --verify --keep 7)。
设置:InteractiveToken + LeastPrivilege、StartWhenAvailable(错过就补跑)、
ExecutionTimeLimit=PT1H、MultipleInstances=IgnoreNew(防重入)。
日志追加到 .workbuddy-backup/logs/backup.log,超 5 MB 自动轮转。
保留 7 份 = 一周窗口(每份 605.5 MB ≈ 4.2 GB,F50 有 256 GB)。 原本是 3 份;定位成「离线那一层备份」后放宽 —— 空间不值钱,回溯窗口值钱。
★ backup-task.cmd 内容必须全 ASCII:cmd.exe 按当前代码页(zh-CN 是 GBK)
解析批处理文件,而 node 输出 UTF-8;UTF-8 中文注释会吞掉 CR/LF、
把下一行当命令执行(实测踩到:一条 rem 被当命令跑了)。
ASCII 是 UTF-8 子集,纯英文注释与 node 的中文输出混写不会乱。
恢复流程:
node scripts/backup-bundle.mjs --decrypt blog-<时间戳>.bundle.enc --out blog.bundle
git bundle verify blog.bundle # 应报 "records a complete history"
git clone blog.bundle blog # 或 git fetch blog.bundle 'refs/*:refs/*'
实测数据(2026-10-06):
| 阶段 | 耗时 |
|---|---|
git bundle create --all |
6.8 ~ 9.9 s(605.5 MB) |
| AES-256-GCM 加密 | 1.3 ~ 2.9 s(体积不变) |
| WebDAV 上传 | 19.2 ~ 20.6 s → 29.4 ~ 31.6 MB/s |
| 下载回读 + sha256 比对 | ≈ 30 s,一致 |
端到端退出码 0;恢复链路已演练(解密 → git bundle verify → 1008 提交完整一致);
失败告警的邮件链路也已实测(发出一封「备份成功」验证信到 imql@qq.com)。
6. 遗留待办
| # | 项 | 说明 |
|---|---|---|
| 1 | gh remote(GitHub) |
✅ 已移除(2026-10-06)。GitHub 账号被标记、仓库被 AUP 清空 —— GitHub 已彻底退出本项目:远端、推送别名、发布链路 4 处硬编码、CI 定义全部清干净 |
| 2 | 远端与备份定案 | ✅ 已定案(2026-10-06):CNB 主仓 + 自建 Gitea 代码辅仓 + OpenList 离线 bundle(§5.2/§5.3/§5.6)。自建 Gitea 不受第三方规则约束,是唯一「平台出事也带不走」的在线副本;离线 bundle 保完整历史。选型依据(Gitee 两道硬门槛、Codeup 对照)保留在 §5.3 备查 |
| 3 | bin/linux/hugo 出库 |
✅ 不必做。那个 83.1MB 二进制只卡 Gitee 的单文件上限;既然不走 Gitee,deploy/Dockerfile 的取 hugo 方式与 .cnb.yml 的 by: 字段一行都不用改 |
| 4 | gitea remote |
✅ 已恢复(2026-10-06):作为代码同步辅仓重新挂上,pushall 恢复双推(origin + gitea)。接入时 Gitea 停在 ed38f938,一次 fast-forward 追平(+57 提交),未强推、未丢历史。发布链路 5 处默认值同步改回 origin,gitea(bootstrap.sh / docker-compose.editor.yml / server.mjs / README.md / setup-cnb-remotes.sh) |
| 5 | GitHub 侧旧 workflow | ✅ 已删除(2026-10-06)。整个 .github/ 目录移除(deploy.yml 1169 行 + aliyun-backup.yml + cleanup.yml),共 −1284 行 —— GitHub 已不再是任何环节的依赖。旧实现可在 git 历史中查 |
| 6 | 令牌 scope | 缺 repo-cnb-history:r(读构建日志);补上后 agent 可自行排错 |
| 7 | Gitea 主机 / 广州中转机 | Gitea 主机已回归(2026-10-06):作为代码辅仓,本机直连 23.254.236.47:3001 即可推。广州中转机仍不参与 git 链路 |
| 8 | README.md |
✅ 已更新(去 GitHub 化 + 补上备份脚本说明) |
| 9 | 敏感文件仍在跟踪中 | .env、GITEA_SECRETS.md、write-server/nginx/ssl/privkey.pem、content/posts/2024/.../setup-secrets.png —— 若还要推任何新平台,先 git rm --cached 并轮换凭据。⚠️ 当初「不改写历史」的唯一顾虑是备份会分叉;GitHub 那份已消失,这个顾虑没有了 → git filter-repo 现在是可行窗口(代价:全部提交 SHA 改变)。离线 bundle 已加密(§5.6),不受此影响 |
| 10 | 整仓离线备份 | ✅ 已上线(2026-10-06),现为备选异地备份(在线那一路是自建 Gitea 辅仓),见 §5.6。计划任务 Blog-BundleBackup 每天 03:30 跑,端到端已验证;失败会发告警邮件(已实测)。待补三件安全项:把 BACKUP_PASSPHRASE 抄进密码管理器;给 OpenList 改掉 admin 密码 + 开两步验证(现 otp: false,且旧密码已出现在对话里);确认 5244 端口未暴露到公网 |
| 11 | F50 存储目录的使用约定 | /本地/ 下的 刷机 / 系统 / 资料 / 软件 / 驱动 / 备份 都是用户自己在管的类别。本项目的 bundle 只写 备份/blog-bundle/ 子目录,不与用户手工备份(github-zqlit-*、local-repos-*)混放 |
附:相关文档
CNB构建落地方案.md— 迁移实施方案与实测数据代码源与构建平台选型.md— 平台对比(CNB / Gitee / GitLab / EdgeOne / 阿里云 ESA)砍COS改造步骤.md— COS 下线记录EdgeOne双区域方案评估.md、Gitee方案评估.md、阿里云ESA评估.md、精简方案-只留CF和Hugo.md— 决策期评估
证据出处(现行):.cnb.yml(284 行)、deploy/Dockerfile、bin/linux/hugo、
scripts/send_mail.js、scripts/refresh_cdn.js、scripts/setup-cnb-remotes.sh、
scripts/backup-bundle.mjs、scripts/backup-run.mjs、scripts/backup-task.cmd。
原 GitHub Actions 流水线(.github/workflows/deploy.yml,1169 行)已于 2026-10-06 删除,
需要查请翻 git 历史。