File size: 6,143 Bytes
41f0983
 
 
 
 
 
 
 
 
 
e094011
41f0983
 
e094011
 
41f0983
 
 
 
 
e094011
 
 
41f0983
 
 
 
 
 
 
e094011
41f0983
 
 
e094011
 
41f0983
 
 
 
e094011
41f0983
 
 
 
 
 
 
 
 
 
 
 
 
e094011
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41f0983
 
e094011
41f0983
 
e094011
41f0983
e094011
41f0983
e094011
 
 
41f0983
 
e094011
 
 
 
 
 
41f0983
e094011
41f0983
e094011
41f0983
e094011
41f0983
 
 
 
 
 
 
 
 
 
 
e094011
 
41f0983
e094011
41f0983
 
 
 
 
 
 
 
 
 
e094011
41f0983
 
 
 
 
 
 
e094011
41f0983
e094011
 
 
 
 
 
 
 
41f0983
e094011
41f0983
e094011
41f0983
 
e094011
 
 
 
41f0983
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// server.js
const http = require('http');
const os = require('os');
const fs = require('fs');
const { exec } = require('child_process');

// --- Hugging Face Spaces 核心配置 ---
const HOST = '0.0.0.0';
const PORT = process.env.PORT || 7860;

// --- 辅助函数:获取磁盘使用情况(需要处理 /data 可能不存在)---
function getDiskUsage() {
    try {
        // 获取当前工作目录所在的文件系统(通常是容器根目录)
        const stats = fs.statfsSync('/');
        const total = stats.bsize * stats.blocks;
        const free = stats.bsize * stats.bfree;
        const used = total - free;
        const usagePercent = ((used / total) * 100).toFixed(2);
        return {
            total: (total / (1024 ** 3)).toFixed(2),
            used: (used / (1024 ** 3)).toFixed(2),
            free: (free / (1024 ** 3)).toFixed(2),
            usagePercent: `${usagePercent}%`
        };
    } catch (error) {
        return { error: '无法获取磁盘信息' };
    }
}

// --- 辅助函数:获取出口公网IP---
function getPublicIP() {
    return new Promise(resolve => {
        exec('curl -s --max-time 5 ifconfig.me', (err, stdout) => {
            if (err) resolve('无法获取公网IP (可能为NAT环境)');
            else resolve(stdout.trim());
        });
    });
}

// --- 辅助函数:安全获取环境变量---
function getSafeEnvVars() {
    const safeVars = {};
    const sensitiveKeys = ['PASSWORD', 'SECRET', 'TOKEN', 'KEY'];
    for (const [key, value] of Object.entries(process.env)) {
        if (sensitiveKeys.some(k => key.toLowerCase().includes(k.toLowerCase()))) {
            safeVars[key] = '****** (已隐藏)';
        } else {
            safeVars[key] = value;
        }
    }
    return safeVars;
}

// --- 辅助函数:检查 /data 目录状态(兼容不存在的情况)---
function checkDataDir() {
    try {
        if (fs.existsSync('/data')) {
            // 目录存在,检查是否可写
            fs.accessSync('/data', fs.constants.W_OK);
            return { exists: true, writable: true };
        } else {
            return { exists: false, writable: false, reason: '目录不存在(免费层未挂载持久存储)' };
        }
    } catch (err) {
        return { exists: true, writable: false, reason: err.message };
    }
}

// --- 创建 HTTP 服务器 ---
const server = http.createServer(async (req, res) => {
    // 1. 动态信息
    const publicIP = await getPublicIP();
    const diskInfo = getDiskUsage();
    const dataDirStatus = checkDataDir();
    
    // 2. 系统与硬件信息
    const cpuInfo = os.cpus()[0];
    const totalMem = (os.totalmem() / (1024 ** 3)).toFixed(2);
    const freeMem = (os.freemem() / (1024 ** 3)).toFixed(2);
    const usedMem = (totalMem - freeMem).toFixed(2);
    const systemUptime = Math.floor(os.uptime() / 60) + ' 分钟';
    
    // 3. 用户ID和权限
    let userId = null, groupId = null;
    try {
        userId = process.getuid();
        groupId = process.getgid();
    } catch(e) { /* Windows 可能不支持 */ }
    
    // 4. 构建环境报告
    const envReport = {
        // 访问信息
        access: {
            spaceUrl: `https://${process.env.SPACE_ID?.replace('/', '-') || 'unknown'}.hf.space`,
            publicIP: publicIP,
            internalIP: (() => {
                const interfaces = os.networkInterfaces();
                for (const iface of Object.values(interfaces)) {
                    for (const addr of iface) {
                        if (addr.family === 'IPv4' && !addr.internal) return addr.address;
                    }
                }
                return '127.0.0.1';
            })(),
            hostname: os.hostname(),
            spaceId: process.env.SPACE_ID || '未设置',
            spaceRepoName: process.env.SPACE_REPO_NAME || '未设置',
        },
        // 硬件资源
        hardware: {
            cpuModel: cpuInfo?.model || '未知',
            cpuCores: os.cpus().length,
            totalRamGB: totalMem,
            usedRamGB: usedMem,
            freeRamGB: freeMem,
            disk: diskInfo,
            uptime: systemUptime,
            expectedFreeTier: '2 vCPU / 16GB RAM / 50GB 临时磁盘'
        },
        // Node.js 环境
        nodeEnv: {
            version: process.version,
            platform: process.platform,
            arch: process.arch,
            execPath: process.execPath,
            cwd: process.cwd(),
        },
        // 容器与权限
        container: {
            userId: userId,
            groupId: groupId,
            expectedUserId: '通常为 1000 (node 用户)',
            isDataDirPresent: dataDirStatus.exists,
            isDataDirWritable: dataDirStatus.writable,
            dataDirDetail: dataDirStatus.reason || (dataDirStatus.writable ? '可写' : '不可写'),
            hfHome: process.env.HF_HOME || '未设置',
            persistentStorageAvailable: dataDirStatus.exists && dataDirStatus.writable,
        },
        // 关键环境变量(安全过滤)
        envVars: getSafeEnvVars(),
        // 部署建议
        suggestions: [
            '✅ 必须监听 0.0.0.0,并在 README.md 中设置 app_port: 7860',
            '✅ 强烈建议为敏感信息使用 HF Secrets,勿硬编码',
            '✅ 免费层磁盘非持久化,重启会丢失数据',
            '✅ 如需持久化,考虑升级 Persistent Storage 或将数据备份到 HF Dataset',
            '✅ 容器默认用户 UID 可能为 1000,Dockerfile 中建议使用现有 node 用户',
        ]
    };
    
    res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
    res.end(JSON.stringify(envReport, null, 2));
});

// 启动服务器
server.listen(PORT, HOST, () => {
    console.log(`🔍 Hugging Face Spaces 环境探针已启动`);
    console.log(`📡 监听地址: http://${HOST}:${PORT}`);
    console.log(`🌐 公网访问: https://${process.env.SPACE_ID?.replace('/', '-') || 'your-space'}.hf.space`);
    console.log(`💡 提示: 访问上述 URL 即可查看详细的环境报告 JSON`);
});