guanghulab/scripts/preflight/check-server-isolation.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

219 lines
7.1 KiB
JavaScript

#!/usr/bin/env node
/**
* ════════════════════════════════════════════════════════════════
* 国内 2C2G 域名机隔离守卫 · check-server-isolation.js
* Sovereign: TCS-0002∞ · 国作登字-2026-A-00037559
* 守护: 铸渊 · ICE-GL-ZY001
* ════════════════════════════════════════════════════════════════
*
* 背景 (cc-004 · 团队技术能力近 0):
* 广州 2C2G (GH-CVM-DOMAIN-PROD-01) 是冰朔挂 guanghulab.com 的
* 唯一对外服务器, 必须单线运行. 仓库里几十个 deploy-* workflow
* 有可能在 push 时把代码推回来污染这台机器.
*
* 本脚本扫描 .github/workflows/ 下所有 .yml, 找引用了
* ZY_CN_SERVER_HOST / USER / KEY / PATH / PORT 的, 必须出现在
* cn-isolation-allowlist.json 里, 否则 CI 红灯.
*
* 用法:
* node scripts/preflight/check-server-isolation.js
*
* 退出码:
* 0 = 全部合法
* 1 = 发现未授权引用 (CI 阻断)
* 2 = 入参/文件错误
*
* 关键设计:
* - 只扫 .github/workflows/ 根目录, 不进 .archive/
* - 中文输出 (cc-004: 给 Awen / 冰朔看的, 不是给开发者)
* - 报告"哪个文件 / 哪一行 / 怎么修"
*/
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..', '..');
const WORKFLOWS_DIR = path.join(ROOT, '.github', 'workflows');
const ALLOWLIST_PATH = path.join(__dirname, 'cn-isolation-allowlist.json');
// 受保护的 secret 名称 (任何引用都需要白名单授权)
const CN_SECRETS = [
'ZY_CN_SERVER_HOST',
'ZY_CN_SERVER_USER',
'ZY_CN_SERVER_KEY',
'ZY_CN_SERVER_PATH',
'ZY_CN_SERVER_PORT',
'ZY_CN_SSH_PORT',
];
const AUTODL_SECRETS = [
'ZY_AUTODL_HOST',
'ZY_AUTODL_PORT',
'ZY_AUTODL_USER',
'ZY_AUTODL_PASS',
];
// 匹配 ${{ secrets.NAME }} 或 secrets.NAME 引用
function makePattern(names) {
// 守卫: secret 名必须只含 A-Z 0-9 _, 否则正则会被注入. 出现非法名字直接 throw.
for (const n of names) {
if (!/^[A-Z0-9_]+$/.test(n)) {
throw new Error('[隔离守卫] 非法 secret 名 (只允许 A-Z0-9_): ' + n);
}
}
return new RegExp(`\\bsecrets\\.(${names.join('|')})\\b`);
}
const CN_PATTERN = makePattern(CN_SECRETS);
const AUTODL_PATTERN = makePattern(AUTODL_SECRETS);
function readAllowlist() {
try {
return JSON.parse(fs.readFileSync(ALLOWLIST_PATH, 'utf8'));
} catch (err) {
console.error('[隔离守卫] ❌ 读不到白名单文件: ' + ALLOWLIST_PATH);
console.error(' ' + err.message);
process.exit(2);
}
}
function listWorkflowFiles() {
if (!fs.existsSync(WORKFLOWS_DIR)) return [];
return fs
.readdirSync(WORKFLOWS_DIR, { withFileTypes: true })
.filter((d) => d.isFile() && /\.ya?ml$/.test(d.name))
.map((d) => d.name);
}
function scanFile(filename) {
const full = path.join(WORKFLOWS_DIR, filename);
const content = fs.readFileSync(full, 'utf8');
const lines = content.split('\n');
const cnHits = [];
const autodlHits = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (CN_PATTERN.test(line)) {
const m = line.match(CN_PATTERN);
cnHits.push({ line: i + 1, secret: m[1], snippet: line.trim().slice(0, 120) });
}
if (AUTODL_PATTERN.test(line)) {
const m = line.match(AUTODL_PATTERN);
autodlHits.push({ line: i + 1, secret: m[1], snippet: line.trim().slice(0, 120) });
}
}
return { cnHits, autodlHits };
}
function main() {
const allowlist = readAllowlist();
const cnAllowed = new Set(
(allowlist.cn_server_workflows || []).map((w) => w.name)
);
const autodlAllowed = new Set(
(allowlist.autodl_workflows || []).map((w) => w.name)
);
const files = listWorkflowFiles();
const line = '═'.repeat(64);
console.log(line);
console.log(' 国内域名机隔离守卫 · 扫描 .github/workflows/');
console.log(' 保护对象: GH-CVM-DOMAIN-PROD-01 (广州 2C2G · 单线运行)');
console.log(' 扫描文件数: ' + files.length);
console.log(line);
const violations = [];
const reportCn = [];
const reportAutodl = [];
for (const f of files) {
const { cnHits, autodlHits } = scanFile(f);
if (cnHits.length > 0) {
const allowed = cnAllowed.has(f);
reportCn.push({ file: f, hits: cnHits, allowed });
if (!allowed) {
violations.push({
type: 'cn',
file: f,
hits: cnHits,
});
}
}
if (autodlHits.length > 0) {
const allowed = autodlAllowed.has(f);
reportAutodl.push({ file: f, hits: autodlHits, allowed });
if (!allowed) {
violations.push({
type: 'autodl',
file: f,
hits: autodlHits,
});
}
}
}
// ── 报告 CN 引用 ──
console.log('');
console.log('§ 引用 ZY_CN_SERVER_* 的工作流 (' + reportCn.length + ' 个)');
if (reportCn.length === 0) {
console.log(' (无 · 干净)');
} else {
for (const r of reportCn) {
const tag = r.allowed ? '✅ 已授权' : '❌ 未授权';
console.log(' ' + tag + ' ' + r.file);
for (const h of r.hits) {
console.log(' L' + h.line + ' ' + h.secret);
}
}
}
// ── 报告 AutoDL 引用 ──
console.log('');
console.log('§ 引用 ZY_AUTODL_* 的工作流 (' + reportAutodl.length + ' 个)');
if (reportAutodl.length === 0) {
console.log(' (无 · PR-3 之前应该是空的)');
} else {
for (const r of reportAutodl) {
const tag = r.allowed ? '✅ 已授权' : '❌ 未授权';
console.log(' ' + tag + ' ' + r.file);
for (const h of r.hits) {
console.log(' L' + h.line + ' ' + h.secret);
}
}
}
// ── 结论 ──
console.log('');
console.log(line);
if (violations.length === 0) {
console.log(' [OK] 隔离守卫通过 · 广州 2C2G 单线运行已守住');
console.log(line);
process.exit(0);
} else {
console.log(' [STOP] 发现 ' + violations.length + ' 处未授权引用');
console.log('');
console.log(' 下一步:');
console.log(' 1. 如果这个 workflow 应该被授权, 把它加到');
console.log(' scripts/preflight/cn-isolation-allowlist.json');
console.log(' 的 cn_server_workflows / autodl_workflows 列表里');
console.log(' 并在 reason 字段写清楚为什么');
console.log('');
console.log(' 2. 如果这个 workflow 不应该再用了, 移到');
console.log(' .github/workflows/.archive/ (git mv)');
console.log(' 归档不是删除, 以后能复活');
console.log('');
console.log(' 原因 (cc-004): 团队技术能力近 0, 任何 push 误触发都');
console.log(' 可能把这台 2C2G 单线机器搞污染. 守卫必须在 PR 合之前');
console.log(' 红灯, 不能等机器爆了再翻英文 stacktrace.');
console.log(line);
process.exit(1);
}
}
main();