guanghulab/scripts/skyeye/scan-external-bridges.js

201 lines
6.0 KiB
JavaScript
Raw Permalink Normal View History

// scripts/skyeye/scan-external-bridges.js
// 天眼·扫描模块D · 外部桥接状态扫描
//
// 扫描内容:
// ① Notion API 连通性(用 NOTION_TOKEN 测试)
// ② 服务器 SSH 连通性(检测 DEPLOY_HOST 配置)
// ③ GitHub API 有效性 + 配额
// ④ Secrets 完整性NOTION_TOKEN, DEPLOY_HOST, DEPLOY_USER, DEPLOY_KEY, SMTP_USER, SMTP_PASS
//
// 输出JSON → stdout
//
// 注意:此脚本在 GitHub Actions 中运行,通过环境变量获取 secrets 存在性
'use strict';
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
const now = new Date();
// ━━━ 检查 Secrets 完整性(仅检查环境变量是否被设置) ━━━
function checkSecrets() {
const requiredSecrets = [
'NOTION_TOKEN',
'DEPLOY_HOST',
'DEPLOY_USER',
'DEPLOY_KEY',
'SMTP_USER',
'SMTP_PASS'
];
const results = [];
let allPresent = true;
for (const secret of requiredSecrets) {
const present = !!process.env[secret] && process.env[secret].length > 0;
if (!present) allPresent = false;
results.push({ name: secret, present });
}
return {
complete: allPresent,
secrets: results,
missing: results.filter(s => !s.present).map(s => s.name)
};
}
// ━━━ 检查 Notion API 连通性 ━━━
async function checkNotionAPI() {
const token = process.env.NOTION_TOKEN;
if (!token) {
return { status: '🔴', detail: 'NOTION_TOKEN 未设置', connected: false };
}
try {
// Use native https to avoid dependency on axios
const result = await new Promise((resolve, reject) => {
const https = require('https');
const options = {
hostname: 'api.notion.com',
path: '/v1/users/me',
method: 'GET',
headers: {
'Authorization': 'Bearer ' + token,
'Notion-Version': '2022-06-28'
},
timeout: 10000
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
resolve({ statusCode: res.statusCode, body: data });
});
});
req.on('error', (e) => reject(e));
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
req.end();
});
if (result.statusCode === 200) {
return { status: '🟢', detail: 'Notion API 连通', connected: true };
} else {
return { status: '🟡', detail: `Notion API 返回 ${result.statusCode}`, connected: false };
}
} catch (e) {
return { status: '🔴', detail: 'Notion API 请求失败: ' + e.message, connected: false };
}
}
// ━━━ 检查 GitHub API ━━━
async function checkGitHubAPI() {
const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
try {
const https = require('https');
const result = await new Promise((resolve, reject) => {
const options = {
hostname: 'api.github.com',
path: '/rate_limit',
method: 'GET',
headers: {
'User-Agent': 'skyeye-scanner',
'Accept': 'application/vnd.github.v3+json'
},
timeout: 10000
};
if (token) {
options.headers['Authorization'] = 'Bearer ' + token;
}
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
resolve({ statusCode: res.statusCode, body: data });
});
});
req.on('error', (e) => reject(e));
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
req.end();
});
if (result.statusCode === 200) {
try {
const rateData = JSON.parse(result.body);
const core = rateData.resources && rateData.resources.core;
return {
status: '🟢',
detail: 'GitHub API 连通',
connected: true,
rate_limit: core ? { remaining: core.remaining, limit: core.limit } : null
};
} catch (e) {
return { status: '🟢', detail: 'GitHub API 连通(无法解析配额)', connected: true };
}
} else {
return { status: '🟡', detail: `GitHub API 返回 ${result.statusCode}`, connected: false };
}
} catch (e) {
return { status: '🔴', detail: 'GitHub API 请求失败: ' + e.message, connected: false };
}
}
// ━━━ 检查服务器 SSH ━━━
function checkServerSSH() {
const host = process.env.DEPLOY_HOST;
const user = process.env.DEPLOY_USER;
const key = process.env.DEPLOY_KEY;
if (!host) {
return { status: '🟡', detail: 'DEPLOY_HOST 未设置', connected: false };
}
// SSH connectivity check would require actually connecting
// In CI, we just verify the credentials are available
const hasCredentials = !!(host && user && key);
return {
status: hasCredentials ? '🟡' : '🔴',
detail: hasCredentials
? '服务器凭证已配置SSH 连通性需运行时验证)'
: '服务器凭证不完整',
connected: null, // Cannot verify without actual SSH
credentials_present: hasCredentials
};
}
// ━━━ 主扫描 ━━━
async function scanExternalBridges() {
const result = {
scan_time: new Date(now.getTime() + BEIJING_OFFSET_MS).toISOString().replace('T', ' ').slice(0, 19) + '+08:00',
notion_api: { status: '⏳', detail: '检查中...' },
github_api: { status: '⏳', detail: '检查中...' },
server_ssh: checkServerSSH(),
secrets: checkSecrets(),
// Summary
all_bridges_ok: false
};
// Parallel API checks
const [notionResult, githubResult] = await Promise.all([
checkNotionAPI().catch(e => ({ status: '🔴', detail: '检查异常: ' + e.message, connected: false })),
checkGitHubAPI().catch(e => ({ status: '🔴', detail: '检查异常: ' + e.message, connected: false }))
]);
result.notion_api = notionResult;
result.github_api = githubResult;
// Summary
result.all_bridges_ok =
result.notion_api.status === '🟢' &&
result.github_api.status === '🟢' &&
result.secrets.complete;
console.log(JSON.stringify(result, null, 2));
}
scanExternalBridges();