1、index.js

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
const { execSync, spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const https = require('https');

const BASE_DIR = '/home/container';
const DASHBOARD_DIR = path.join(BASE_DIR, 'dashboard');
const BINARY_NAME = 'dashboard-linux-amd64';
const CONFIG_PATH = path.join(DASHBOARD_DIR, 'data/config.yaml');

const DOWNLOAD_URL = 'https://github.com/naiba/nezha/releases/download/v0.20.13/dashboard-linux-amd64.zip';

const PORT = process.env.PORT || 20234;

console.log('🚀 哪吒 Dashboard 一键启动脚本(含自动生成配置)');

function runCommand(cmd, options = {}) {
console.log(`执行: ${cmd}`);
try {
execSync(cmd, { stdio: 'inherit', ...options });
return true;
} catch (e) {
console.error(`❌ 执行失败: ${cmd}`);
return false;
}
}

async function getPublicIP() {
return new Promise((resolve) => {
https.get('https://api.ipify.org', (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(data.trim()));
}).on('error', () => resolve('你的服务器IP'));
});
}

async function downloadFile(url, dest) {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(dest);
https.get(url, (res) => {
if (res.statusCode === 301 || res.statusCode === 302) {
return downloadFile(res.headers.location, dest).then(resolve).catch(reject);
}
res.pipe(file);
file.on('finish', () => { file.close(); resolve(); });
}).on('error', (err) => { fs.unlinkSync(dest); reject(err); });
});
}

async function createDefaultConfig() {
if (fs.existsSync(CONFIG_PATH)) {
console.log('✅ config.yaml 已存在,跳过创建');
return;
}

const configContent = `debug: false
httpport: ${PORT}
listen_host: "0.0.0.0"

oauth2:
type: github
admin: "你的GitHub用户名" # ← 必须修改
clientid: "" # ← 必须修改
clientsecret: "" # ← 必须修改

site:
brand: "哪吒监控面板"
cookiename: "nezha-dashboard"

grpcport: 5555
`;

fs.mkdirSync(path.join(DASHBOARD_DIR, 'data'), { recursive: true });
fs.writeFileSync(CONFIG_PATH, configContent);
console.log('✅ 已自动生成 data/config.yaml');
console.log('⚠️ 请修改 config.yaml 中的 admin、clientid、clientsecret 后再访问面板!');
}

async function ensureResources() {
const dirs = ['resource/template/theme-custom', 'resource/static/custom', 'data'];
dirs.forEach(dir => {
fs.mkdirSync(path.join(DASHBOARD_DIR, dir), { recursive: true });
});

const placeholder = path.join(DASHBOARD_DIR, 'resource/template/theme-custom/index.html');
if (!fs.existsSync(placeholder)) {
fs.writeFileSync(placeholder, '<!DOCTYPE html><html><head><title>Nezha Dashboard</title></head><body><h1>哪吒面板启动成功</h1></body></html>');
}
console.log('✅ 资源目录与模板文件已准备完成');
}

async function main() {
fs.mkdirSync(DASHBOARD_DIR, { recursive: true });
process.chdir(DASHBOARD_DIR);

if (!fs.existsSync(path.join(DASHBOARD_DIR, BINARY_NAME))) {
console.log('📥 下载哪吒 Dashboard...');
await downloadFile(DOWNLOAD_URL, 'dashboard.zip');
runCommand('unzip -o dashboard.zip');
if (fs.existsSync('dashboard.zip')) fs.unlinkSync('dashboard.zip');
} else {
console.log('✅ 二进制已存在');
}

await ensureResources();
await createDefaultConfig(); // ← 新增:自动创建配置

runCommand(`chmod +x ${BINARY_NAME}`);

const publicIP = await getPublicIP();
console.log(`\n🌟 正在启动哪吒 Dashboard...`);
console.log(`🔗 访问地址 → http://${publicIP}:${PORT}\n`);

const dashboard = spawn(`./${BINARY_NAME}`, [], {
stdio: 'inherit',
cwd: DASHBOARD_DIR
});

dashboard.on('error', err => console.error('启动错误:', err));
}

main().catch(err => console.error('脚本异常:', err));

2、package.json

1
2
3
4
5
6
7
8
9
{
"name": "nezha-dashboard",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {}
}

3、如何快速获取 GitHub Client ID 和 Secret?

打开这个链接:https://github.com/settings/applications/new
填写以下信息:
Application name: Nezha Dashboard
Homepage URL: http://你的IP:20234
Authorization callback URL: http://你的IP:20234/oauth2/callback

点击 Register application
复制 Client ID 和 Client Secret,填入上面的配置文件。