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 编译产物)
This commit is contained in:
1 parent
ca3e7e8160
commit
1fa639e630
15 files changed
+1387
-52
No files matched your search
@@ -109,6 +109,23 @@ export class AcmeClient {
|
||||
private nonce: string | null = null;
|
||||
private readonly log: AcmeLogger;
|
||||
|
||||
/**
|
||||
* ★ 账户私钥的 CryptoKey 缓存(2026-10-06 加)。
|
||||
*
|
||||
* 原来 signJws() 每次调用都 `importKey('jwk', ...)`。一次签发有 ~11 次 JWS,
|
||||
* 本地实测(.editor-tmp/cpu-bench4.mjs,3000 次迭代):
|
||||
* importKey('jwk') 单次 126 µs;若密钥已就绪,纯 sign 只要 84 µs。
|
||||
* 也就是说每次签发白烧 ≈ 1.4 ms。Workers 免费版 CPU 硬顶 10 ms,
|
||||
* 这 1.4 ms 值得省;就算跑在国内机,少一次密钥解析也没坏处。
|
||||
*
|
||||
* 缓存安全性:账户密钥(this.account.jwk)在实例生命周期内**不变**,
|
||||
* 而一次签发自始至终用同一个实例(见 certissue.ts 的 issueDomain)。
|
||||
*
|
||||
* 存 Promise 而不是 CryptoKey:并发调用时只真正 import 一次
|
||||
* (存 CryptoKey 的话,两个并发请求会各 import 一次,结果一样但白花 CPU)。
|
||||
*/
|
||||
private signingKey: Promise<CryptoKey> | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly directoryUrl: string,
|
||||
private readonly account: { jwk: JsonWebKey; kid: string },
|
||||
@@ -168,14 +185,29 @@ export class AcmeClient {
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取(并缓存)账户签名密钥 —— 见 signingKey 字段注释。
|
||||
* 只在第一次调用时真的 importKey,之后复用同一个 CryptoKey。
|
||||
*/
|
||||
private importSigningKey(): Promise<CryptoKey> {
|
||||
if (!this.signingKey) {
|
||||
// 失败时清掉缓存,否则一次网络/参数抖动会被永久缓存成 reject
|
||||
this.signingKey = (crypto.subtle.importKey(
|
||||
'jwk',
|
||||
this.account.jwk,
|
||||
{ name: 'ECDSA', namedCurve: 'P-256' },
|
||||
false,
|
||||
['sign'],
|
||||
) as Promise<CryptoKey>).catch((e) => {
|
||||
this.signingKey = null;
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
return this.signingKey;
|
||||
}
|
||||
|
||||
private async signJws(protectedHeader: Record<string, unknown>, payload: unknown): Promise<string> {
|
||||
const key = await crypto.subtle.importKey(
|
||||
'jwk',
|
||||
this.account.jwk,
|
||||
{ name: 'ECDSA', namedCurve: 'P-256' },
|
||||
false,
|
||||
['sign'],
|
||||
);
|
||||
const key = await this.importSigningKey();
|
||||
const signingInput = `${b64uJson(protectedHeader)}.${b64uJson(payload)}`;
|
||||
const sig = await crypto.subtle.sign(
|
||||
{ name: 'ECDSA', hash: 'SHA-256' },
|
||||
|
||||
Reference in new issue
Block a user