zqlit
|
448b898e40
|
feat(远端): 恢复自建 Gitea 代码辅仓,OpenList 降为备选备份
用户定案:「自建 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 均通过
|
2026-10-06 21:42:08 +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
|
b39dc29753
|
chore(远端): 移除 GitHub,改为「CNB 主仓 + Gitee 辅仓」
起因:GitHub 账号被平台标记,随后仓库被按 AUP 合规条款清空 ——
远端只剩一条孤立提交(无父提交、无内容):
c21d5669 2026-10-06 20:08:32 +0800 zqlit
chore: remove repository content (AUP compliance)
同时远端 main 的历史与本地**完全分叉**:两边只共享 2024-07-11 的 Initial commit,
之后每个提交 SHA 都不同(远端那份 989 提交、剥离了 .env/私钥/大二进制)。
所以 `git push gh main` 无法快进,也不可能再当备份用。
改动:
- `git remote remove gh`(改动前配置存 .workbuddy-backup/git-remotes.20261006-201354.txt)
- `pushall` 别名 → `git push origin main; git push gitee main`
- `scripts/setup-cnb-remotes.sh`:参数化第二个远端为 Gitee,
顺带把「若残留 gh remote 就清掉」做进脚本;Gitee 凭据走 wincred
- `架构总览.md`:§2 发布旅程图、§5.2 远端表、§6 遗留待办全部改指向;
新增 §5.2 的 GitHub 退役记录(含那条孤立提交的原文)
与 §5.3「Gitee 辅仓的两条硬约束」实测核算
- `CNB构建落地方案.md`、`README.md`:远端与 pushall 说明同步
- **发布链路一起去 GitHub 化**(这几处原先都写死了 gh):
· `deploy/editor-api/bootstrap.sh` GH_URL/GH_SSH_KEY → GITEE_URL/GITEE_SSH_KEY,
remote `gh` → `gitee`,known_hosts/私钥文件名跟着改;
保留原设计:**没给私钥就自动降级成只推 origin**,不会让每次发布都报错
· `docker-compose.editor.yml` PUSH_REMOTES → origin,gitee
· `editor-api/server.mjs` 默认值 → 'origin,gitee'
· `editor-api/README.md` 变量表同步
★ 尚未解决 / 需要决策的两条 Gitee 硬约束(详见 架构总览.md §5.3):
1. **单文件 ≤ 50MB,而 `bin/linux/hugo` 是 83.1MB**
—— 不管怎么瘦身历史,只要它还跟踪在 HEAD 里,Gitee 一律拒收。
2. 单仓库 ≤ 500MB,而 `.git` 是 620MB
—— 移出那个 83MB 后 HEAD ≈ 374MB,才有余量。
另:线上 editor-api 容器的 .env 仍是 `PUSH_REMOTES=origin,gh`,
且仓库里还有一个 `gh` remote。因为推送逻辑对辅仓失败只警告、不阻断发布
(`git.mjs`:主仓成即算成),所以**线上发布没有坏**;
但要真正切到 Gitee,需要等 Gitee 仓库与私钥就位后再部署一次。
|
2026-10-06 20:18:29 +08:00 |
|
zqlit
|
740d77e4cb
|
fix(ssl): 修掉 5 处静默失败,本项目全面接管签发部署,Worker 退回只读
一、1Panel 部署器三个缺陷(其中两个此前完全不可见)
1. `POST /websites/{id}/https` 的字段名是 `websiteSSLId`,不是 `sslId`。
发 `sslId` 会被 Go 静默忽略成零值 0,于是
`websiteSSLRepo.GetFirst(WithByID(0))` → 返回
`HTTP 200 + code 500「服务错误: record not found」`,
报错文案落在 DB 层,完全指不到参数名 —— 整个 t-t.live 部署被这条卡住。
对照实验:`sslId=13 → 500` / `websiteSSLId=13 → 200 code=200`。
2. 换证书内容的接口选错。`POST /websites/ssl/update` 的结构体
`WebsiteSSLUpdate` **根本没有** `certificate` / `privateKey` 字段,
传了被丢弃、且 `domains` 只从 `otherDomains` 取(不传就清空),
还会顺带把 `autoRenew` 置 false —— 而它**照样返回 200 success**。
实测:原样 update 后 5 个站点的 `ssl/*.pem` mtime+md5 一个都没变。
正确接口是 `POST /websites/ssl/upload` + `sslID > 0`:取记录 → 覆盖
→ 重算 ExpireDate/domains → `UpdateSSLConfig()` → 重新物化站点文件。
副作用:`Upload()` 把 `primaryDomain` 重算成证书第一个 SAN
(#11 因此从 `usj.cc` 漂成 `*.usj.cc`)→ 必须补 `domains` 兜底匹配,
否则每次续期都新建一条重复记录。
3. `deploy()` 幂等捷径漏了「记录被换过」这一维:`sslId` 没变但内容变了时
会跳过绑定,站点文件就停留在旧证书。改为引入 `replaced` 标志强制重绑。
二、另外两处静默失败
4. `parsePemInfo()` 对**完整链**返回 `{}`:旧实现把 PEM 各段 base64 拼接后
一次 `atob`,中间段尾部的 `=` 填充导致抛错,整函数返回空 →
`rec.expireAt` 退化成「签发时刻 + 90 天」。而完整链恰恰是部署器最常
拿到的形态。改为只解析第一段(叶证书)。
顺带新增 `derLen()` / `parseSanFromDer()`,精确定位 SAN 扩展
OID `2.5.29.17` 再读 `[2] dNSName`,替掉原来的字节扫描启发式。
这条同时是「多吉云复用失效」的根因:判据缺到期时间,只看域名集合
就永远认为已覆盖 → 续期静默空转。修好后判据带上 `notAfter` 比对(1 天容差)。
5. DNS-01 挑战通知会撞 `400 authorization must be pending`(200181.xyz 连中两次)。
这是**竞态**不是逻辑错:「先读状态再 POST」挡不住毫秒级窗口。
已在 POST 侧做幂等容错(只认这一句),最终由 `pollAuthz` 定论。
三、Worker 退回只读
- `crons` 去掉 `10 4 * * *`,`index.ts` 里 renew 分支整体删除
- `POST /ssl/issue` 改 **501 硬拒绝**(而不是静默降级),响应给出国内机命令
- 签发 + 部署整条链路跑在国内机容器 `cn-certkeeper`
四、顺带修掉的两个「配置被悄悄抹掉」
- `configSave()` 不再丢掉表单不管理的 `probe_connect` / `probe_sni`。
之前管理员在面板改任何一项,这两个字段就会被清空,
后果是挂在 CDN 后的 t-t.live 探针退回公网、被误判成「还剩 80 多天」,
源站证书到期也不续。现在保存时从旧配置带过来。
五、新增 4 个常驻运维工具(deploy/cn-certkeeper/src/)
- `renew-one.mjs` 只对单个域名签发+部署(原 `/renew` 无域名过滤,会全量重签)
- `redeploy.mjs` 复用已签好的证书只重跑部署(不碰 ACME,不白烧配额)
- `rollback-dogecloud.mjs` 应急把 CDN 域名绑回指定证书 id
- `txt-inspect.mjs` `_acme-challenge` 下的 TXT 残留盘点/清理
- `selfcheck-acme.mjs` CA 层诊断:只读目录 + 复用账户,不签发不部署
六、测试与文档
- 自测新增 [11] 节 8 项 PEM 解析回归(样本是 openssl 现场生成、内联写死的
叶+中间证书,两段都以 `=` 结尾,正是 bug 现场),含精确值断言:
> 125 项通过,0 失败
- `npm run typecheck` 零错误
- 方案文档:§9.11 由「待验证」改为定案(`ssl/update` 不物化、
`ssl/upload+sslID` 才物化);新增 §9.12「本轮又修掉的 5 个静默失败」、
§9.13「本轮最终状态」、§9.14「certimate 工作流清查」
线上验收:三个域名(t-t.live / usj.cc / 200181.xyz)线上证书均为
LiteSSL ECC、2027-01-04 到期、daysLeft=90、needRenew=false;
1Panel 证书库 5 条精简为 3 条且全部在用;
certimate 停掉全部「会签发并部署」的工作流(团团 / 优世界 / 200181.xyz),
保留三条纯监控告警。
|
2026-10-06 20:08:28 +08:00 |
|
zqlit
|
1427c47dcf
|
feat(ssl): 证书签发链路搬到国内机 Docker,清理 1Panel 过期证书
架构定案(B+C):CF Worker 免费版 CPU 硬顶 10ms(cron 同),
不再购买 Paid($5/月≈¥36),改为——
B. Worker 留免费版 + 代码优化(把 CPU 压进预算)
C. 签发+部署整条链路搬国内机 Docker 容器
代码
- acme.ts: 缓存 signingKey 为 Promise,单次签发 importKey 12→1 次
(实测 importKey 126µs / sign 84µs;一次签发 3.3ms → 1.4ms)
- deployer.ts: 新增 DogeCloudDeployer.ping();修正 cert_id → id 的注释
- dnsprovider.ts: 新增 RemoteDns(把 DNS-01 写 TXT 委托给国内机 cn-dns-helper)
- tools/certkeeper-config.mjs: 域名配置抽为唯一事实源(两个消费方共用)
- tools/export-certkeeper-data.mjs: 导出国内机数据目录
新增部署单元
- deploy/cn-certkeeper: 签发+部署容器(只绑 127.0.0.1:8019,compose 管理)
含 FileKV(文件系统版 KVNamespace)、带鉴权 HTTP、每日 4:10 续期
- deploy/cn-dns-helper: DNS-01 写 TXT 助手(只绑 127.0.0.1:8018)
文档
- 函数版证书管家-方案.md 新增第九章:B+C 定案、实测 CPU 数据、
容器验收记录、1Panel 过期证书清理记录、t-t.live 两套管理冲突
- 标注旧 8.3 节「免费版跑不了签发」为未实测误判
一并纳入:.gitignore 忽略 deploy/cn-certkeeper/lib/(tsc 编译产物)
|
2026-10-06 18:28:55 +08:00 |
|
zqlit
|
9f586d625b
|
后台登录改造:自建登录页 + Cookie 会话,弃用 basic auth 弹窗
- editor-api 新增 /admin/login|session|logout:HttpOnly Cookie 会话(落盘持久化)、
登录失败限速(5 次锁 5 分钟)、鉴权改为「X-Editor-Token 或 会话」双通道
- 登录页重做:品牌标识、图标输入框、密码可见切换、柔光背景,深浅色适配
- 前端以 /admin/session 探测部署模式:国内机走本机会话,CF 版走原令牌流程
- nginx:/admin/ 与 /api/v2/editor/ 去掉 basic auth 与令牌注入,登录凭据只存 Cookie
- bootstrap.sh 同步 ADMIN_USER/ADMIN_PASS,保持一键迁移能力
|
2026-10-04 23:40:23 +08:00 |
|
zqlit
|
96751b8143
|
fix(editor): 删文章 EXDEV 修正 + 订阅口令不再阻断登录
1) 删除文章报 EXDEV(cross-device link not permitted)
· 根因一:容器内 /blog 与 /app/trash 是两个独立 bind mount,
rename(2) 跨挂载点必然 EXDEV —— 哪怕宿主机上它们同在一块盘。
· 根因二:退化成 fs.cpSync 后,Windows 上遇到**中文目录名**会把
Node 进程直接干崩(exit 127,不抛异常,stdout 缓冲丢失)。
而文章目录名几乎必然含中文(<日期>-<中文标题>-<slug>)。
· 修法:改用 readdirSync/copyFileSync 自己递归复制(宽字符路径,中文 OK);
bootstrap.sh / compose 改成挂 BLOG_DIR 的父目录,让仓库与回收站
落在**同一个挂载点**内,正常情况下 rename 直接成功、不再复制。
2) 登录页「订阅口令」填错会阻断整个登录
· 它只是可选订阅模块的口令,不该 throw 掉登录流程。
· rssApi() 无条件 searchParams.set('token', RSS_TOKEN),会用浏览器里
记住的旧口令覆盖调用方刚传的值 → 正确口令也验不过。
· 现在:填错只 toast 提示 + 清掉失效旧口令;rssApi 尊重调用方传的值。
3) wrangler.toml:EDITOR_API_BASE 改指国内机新域名 writeapi.usj.cc
(原指向即将到期的境外机 post.usj.cc,且那台从未部署过 editor-api)
验证:api-e2e 44/44;front matter 往返 130/130;保存往返 130/130;
中文路径删除实测通过(目录 + 中文子目录 + 图片全部移入回收站,进程不崩)。
|
2026-10-04 22:11:02 +08:00 |
|
zqlit
|
c217d20c30
|
feat(editor): 在线编辑文章(Worker 前端 + 轻量 docker 后端)
后端(新增 editor-api/,零 npm 依赖,只用 node 内置模块):
- 只做文章相关:列表/读取/新建/保存/删除/图片上传/git 状态·发布·同步
- front matter 往返保真:未改动的块按字节照抄,CRLF/块标量/引号写法都不动
- 列表用目录名当 id,slug 撞名不再静默改错文件(返回 409 列候选)
- 鉴权只有一条路:X-Editor-Token(只存在 Worker 侧,浏览器拿不到)
- 端口只绑 127.0.0.1,由宿主机 nginx 反代出去
部署(新增 deploy/editor-api/bootstrap.sh):
- 一条命令在新机器上完成 克隆仓库→写 .env→起容器→健康检查
- 状态全在两个目录(/srv/blog 仓库工作区 + /srv/editor-api 配置),
迁移 = 复制目录或在新机重跑本脚本,容器本身无状态
Cloudflare Worker 侧(blog-admin):
- src/routes/editor.ts:鉴权 + 反代,浏览器只跟 Worker 说话
- 管理面板新增「文章编辑」:两栏布局 + 快捷插入面板(13 项短代码,
与 write-server 的 ShortcutPanel 一致)+ 底部 草稿/保存/发布
- 系统设置改为 schema 驱动的表单,且**以运行时实际生效的配置为准**
(frontend_conf/captcha/moderator/ip_region/site_default + KV human_check),
修复「表单显示一套、评论系统跑另一套」的脱节问题
验证:tsc 0 错;front matter 往返 130/130;保存往返 130/130;
API e2e 44/44;无头 Chrome UI e2e 14/14
|
2026-10-04 21:16:06 +08:00 |
|
zqlit
|
e0218b42bb
|
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/(工作数据不入库)
|
2026-10-04 12:40:17 +08:00 |
|