guanghulab/scripts/skyeye/scan-external-bridges.js
2026-05-10 13:12:44 +08:00

201 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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();