Spaces:
Paused
Paused
Create server.js
Browse files
server.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// server.js
|
| 2 |
+
const http = require('http');
|
| 3 |
+
const os = require('os');
|
| 4 |
+
const fs = require('fs');
|
| 5 |
+
const { exec } = require('child_process');
|
| 6 |
+
const path = require('path');
|
| 7 |
+
|
| 8 |
+
// --- Hugging Face Spaces 核心配置 ---
|
| 9 |
+
// 必须监听所有接口,以响应外部请求
|
| 10 |
+
const HOST = '0.0.0.0';
|
| 11 |
+
// 必须使用 Hugging Face 指定的端口
|
| 12 |
+
const PORT = process.env.PORT || 7860;
|
| 13 |
+
|
| 14 |
+
// --- 辅助函数:获取磁盘使用情况 ---
|
| 15 |
+
function getDiskUsage() {
|
| 16 |
+
try {
|
| 17 |
+
const stats = fs.statfsSync('/'); // 获取根目录的磁盘信息
|
| 18 |
+
const total = stats.bsize * stats.blocks;
|
| 19 |
+
const free = stats.bsize * stats.bfree;
|
| 20 |
+
const used = total - free;
|
| 21 |
+
const usagePercent = ((used / total) * 100).toFixed(2);
|
| 22 |
+
return {
|
| 23 |
+
total: (total / (1024 ** 3)).toFixed(2), // GB
|
| 24 |
+
used: (used / (1024 ** 3)).toFixed(2), // GB
|
| 25 |
+
free: (free / (1024 ** 3)).toFixed(2), // GB
|
| 26 |
+
usagePercent: `${usagePercent}%`
|
| 27 |
+
};
|
| 28 |
+
} catch (error) {
|
| 29 |
+
return { error: '无法获取磁盘信息' };
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
// --- 辅助函数:获取出口公网IP(cURL方式)---
|
| 34 |
+
function getPublicIP() {
|
| 35 |
+
return new Promise(resolve => {
|
| 36 |
+
// Hugging Face Spaces 容器内通常可以访问外部网络
|
| 37 |
+
exec('curl -s --max-time 5 ifconfig.me', (err, stdout) => {
|
| 38 |
+
if (err) {
|
| 39 |
+
resolve('无法获取公网IP (可能为NAT环境)');
|
| 40 |
+
} else {
|
| 41 |
+
resolve(stdout.trim());
|
| 42 |
+
}
|
| 43 |
+
});
|
| 44 |
+
});
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
// --- 辅助函数:获取所有环境变量(过滤掉敏感信息)---
|
| 48 |
+
function getSafeEnvVars() {
|
| 49 |
+
const safeVars = {};
|
| 50 |
+
const sensitiveKeys = ['PASSWORD', 'SECRET', 'TOKEN', 'KEY'];
|
| 51 |
+
for (const [key, value] of Object.entries(process.env)) {
|
| 52 |
+
if (sensitiveKeys.some(k => key.toLowerCase().includes(k.toLowerCase()))) {
|
| 53 |
+
safeVars[key] = '****** (已隐藏)';
|
| 54 |
+
} else {
|
| 55 |
+
safeVars[key] = value;
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
return safeVars;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
// --- 创建 HTTP 服务器 ---
|
| 62 |
+
const server = http.createServer(async (req, res) => {
|
| 63 |
+
// 1. 获取动态信息
|
| 64 |
+
const publicIP = await getPublicIP();
|
| 65 |
+
const diskInfo = getDiskUsage();
|
| 66 |
+
|
| 67 |
+
// 2. 系统与硬件信息 (通过 os 模块)
|
| 68 |
+
const cpuInfo = os.cpus()[0];
|
| 69 |
+
const totalMem = (os.totalmem() / (1024 ** 3)).toFixed(2); // GB
|
| 70 |
+
const freeMem = (os.freemem() / (1024 ** 3)).toFixed(2); // GB
|
| 71 |
+
const usedMem = (totalMem - freeMem).toFixed(2); // GB
|
| 72 |
+
const systemUptime = Math.floor(os.uptime() / 60) + ' 分钟';
|
| 73 |
+
|
| 74 |
+
// 3. 关键环境检查
|
| 75 |
+
const isNodePathSet = !!process.env.NODE_PATH;
|
| 76 |
+
const isDataDirWritable = fs.accessSync('/data', fs.constants.W_OK) === undefined; // 检查 /data 是否可写
|
| 77 |
+
const isHfHomeSet = !!process.env.HF_HOME;
|
| 78 |
+
|
| 79 |
+
// 4. 构建用于展示的数据模型
|
| 80 |
+
const envReport = {
|
| 81 |
+
// ========== 1. 访问信息 ==========
|
| 82 |
+
access: {
|
| 83 |
+
spaceUrl: `https://${process.env.SPACE_ID?.replace('/', '-')}.hf.space` || '未知 (请通过公网域名访问)',
|
| 84 |
+
publicIP: publicIP,
|
| 85 |
+
internalIP: (() => {
|
| 86 |
+
const interfaces = os.networkInterfaces();
|
| 87 |
+
for (const iface of Object.values(interfaces)) {
|
| 88 |
+
for (const addr of iface) {
|
| 89 |
+
if (addr.family === 'IPv4' && !addr.internal) return addr.address;
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
return '127.0.0.1';
|
| 93 |
+
})(),
|
| 94 |
+
hostname: os.hostname(),
|
| 95 |
+
spaceId: process.env.SPACE_ID || '未知',
|
| 96 |
+
spaceRepoName: process.env.SPACE_REPO_NAME || '未知',
|
| 97 |
+
},
|
| 98 |
+
// ========== 2. 硬件资源 (免费层标准) ==========
|
| 99 |
+
hardware: {
|
| 100 |
+
cpuModel: cpuInfo?.model || '未知',
|
| 101 |
+
cpuCores: os.cpus().length,
|
| 102 |
+
totalRamGB: totalMem,
|
| 103 |
+
usedRamGB: usedMem,
|
| 104 |
+
freeRamGB: freeMem,
|
| 105 |
+
disk: diskInfo,
|
| 106 |
+
uptime: systemUptime,
|
| 107 |
+
expectedFreeTier: '2 vCPU / 16GB RAM / 50GB 临时磁盘'
|
| 108 |
+
},
|
| 109 |
+
// ========== 3. Node.js 环境 ==========
|
| 110 |
+
nodeEnv: {
|
| 111 |
+
version: process.version,
|
| 112 |
+
platform: process.platform,
|
| 113 |
+
arch: process.arch,
|
| 114 |
+
execPath: process.execPath,
|
| 115 |
+
cwd: process.cwd(),
|
| 116 |
+
nodePath: isNodePathSet ? process.env.NODE_PATH : '未设置'
|
| 117 |
+
},
|
| 118 |
+
// ========== 4. 容器与权限 ==========
|
| 119 |
+
container: {
|
| 120 |
+
userId: process.getuid?.(),
|
| 121 |
+
groupId: process.getgid?.(),
|
| 122 |
+
expectedUserId: '1000 (Hugging Face 默认用户ID)',
|
| 123 |
+
isDataDirWritable: isDataDirWritable,
|
| 124 |
+
hfHome: process.env.HF_HOME || '未设置 (可设置为 /data/.huggingface 加速重启)',
|
| 125 |
+
// 检测是否使用了 Persistent Storage
|
| 126 |
+
persistentStorageMounted: fs.existsSync('/data'),
|
| 127 |
+
},
|
| 128 |
+
// ========== 5. 关键环境变量 (安全过滤) ==========
|
| 129 |
+
envVars: getSafeEnvVars(),
|
| 130 |
+
// ========== 6. 部署建议 ==========
|
| 131 |
+
suggestions: [
|
| 132 |
+
'✅ 必须监听 0.0.0.0,并在 README.md 中设置 app_port: 7860',
|
| 133 |
+
'✅ 强烈建议为敏感信息(如 OPENCODE_PASSWORD)使用 HF Secrets,勿硬编码',
|
| 134 |
+
'✅ 如需数据持久化,请考虑升级 Persistent Storage 或将数据备份到 HF Dataset',
|
| 135 |
+
'✅ 容器默认以用户 ID 1000 运行,Dockerfile 中需创建此用户避免权限问题',
|
| 136 |
+
'✅ 可设置 HF_HOME=/data/.huggingface 以加速 Hugging Face 库的重复启动',
|
| 137 |
+
]
|
| 138 |
+
};
|
| 139 |
+
|
| 140 |
+
// 5. 输出美观的 JSON 报告
|
| 141 |
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
| 142 |
+
res.end(JSON.stringify(envReport, null, 2));
|
| 143 |
+
});
|
| 144 |
+
|
| 145 |
+
// 启动服务器
|
| 146 |
+
server.listen(PORT, HOST, () => {
|
| 147 |
+
console.log(`🔍 Hugging Face Spaces 环境探针已启动`);
|
| 148 |
+
console.log(`📡 监听地址: http://${HOST}:${PORT}`);
|
| 149 |
+
console.log(`🌐 公网访问: https://${process.env.SPACE_ID?.replace('/', '-') || 'your-space'}.hf.space`);
|
| 150 |
+
console.log(`💡 提示: 访问上述 URL 即可查看详细的环境报告 JSON`);
|
| 151 |
+
});
|