90 lines
4.2 KiB
JavaScript
90 lines
4.2 KiB
JavaScript
/**
|
|||
|
|
* 应急回退:把多吉云 CDN 的加速域名绑回**指定的证书 id**。
|
||
|
|
*
|
||
|
|
* 什么时候用:换了新证书之后 CDN 侧表现异常(比如最终端不吃 ECDSA),
|
||
|
|
* 需要立刻把域名绑回上一代证书恢复服务。
|
||
|
|
*
|
||
|
|
* 用法:
|
||
|
|
* docker exec cn-certkeeper node src/rollback-dogecloud.mjs usj.cc 40948
|
||
|
|
* docker exec cn-certkeeper node src/rollback-dogecloud.mjs usj.cc # 只列候选
|
||
|
|
*
|
||
|
|
* 绑定的域名取自**配置里该域名的 `dogecloud_domains`**,所以不会误伤别的域名。
|
||
|
|
*/
|
||
|
|
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 WANT_ID = argv.filter((a) => !a.startsWith('--'))[1];
|
||
|
|
if (!NAME) {
|
||
|
|
console.error('用法:node src/rollback-dogecloud.mjs <域名> [证书id]');
|
||
|
|
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, 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}」`); process.exit(2); }
|
||
|
|
const domains = (d.dogecloud_domains || []).map((s) => s.trim()).filter(Boolean);
|
||
|
|
if (!domains.length) { console.error('该域名没配 dogecloud_domains'); process.exit(2); }
|
||
|
|
|
||
|
|
const cred = await getAccess(env, 'dogecloud');
|
||
|
|
const dp = makeDeployer(cred);
|
||
|
|
|
||
|
|
// 复用部署器的私有 api:通过 re-deploy 的 ping 拿不到列表,这里直接照签名规则自己调。
|
||
|
|
// (把 deployer 当黑盒的话拿不到 list,所以就地实现一次只读列举)
|
||
|
|
const nc = await import('node:crypto');
|
||
|
|
const enc = new TextEncoder();
|
||
|
|
async function dapi(path_, body) {
|
||
|
|
const bodyStr = body === null ? '' : JSON.stringify(body);
|
||
|
|
const key = await nc.subtle.importKey('raw', enc.encode(cred.secretKey), { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']);
|
||
|
|
const sig = Buffer.from(await nc.subtle.sign('HMAC', key, enc.encode(`${path_}\n${bodyStr}`))).toString('hex');
|
||
|
|
const r = await fetch('https://api.dogecloud.com' + path_, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { Authorization: `TOKEN ${cred.accessKey}:${sig}`, 'Content-Type': 'application/json', Accept: 'application/json' },
|
||
|
|
body: bodyStr || undefined,
|
||
|
|
});
|
||
|
|
return JSON.parse(await r.text());
|
||
|
|
}
|
||
|
|
|
||
|
|
const cl = await dapi('/cdn/cert/list.json', {});
|
||
|
|
const certs = cl?.data?.certs || [];
|
||
|
|
const dl = await dapi('/cdn/domain/list.json', {});
|
||
|
|
const inUse = new Map((dl?.data?.domains || []).map((x) => [x.name, x.cert_id]));
|
||
|
|
|
||
|
|
console.log(`域名 ${NAME};将操作的加速域名:${domains.join(', ')}\n`);
|
||
|
|
console.log('当前绑定:');
|
||
|
|
for (const n of domains) console.log(` ${n.padEnd(20)} cert_id=${inUse.get(n)}`);
|
||
|
|
console.log('\n候选证书(域名集含上述域名的):');
|
||
|
|
const want = new Set(domains.map((s) => s.toLowerCase()));
|
||
|
|
for (const c of certs) {
|
||
|
|
const have = new Set((c.domains || []).map((x) => String(x.name).toLowerCase()));
|
||
|
|
let covers = true;
|
||
|
|
for (const w of want) if (!have.has(w)) { covers = false; break; }
|
||
|
|
if (!covers) continue;
|
||
|
|
console.log(` #${String(c.id).padEnd(8)} note=${JSON.stringify(c.note)} issue=${c.issueDate} expire=${c.expireText} SAN=${JSON.stringify(c.info?.SAN || [])} algo=${c.info?.encryptAlgorithm || '?'}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!WANT_ID) {
|
||
|
|
console.log('\n(未指定证书 id,只列举,不做改动)');
|
||
|
|
console.log('回退用法:node src/rollback-dogecloud.mjs ' + NAME + ' <上面的某个 id>');
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
const id = Number(WANT_ID);
|
||
|
|
if (!certs.some((c) => Number(c.id) === id)) { console.error(`\n★ 证书 #${id} 不在列表里,拒绝执行`); process.exit(1); }
|
||
|
|
for (const n of domains) {
|
||
|
|
const r = await dapi('/cdn/cert/bind.json', { id, domain: n });
|
||
|
|
console.log(` bind ${n} → #${id} code=${r.code} msg=${r.msg || ''}`);
|
||
|
|
}
|
||
|
|
console.log('\n回退完成。');
|