/** * 只重跑「部署」这一步 —— 复用 KV 里**已经签好的**证书,不碰 ACME。 * * 为什么需要它(2026-10-06 加): * 签发的成败与部署的成败是**两件独立的事**。一旦部署侧出问题(1Panel 接口 * 改结构、站点名对不上、面板限流…),重新走 `/renew` 会**再签一张新证书** * —— 白白消耗 CA 配额,而证书本身根本没出问题。 * 这个脚本直接把 KV 里的证书 + 配置里的部署目标重跑一遍。 * * 用法: * docker exec cn-certkeeper node src/redeploy.mjs t-t.live * docker exec cn-certkeeper node src/redeploy.mjs t-t.live --dry # 只看会绑哪些站点 */ import path from 'node:path'; import { createRequire } from 'node:module'; import { fileURLToPath } from 'node:url'; import { FileKV } from './kv-file.mjs'; const HERE = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); const lib = (n) => require(path.resolve(HERE, '../lib', `${n}.js`)); const argv = process.argv.slice(2); const NAME = argv.find((a) => !a.startsWith('--')); const DRY = argv.includes('--dry'); if (!NAME) { console.error('用法:node src/redeploy.mjs <域名> [--dry]'); process.exit(2); } const kv = new FileKV(process.env.DATA_DIR || '/data'); const env = { RSS_KV: kv, TOKEN_SECRET: String(process.env.TOKEN_SECRET || '').trim(), EDITOR_API_BASE: '', EDITOR_TOKEN: '' }; const { loadConfig, getCert, getAccess } = lib('certstore'); const { makeDeployer } = lib('deployer'); const cfg = await loadConfig(env); const d = cfg.domains.find((x) => x.name === NAME); if (!d) { console.error(`配置里没有域名「${NAME}」(现有:${cfg.domains.map((x) => x.name).join(', ')})`); process.exit(2); } const rec = await getCert(env, NAME); if (!rec?.cert || !rec?.key) { console.error(`KV 里没有「${NAME}」的证书内容(先跑一次签发)`); process.exit(2); } console.log(`域名 ${NAME}:证书到期 ${new Date(rec.expireAt).toISOString()}(${Math.round((rec.expireAt - Date.now()) / 86400000)} 天)`); console.log(`部署目标 ${(d.deploy || []).join(', ')};1Panel 站点 ${(d.one_panel_sites || []).join(', ')}`); if (DRY) process.exit(0); for (const target of d.deploy || []) { const credName = target === '1panel' ? '1panel-cn' : target; const cred = await getAccess(env, credName); if (!cred) { console.log(` ${target}: ★ 找不到凭据「${credName}」`); continue; } try { const dp = makeDeployer(cred); const res = await dp.deploy( { domain: NAME, cert: rec.cert, key: rec.key, notAfter: rec.expireAt }, { dogecloudDomains: d.dogecloud_domains, onePanelSites: d.one_panel_sites, log: (m) => console.log(` [${target}] ${m}`) }, ); console.log(` ${target}: 完成 —— ${res.details.join(';')}`); } catch (e) { console.log(` ${target}: ★ 失败 —— ${e instanceof Error ? e.message : e}`); process.exitCode = 1; } }