d3da972c3b21b32e59b4e9a2afe5d28e32e71d22
用户定案:「editor-api 容器 采用 cnb主仓 gitea备份仓 openlist文件备份」。
三层各有各的失效模式,不能互相替代:
① 主仓 origin = CNB —— 源码 + 触发构建,依赖 CNB 平台
② 代码辅仓 gitea = 自建 Gitea —— 不受第三方平台规则约束,可 clone / 按提交追溯
③ 离线备份 = F50/OpenList —— 单一加密文件,平台全挂也能恢复
一、线上容器实测切双推(国内机 119.29.215.187)
先探测出两个关键事实:
· 国内机**能**直连自建 Gitea(/api/v1/version → 200,0.38s)
· 国内机**够不到**家里的 OpenList(192.168.0.1 私有地址,6s 超时)
→ 「文件备份」这一层只能由家庭机发起,国内机做不到(下面的 fetch 修复正因此
改动:
· /srv/blog 停在 432cf5e3,落后 origin/main 10 个提交且是其后代
→ git merge --ff-only 追平到 448b898e,**未强推**
· 挂 gitea 远端(凭据编在 URL 里,同本机做法)
· .env:PUSH_REMOTES=origin → origin,gitea;追加 GITEA_URL/USER/PASS
· docker-compose.yml 默认值同步为 ${PUSH_REMOTES:-origin,gitea}
· docker compose up -d --build 重建
验证(全部实测通过):
· 启动日志 [editor-api] 分支 main 推送远端 origin, gitea
· curl /health → "remotes":["origin","gitea"]
· 容器内推 origin ✅ / 推 gitea ✅(推临时分支 → ls-remote 确认 → 删分支,无残留)
· 三处 main 对齐 448b898e(本地 / origin / gitea);工作区干净
二、备份侧:修掉一个静默漏洞 —— 打包前必须先 fetch
git bundle create --all 取的是**本地已知** ref,其中 refs/remotes/origin/main
停在上次 fetch/pull 的位置。而这份备份跑在**家里**那台机器上,工作区并不会
随写作后台(editor-api)的发布自动更新。所以原先是:后台新发的文章**一篇都不
在备份里**,而备份照样报「成功」—— 失败是静默的。
· backup-bundle.mjs 打包前插入 git fetch --all --tags --prune
- 失败**不致命**(离线也得出得来备份)→ 降级为「用本地已有 ref 打包」+ 显著告警
- 新增 --no-fetch 可跳过;步骤号 1/6..6/6 → 0/7..6/7
- 新增一行 `快照 origin/main = <sha> <时间> <标题>` —— 恢复时第一件要确认的就是它
· backup-run.mjs:头部补三层说明;失败邮件的「常见原因」加上 fetch 降级这一条
验证:fetch 3s 拉完 origin+gitea → 打包 3.6s → 加密 1.7s → 上传 19.2s(31.5MB/s)
→ 读回 sha256 一致,快照 = 448b898e
三、bootstrap.sh:修掉另一个静默陷阱
脚本每次都会**重写** .env,而 .env 是 GITEA_PASS 的唯一落点 →
「重跑一次但没现给 GITEA_PASS」会把辅仓推送**静默关掉**(PUSH_REMOTES 降级成
origin),不报错、不提示,直到要恢复时才发现 Gitea 早就没在同步。
· 改为:命令行没给就从旧 .env 里捡回来(显式传新值仍优先)
· 并把 GITEA_URL/USER/PASS 也写进 .env(容器不读这几个键,仅作复用锚点)
· 该逻辑用 4 个用例离线验证(有/无 .env、显式覆盖、.env 里没 PASS),
并确认 set -euo pipefail 下不会被 [ -n ] && cmd 这类写法误触发退出
四、文档
· 架构总览.md:§1.2 地址地图加 editor-api 行;§2 旅程图补「两条推送入口同一套语义」;
§5.2 加「线上容器的推送目标」段(含验证方法与 pull 源只有 origin 的铁律);
§5.6 加「打包前必须 git fetch」+「覆盖范围」表(诚实列出够不到的部分);
§6 待办 #4/#10 更新、新增 #12
· editor-api/README.md:新增「落盘:写一次,存三处」
· README.md:推送说明补线上后台那一路与 fetch 说明;脚本表更新
优世界博客(usj.cc)
Hugo 静态博客 + 自研评论后端 + 写作后台。 构建与发布跑在腾讯云 CNB(国内节点),单次发布约 3.5 分钟,境内/境外两条线路一次推完。
架构全貌、迁移前后对比、运维要点 →
架构总览.md
一、项目构成
| 子系统 | 位置 | 技术栈 |
|---|---|---|
| 内容 | content/、themes/Ying/ |
Hugo 0.128.2 extended + Ying 主题 |
| 评论后端 | blog-admin/ |
artalk-cf:Cloudflare Workers + D1 + KV(api.200181.xyz) |
| 写作后台 | write-server/(线上 post.usj.cc)、write/(本地 Windows) |
Next.js |
| 发布 | .cnb.yml、deploy/ |
CNB 流水线 + Dockerfile |
二、目录结构
blog/
├── .cnb.yml # ★ CNB 流水线(push + 每日定时)
├── deploy/Dockerfile # 构建镜像(hugo 二进制由 bin/linux/hugo 提供)
├── bin/linux/hugo # Hugo extended 0.128.2(linux/amd64,供 CNB 构建用)
├── content/
│ ├── posts/<年>/<日期>-<标题>/ # 文章(Page Bundle,index.md + 图片)
│ ├── about.md / links.md / circles.md / archives.md
├── themes/Ying/ # 主题(layout / assets / data)
├── static/ # 原样复制进产物(emotion 表情、image、js …)
├── blog-admin/ # 评论后端(artalk-cf)
├── write-server/ # 线上写作后台
├── write/ # 本地写作前端
├── scripts/ # 各类工具脚本(见第六节)
├── hugo.toml # Hugo 主配置
└── 架构总览.md # ★ 架构文档
三、内容写作
文章结构
每篇文章是一个 Page Bundle:
content/posts/2024/2024-05-01-文章标题/
├── index.md # 正文
└── 配图.jpg # 同目录图片(可用相对路径引用)
URL 规则
由 front matter 的 slug 决定(hugo.toml 里 permalinks.post = "/:slug"):
---
title: "我的文章"
date: 2024-05-01
slug: "my-post"
---
生成 https://usj.cc/my-post.html(uglyURLs,带 .html)。
隐藏文章
在 front matter 加 status: hidden。构建前 scripts/add_draft_to_hidden.py 会把它转成
draft: true,不出现在列表里,但直达链接仍可访问。
本地预览
hugo server -D # 含草稿
四、发布流程
写作(write-server / write/)
│ git pushall (= git push origin main ; git push gitea main)
▼
origin → CNB 主仓 zqlit/blog ──触发──► CNB 流水线(国内节点,约 3.5 分钟)
├─ Hugo 构建
├─ 同步到又拍云(境内源站)
├─ 刷新又拍云 CDN
├─ 刷新多吉云 CDN
├─ 部署 EdgeOne Pages(境外线路)
└─ 邮件通知
gitea → 自建 Gitea(23.254.236.47:3001) 代码同步辅仓:只推不拉、不参与构建
- 推送:
git pushall—— 依次推origin(CNB 主仓,触发构建)与gitea(自建 Gitea 代码同步辅仓) ;只推主仓用git push origin main- 线上写作后台(国内机
editor-api容器)走同一套:PUSH_REMOTES=origin,gitea, 主仓成功即算发布成功,辅仓失败只警告不阻断 - ★ 两边同一条铁律:任何提交都必须先落到 CNB —— pull 源只有
origin, 只推gitea的提交后台看不见,下次发布会因 non-fast-forward 被拒
- 线上写作后台(国内机
- 备选异地备份:不走 git 远端。本机计划任务每天 03:30 把整仓 bundle
(打包前先
git fetch --all,否则备的是过期快照)加密后传到中兴 F50 上的 OpenList(见架构总览.md§5.6) - 触发:推送到
main;另有每日0 9 * * *(北京时间)定时构建 - 密钥:全部来自 CNB 密钥仓库
zqlit/blog-secrets,经.cnb.yml的imports注入, 仓库里没有任何明文密钥 - 改动
main即自动上线,本地无需构建
五、三个子系统
内容系统
Hugo + Ying 主题。hugo.toml 控站点信息、永久链接、Artalk 地址、弹幕等。
评论系统(blog-admin/)
自研的 Artalk v2 兼容服务端,跑在 Cloudflare Workers + D1(SQLite)+ KV:
- 前端用官方 Artalk 客户端(
themes/Ying/assets/js/libs/Artalk.js,本地打包,非 CDN) - 后端 API 基址
https://api.200181.xyz(评论/api/v2/*与 RSS 订阅/api/*同一 Worker) - 部署:
cd blog-admin && npm run deploy(详细步骤见blog-admin/README.md、部署清单.md)
写作后台
write-server/:线上版(Next.js),部署在独立主机write/:本地 Windows 版
六、常用脚本(scripts/)
| 脚本 | 用途 | 在哪跑 |
|---|---|---|
add_draft_to_hidden.py |
构建前把 status: hidden 转成草稿 |
CI |
refresh_cdn.js |
刷新多吉云 CDN(零依赖) | CI |
send_mail.js |
构建结果邮件通知(零依赖 SMTP) | CI |
setup-cnb-remotes.sh |
切换/重建 git 远端(CNB 主仓 + 自建 Gitea 辅仓;顺手清掉退役远端) | 本机 |
backup-run.mjs |
备份的推荐入口:跑 backup-bundle + 失败时发告警邮件 |
本机/计划任务 |
backup-bundle.mjs |
git fetch --all → 整仓 bundle → AES-256-GCM 加密 → WebDAV 传中兴 F50(见 架构总览 §5.6) |
本机/计划任务 |
backup-task.cmd |
上面的计划任务入口(每天 03:30;内容必须全 ASCII) | 计划任务 |
optimize_images.js |
图片批量压缩优化 | 本机 |
generate_circle_data.js |
抓友链 RSS 生成朋友圈数据 | 本机 |
update_link_lite_json.ps1 |
友链 links.yaml → JSON |
本机 |
add_ancient_chars.py / check_ancient_chars.py / merge_chars.py |
字体生僻字增补与校验 | 本机 |
cleanup_duplicates.js / migrate_slugs.js |
一次性维护脚本 | 本机 |
deploy_*.sh(又拍云 / EdgeOne / 定时)是本机手动部署的旧入口,日常已不需要—— 推送main由 CNB 自动完成。
七、相关文档
| 文档 | 内容 |
|---|---|
架构总览.md |
当前架构全貌(子系统、发布链路、迁移前后对比、运维要点) |
CNB构建落地方案.md |
迁 CNB 的实施方案与实测数据 |
代码源与构建平台选型.md |
平台对比(CNB / Gitee / GitLab / EdgeOne / 阿里云 ESA) |
砍COS改造步骤.md |
腾讯云 COS 下线记录 |
blog-admin/README.md |
评论后端完整说明 |
blog-admin/部署清单.md |
评论后端部署步骤 |
Languages
TypeScript
42%
JavaScript
32.2%
CSS
13.8%
HTML
6.2%
Shell
3.4%
Other
2.4%