Files
blog/blog-admin/tools/certkeeper-config.mjs
T
zqlit 3eeafa07b2 fix(证书管家): writeapi.usj.cc 证书纳管(相对软链接);pushall 改为先 rebase 再推
用户:「动手 推送吧」—— 动手处理两个有死线的遗留。

一、writeapi.usj.cc 的证书(12 月会断)→ 已解决

现状复核推翻了上一轮的判断:
  · 上一轮记的「它的 ssl_certificate 引用不在 /www/sites/ 树里」是**错的** ——
    /www/conf.d/writeapi.usj.cc.conf 里明确写着
    ssl_certificate /www/sites/writeapi.usj.cc/ssl/fullchain.pem;
  · 真正原因是**它在 1Panel 面板上根本不是一个网站**(面板共 11 个站点,没有它),
    所以 `findWebsite('writeapi.usj.cc')` 必然抛「找不到网站」→
    加进 one_panel_sites 只会让**每次续期都报错**(而且只警告不阻断,长期没人发现)
  · 实测确认:redeploy 时其余 5 个站点正常,只有它失败

解法:**不写一行代码** —— 它与 artalk.usj.cc 本来就同用一张 *.usj.cc 证书,
让它用**相对软链接跟随 artalk** 即可,随续期自动更新:
  ln -sfn ../../artalk.usj.cc/ssl/fullchain.pem /1panel/1panel/www/sites/writeapi.usj.cc/ssl/fullchain.pem

★★ 踩到的坑:软链接**必须相对路径**
  宿主 /1panel/1panel/www/sites/ ↔ 容器内 /www/sites/(1Panel 的 openresty 跑在容器
  `1Panel-openresty-Kmh2`)。第一版写成宿主绝对路径 → **容器侧断链** →
  nginx -s reload **静默失败**(保留旧配置、服务不中断、握手照旧拿旧证书)→
  表面全绿,真实后果是「等某天 nginx 重启,writeapi 的 HTTPS 直接挂」。
  唯一线索是 worker 进程的启动时间没变 —— **所以验收必须看 worker 是否真的重启**。

验收(全部实测):
  替换前 CN=usj.cc    / LiteSSL RSA CA / 2026-12-07(mtime 10-04 21:50)
  替换后 CN=*.usj.cc  / LiteSSL ECC CA / 2027-01-04
  容器内 head -1 → -----BEGIN CERTIFICATE----- / -----BEGIN PRIVATE KEY-----
  预检 syntax is ok / test is successful
  reload → signal process started;**worker 22:51:39 重启**(证明软链接真被读了)
  握手 SNI=writeapi.usj.cc → *.usj.cc / Jan 4 2027;实访 /health → HTTP 200

新增 deploy/cn-certkeeper/src/dump-cert.mjs:把 KV 里(AES-GCM 加密)的证书导成明文 PEM,
补上「KV ↔ 手工 vhost 磁盘文件」之间原先不存在的桥。只读,用完即删(含明文私钥)。

操作层的两个坑(写进文档):
  · 1Panel 文件 API **拒绝写 .mjs**(返回 500「目标路径不存在」,实为可执行扩展名过滤);
    往新建的 root:root 700 目录写也会失败 → 临时脚本用
    `docker exec -i … sh -c 'cat > 路径' < 本地文件` 送
  · cnrun.py(1Panel 计划任务通道)偶发「退出码 0 但零输出」;
    加 `timeout 30 docker exec …` 包一层即稳定,判断状态优先看容器内直接证据

二、配置回退 + 文档更正

  · certkeeper-config.mjs:把 writeapi 从 one_panel_sites 移除,换成一段注释说明
    「它不是 1Panel 站点,别加进来」+ 软链接做法 + 必须相对的警告;
    线上 config.kv 同步回退(sha256 与改动前完全一致 ccac2530…)
  · 更正「3 个手工 vhost」的说法:vaultwarden **是**正常纳管的站点(面板 #13,
    主域名 vw.usj.cc,别名才是 vaultwarden)→ 手工 vhost 实际只剩 writeapi + dnsapi
  · dnsapi.usj.cc 重新定性为**不做**:它是 cn-dns-helper 的反代入口
    (proxy_pass 127.0.0.1:8018,仅 HTTP),去留应与 cn-dns-helper 一起决定,
    不该单独给一个待退役的服务加 HTTPS

三、pushall 加固:先 fetch + rebase 再推(本次真实撞到的问题)

推 CNB 时被 rejected —— 因为线上写作后台(editor-api)发文章会**直接推 main**,
本机两笔提交与之分叉。这不是异常,是**日常**。原别名直接 push,必然反复撞。

  · 新增 scripts/pushall.sh:fetch → 已在远端之后则 rebase → 推所有远端
    - 冲突时**停在 rebase 中途**交人工(不强推、不丢东西)
    - 工作区不干净时 git 自己会拒绝 rebase(不会吞改动)
    - 任一远端失败**不改判另一个**(主仓失败辅仓照样推),退出码以第一次失败为准
    - ⚠️ 修了自己写的一处疏漏:`if ! cmd; then ec=$?` 的 $? 是**取反后**的结果(0),
      不是 git 的退出码 —— 必须显式 ec=1(靠 set -o pipefail 保证管道退出码不被 sed 掩盖)
  · setup-cnb-remotes.sh 的别名改指向脚本;README 脚本表补这一行

四、文档

  · docs/证书管家.md:速览表「已知遗留」→ 已解决;§9.14 遗留段重写(含纠正自己写错的判据
    「路径像不像不构成判据,要直接查面板站点清单」);**新增 §9.15**
    「手工 vhost 的证书:用相对软链接跟随已纳管站点」(机制 + 路径坑 + 三条验收 + dump-cert 用法)
  · docs/架构精简候选.md:候选 5 标记完成,补上「为什么 A 走不通、B 未采用」;
    执行顺序里划掉它
  · 架构总览.md:§5.7「已知遗留」重写(2 个手工 vhost + vaultwarden 更正);
    §6 待办 #13 改为已解决、#14 标注完成
2026-10-06 22:54:58 +08:00

85 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 证书管家的**域名配置唯一事实源**。
*
* 为什么单独抽出来(2026-10-06):
* 这份配置有两个消费方 ——
* ① `seed-ssl-config.mjs`:写进 Cloudflare KV(Worker 侧 UI / 探针 / 自检用)
* ② `export-certkeeper-data.mjs`:生成国内机 cn-certkeeper 的数据目录
* 原先 CONFIG 内联在 seed 脚本里,而那个脚本**一被 import 就执行**
* (读 .env、连 KV 开始写),第 ② 个消费方没法安全复用。
* 抽出来之后两边共用一份定义,不会出现「改了 KV 忘了改国内机」。
*
* ★ 站点名必须与 1Panel 里的 `primaryDomain` 或 `alias` **精确对上**,
* 否则部署时会被跳过。下面这些是 2026-10-06 从面板实测出来的。
*/
export const CONFIG = {
version: 1,
notify: { emails: ['177018615@qq.com'], daysBefore: 30 },
domains: [
{
name: 'usj.cc',
san: ['usj.cc', '*.usj.cc'],
dns: 'tencent-usj',
deploy: ['dogecloud', '1panel'],
// ★ 站点名必须与 1Panel 里的 `primaryDomain` 或 `alias` 精确对上,
// 否则部署时会被跳过。下面这些是 2026-10-06 从面板实测出来的
// (面板上**没有** primaryDomain 为 `usj.cc` 的网站 —— 它只是证书名)。
dogecloud_domains: ['usj.cc', 'www.usj.cc', 'artalk.usj.cc'],
one_panel_sites: [
'artalk.usj.cc', // blog 评论后端
'openlist.usj.cc', // 网盘
'wifi.usj.cc',
'openwrt.usj.cc',
'vw.usj.cc', // vaultwarden(alias 才是这个名字)
// ★★ 不要在这里加 writeapi.usj.cc —— 它**不是 1Panel 站点**
// (2026-10-06 实测:面板上共 11 个网站,没有它;它连面板都没有,
// nginx 配置是手工放在 /www/conf.d/writeapi.usj.cc.conf 的)。
// 加进来会让**每次续期**都报「1Panel 里找不到网站「writeapi.usj.cc」」——
// 只警告不中断,所以很容易长期没人发现。
//
// 它是**手工 vhost**,证书靠**软链接跟随这里第一个站点**获取:
// ln -sfn ../../artalk.usj.cc/ssl/fullchain.pem \
// /1panel/1panel/www/sites/writeapi.usj.cc/ssl/fullchain.pem
// ln -sfn ../../artalk.usj.cc/ssl/privkey.pem \
// /1panel/1panel/www/sites/writeapi.usj.cc/ssl/privkey.pem
// ⚠️ 必须是**相对路径**:宿主是 /1panel/1panel/www/…,容器内是 /www/…,
// 写绝对路径会在容器里断链(nginx 重启后 HTTPS 直接挂)。
// 机制与排错见 docs/证书管家.md「手工 vhost 的证书」一节。
],
// ★ 源站直连探针:usj.cc 走多吉云 CDN,公网握手拿到的是 CDN 边缘证书;
// 要续的是本机 1Panel 上那张。面板里没有 usj.cc 同名站点,
// 所以 SNI 指到真正引用该证书的 artalk.usj.cc。
probe_connect: '119.29.215.187',
probe_sni: 'artalk.usj.cc',
disabled: false,
},
{
name: 't-t.live',
san: ['t-t.live', '*.t-t.live'],
dns: 'tencent-tt',
deploy: ['1panel'],
one_panel_sites: ['t-t.live', 'www.t-t.live', 'pl.t-t.live', 'pwd.t-t.live', 'certd.t-t.live'],
// ★★ t-t.live 的公网入口是**腾讯云 EO 边缘加速**(apex 是一条 CNAME →
// t-t.live.eo.dnse2.com),公网握手量到的是 EO 的边缘证书(LE,12-27),
// 而我们要续的是本机 nginx 上那张源站证书(曾停在 07-11、10-09 到期)。
// 不连源站 IP 的话,续期判定会永远「还剩 80 多天」→ 源站证书悄悄过期。
probe_connect: '119.29.215.187',
probe_sni: 't-t.live',
disabled: false,
},
{
name: '200181.xyz',
san: ['200181.xyz', '*.200181.xyz'],
dns: 'cloudflare',
deploy: ['1panel'],
one_panel_sites: ['ssh.200181.xyz'],
// 同理:200181.xyz 的 NS 在 Cloudflare(带代理),公网拿到的是 CF 边缘证书。
// 面板上的站点名是 ssh.200181.xyz,用它当 SNI。
probe_connect: '119.29.215.187',
probe_sni: 'ssh.200181.xyz',
disabled: false,
},
],
};