Commit Graph
4 Commits
Author SHA1 Message Date
zqlit 7abef4ac13 docs: 全量梳理文档结构;补 Gitea 迁移指南 / 架构精简候选;架构总览补证书管家子系统
用户四问:「把项目文件全部理一遍 还有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 迁移死线」两节;证书管家节补实测与遗留。
2026-10-06 22:27:36 +08:00
zqlit d3da972c3b feat(editor-api): 线上容器切「CNB 主 + Gitea 备」双推;备份补 fetch 步骤
用户定案:「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 说明;脚本表更新
2026-10-06 22:27:36 +08:00
zqlit 5f27ad321d chore(备份): 异地备份定案为 OpenList 离线 bundle,撤销全部 git 辅仓
用户定案:「辅助仓就用 openlist,其他不再考虑」。
据此把前一轮为 Gitee 铺的路全部收回,git 远端只剩 CNB 一个。

一、git 配置收口
- `pushall` 别名 `origin + gitee` → `!git push origin main`(只推唯一远端)
- 移除 `gitea` remote(自建 23.254.236.47:3001)—— 远端仓库本身没删,
  需要时可 `git remote add` 恢复;改动前配置存
  `.workbuddy-backup/git-remotes.20261006-211516.txt`
- 确认无 gitee 相关 credential 残留

二、发布链路去 Gitee 化(6 处)
- `deploy/editor-api/bootstrap.sh`
    · 删掉 GITEE_URL / GITEE_SSH_KEY 两个变量与「没给私钥就降级」的分支
    · PUSH_REMOTES 默认 → origin
    · 原「配 gitee 辅仓远端」一节改为「清理退役远端」循环(gh/gitee/gitea),
      让从旧部署续用的工作区自动恢复干净
- `docker-compose.editor.yml`、`editor-api/server.mjs` → 默认值 origin
- `editor-api/README.md` → 变量表同步
- `editor-api/Dockerfile` → 注释里的「CNB / GitHub」改「CNB」
- `blog-admin/src/routes/rss/tools.ts` → deploy-notify 的注释里
  「与 Gitea Actions 的构建通知配套」改为中性描述
  (该轮询链路 2026-10-04 起已被 CNB 国内节点直传取代)

三、`scripts/setup-cnb-remotes.sh` 重写为单远端模式
- 去掉 GITEE_URL / GITEE_TOKEN 参数、校验、凭据写入与自检提示
- 新增「清理退役远端」步骤
- 凭据处理改为「已存在空的 credential.helper 就不再添加」,
  不再用 --replace-all —— 本仓另有一个从 `$HOME/.workbuddy/secrets/cnb-token`
  读令牌的自定义 helper,那是有效的,不能被脚本抹掉

四、备份升级为「唯一辅仓」的配置
- 保留份数 3 → 7(一周窗口;每份 605.5 MB ≈ 4.2 GB,F50 有 256 GB)
    · `scripts/backup-task.cmd` 默认参数 --keep 7
    · `scripts/backup-bundle.mjs` 的 KEEP 默认值同步为 7
      (原先写的是 2,一直被命令行参数掩盖着)
- 远端目录 `/本地/备份` → `/本地/备份/blog-bundle`:
  根目录是用户自己在用的(放着 github-zqlit-*、local-repos-* 等手工备份),
  实测发现直接放根下的 bundle 已被清掉 —— 改子目录隔离,避免混放与误删

五、新增 `scripts/backup-run.mjs`:备份的推荐入口 + 失败告警
- 读 `.workbuddy-backup/openlist-backup.env`(只补空缺,环境变量优先)
- 跑 backup-bundle.mjs 并实时透传输出,同时留一份日志尾部
- 退出码非 0 → 经 `scripts/send_mail.js` 发告警邮件(附日志尾部与常见原因);
  成功默认不发,`--notify-success` 才发
- 退出用 `process.exitCode` 而非 `process.exit()`,避免截断未排干的 stdout
- 发信失败不改判备份退出码 —— 通知不该掩盖真正的故障
- 理由:这是当前**唯一**的异地备份,而「每天自动跑」的任务最典型的失败模式
  恰恰是静默的(F50 被带出门、换了网段、OpenList 没起来、口令改过……),
  没有告警就要等到真要用备份那天才发现
- `scripts/backup-task.cmd` 改调它

六、文档
- `架构总览.md`
    · §1.2 地址地图:备份行改指 F50/OpenList;通知行补「兼做备份失败告警」
    · §2 旅程图:双推改单推,并说明备份换了介质
    · §5.1 / §5.2 推送与远端:只剩 origin;补「已移除远端」表与恢复命令;
      GitHub 退役记录保留并补上「CI 定义也已删除」
    · §5.3 由「辅仓选型」改为「异地备份的定案」—— 明确不走 git 远端;
      平台对比数据保留备查,并注明 `bin/linux/hugo` 出库不必再做了
    · §5.6 补「唯一备份」定位、专属子目录、失败告警、keep 7、SMTP 配置键,
      实测数据更新为本次复测值
    · §6 待办:#2 定案、#3 不必做、#4 已移除、#8 已更新、#10 定位升级,
      新增 #11(F50 目录使用约定)
- `CNB构建落地方案.md` §4.0:双远端改单远端,脚本示例去掉 Gitee 参数
- `README.md`:推送说明改单推;脚本表补 backup-run.mjs

实测(2026-10-06,本轮复测):
  bundle 7.4s / AES-256-GCM 加密 1.3s / 上传 19.2s(31.6 MB/s)
  / 读回 sha256 一致 → 端到端 60.6s,退出码 0
  告警邮件链路已实测(发出一封「备份成功」验证信)

★ 一处过程记录,供以后避免重复踩坑:
  中途我把「本机沙箱里 `env -u ... cmd > file` 会让输出整个消失」
  误判成 process.exit 截断 stdout,并据此改了日志实现;
  随后用 `env -u FOO echo hi > file`(同样零输出)证伪 ——
  那是沙箱文件重定向的伪影,与脚本无关。相关改动已回滚,
  只留下本身无害的 process.exitCode 写法。
2026-10-06 21:27:28 +08:00
zqlit 8abe93d17a feat(backup): 整仓离线备份上线 —— bundle 加密后传中兴 F50 上的 OpenList
背景:GitHub 被按 AUP 清空后,用户提出自己的中兴 F50(5G CPE + 内置 256GB)
上跑着 OpenList,想用它当第三层备份。实测可行,已落地并跑通。

为什么是 bundle 而不是直接推 git:
  WebDAV 不支持原子的 rename/lock,bare repo 挂上去 push 会让对象写坏 ——
  表面成功、实际随机损坏,可能几个月后才发现。bundle 是单文件顺序写,安全。

为什么加密(用户一度想省掉):
  历史里含 .env、TLS 私钥、GITEA_SECRETS.md。介质是随身设备的内部存储,
  明文 = 把密钥放在一台可能丢失/刷机/送修的机器上。
  OpenList 的登录只保护「访问通道」,不保护「存储介质」——拆机就能读。
  加密成本实测仅 1.9~2.9 秒、体积不变;且 CNB/Gitea 仍是明文副本,
  口令丢失只是少一份备份,不构成单点。

实现(scripts/backup-bundle.mjs,零 npm 依赖):
  - git bundle create --all → AES-256-GCM(Node 内置 crypto)
  - 布局 magic(8)|salt(16)|iv(12)|密文|tag(16),scrypt(N=32768,r=8,p=1) 派生密钥
  - 为什么不用 gpg:本机 gpg 2.4.9 在 Windows 下已损坏(反复 stale lockfile,
    node spawn 直接 EBUSY);换内置 crypto 后零外部依赖且带认证标签
  - 上传后可选 --verify:下载回来比对 sha256,端到端闭环
  - --keep 控制远端保留份数;--decrypt 恢复;--list 盘点;--dry 不上传

定时任务(scripts/backup-task.cmd + Windows 计划任务 Blog-BundleBackup):
  - 每天 03:30 本地时间,默认 --verify --keep 3
  - InteractiveToken + LeastPrivilege、StartWhenAvailable、1h 超时、IgnoreNew 防重入
  - 日志追加到 .workbuddy-backup/logs/backup.log,超 5MB 轮转

★ backup-task.cmd 内容必须全 ASCII:
  cmd.exe 按当前代码页(zh-CN 是 GBK)解析批处理文件,而 node 输出 UTF-8。
  UTF-8 中文注释会吞掉 CR/LF 并把下一行当命令执行 —— 实测踩到(一条 rem 被当命令跑)。
  ASCII 是 UTF-8 子集,纯英文注释与 node 的中文输出混写不会乱。
  同理不能用 %date%(含本地化星期),改用系统时间 API 取 ISO 格式时间。
  日志轮转的 for 语句必须加 if exist 守卫,否则首次运行报「系统找不到指定的路径」。

实测(由计划任务实际拉起,非手工执行):
  bundle 6.8~9.9s(605.5MB)/ 加密 1.9~2.9s / 上传 19.4~20.6s(29.4~31.3 MB/s)
  / 下载回读 sha256 一致,端到端退出码 0
  恢复链路已演练:--decrypt → git bundle verify 报 "records a complete history"
  → 1008 提交完整一致

文档(架构总览.md):
  - §5.3 从「Gitee 两条硬约束」扩写为「辅仓选型」,补入云效 Codeup 基础版对照
    (Git 5GiB + 单文件命令行 200MB)—— 选它则 bin/linux/hugo 不必出库、
    构建链路一行不用改
  - 新增 §5.6 整仓离线备份(介质 / 为什么 bundle / 为什么加密 / 加密格式 / 用法 /
    配置 / 定时任务 / 恢复流程 / 实测数据)
  - §6 待办:#2 改为「辅仓选型未定」并说明 pushall 现状;#3 标注只有选 Gitee 才必须做;
    #9 补记「不改写历史」的唯一障碍已随 GitHub 消失;新增 #10 备份已上线 + 三项安全待办
2026-10-06 21:03:29 +08:00