@eooce/idx 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +2 -0
- package/index.js +1066 -1043
- package/package.json +12 -18
package/index.js
CHANGED
|
@@ -1,1044 +1,1067 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const express = require("express");
|
|
4
|
-
const app = express();
|
|
5
|
-
const axios = require("axios");
|
|
6
|
-
const os = require('os');
|
|
7
|
-
const fs = require("fs");
|
|
8
|
-
const path = require("path");
|
|
9
|
-
const { promisify } = require('util');
|
|
10
|
-
const exec = promisify(require('child_process').exec);
|
|
11
|
-
const { execSync } = require('child_process');
|
|
12
|
-
const UPLOAD_URL = process.env.UPLOAD_URL || ''; // 订阅或节点自动上传地址,需填写部署Merge-sub项目后的首页地址,例如:https://merge.ct8.pl
|
|
13
|
-
const PROJECT_URL = process.env.PROJECT_URL || ''; // 需要上传订阅或保活时需填写项目分配的url,例如:https://google.com
|
|
14
|
-
const AUTO_ACCESS = process.env.AUTO_ACCESS || false; // false关闭自动保活,true开启,需同时填写PROJECT_URL变量
|
|
15
|
-
const YT_WARPOUT = process.env.YT_WARPOUT || false; // 设置为true时强制使用warp出站访问youtube,false时自动检测是否设置warp出站
|
|
16
|
-
const FILE_PATH = process.env.FILE_PATH || '.npm'; // sub.txt订阅文件路径
|
|
17
|
-
const SUB_PATH = process.env.SUB_PATH || 'sub'; // 订阅sub路径,默认为sub,例如:https://google.com/sub
|
|
18
|
-
const UUID = process.env.UUID || '0a6568ff-ea3c-4271-9020-450560e10d63'; // 在不同的平台运行了v1哪吒请修改UUID,否则会覆盖
|
|
19
|
-
const NEZHA_SERVER = process.env.NEZHA_SERVER || ''; // 哪吒面板地址,v1形式:nz.serv00.net:8008 v0形式:nz.serv00.net
|
|
20
|
-
const NEZHA_PORT = process.env.NEZHA_PORT || ''; // v1哪吒请留空,v0 agent端口,当端口为{443,8443,2087,2083,2053,2096}时,自动开启tls
|
|
21
|
-
const NEZHA_KEY = process.env.NEZHA_KEY || ''; // v1的NZ_CLIENT_SECRET或v0 agwnt密钥
|
|
22
|
-
const ARGO_DOMAIN = process.env.ARGO_DOMAIN || ''; // argo固定隧道域名,留空即使用临时隧道
|
|
23
|
-
const ARGO_AUTH = process.env.ARGO_AUTH || ''; // argo固定隧道token或json,留空即使用临时隧道
|
|
24
|
-
const ARGO_PORT = process.env.ARGO_PORT || 8001; // argo固定隧道端口,使用token需在cloudflare控制台设置和这里一致,否则节点不通
|
|
25
|
-
const TUIC_PORT = process.env.TUIC_PORT || ''; // tuic端口,支持多端口的可以填写,否则留空
|
|
26
|
-
const HY2_PORT = process.env.HY2_PORT || ''; // hy2端口,支持多端口的可以填写,否则留空
|
|
27
|
-
const REALITY_PORT = process.env.REALITY_PORT || ''; // reality端口,支持多端口的可以填写,否则留空
|
|
28
|
-
const CFIP = process.env.CFIP || 'cdns.doon.eu.org'; // 优选域名或优选IP
|
|
29
|
-
const CFPORT = process.env.CFPORT || 443; // 优选域名或优选IP对应端口
|
|
30
|
-
const PORT = process.env.PORT || 3000; // http订阅端口
|
|
31
|
-
const NAME = process.env.NAME || ''; // 节点名称
|
|
32
|
-
const CHAT_ID = process.env.CHAT_ID || ''; // Telegram chat_id 两个变量不全不推送节点到TG
|
|
33
|
-
const BOT_TOKEN = process.env.BOT_TOKEN || ''; // Telegram bot_token 两个变量不全不推送节点到TG
|
|
34
|
-
|
|
35
|
-
require('dotenv').config();
|
|
36
|
-
|
|
37
|
-
//创建运行文件夹
|
|
38
|
-
if (!fs.existsSync(FILE_PATH)) {
|
|
39
|
-
fs.mkdirSync(FILE_PATH);
|
|
40
|
-
console.log(`${FILE_PATH} is created`);
|
|
41
|
-
} else {
|
|
42
|
-
console.log(`${FILE_PATH} already exists`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let privateKey = '';
|
|
46
|
-
let publicKey = '';
|
|
47
|
-
|
|
48
|
-
// 生成随机6位字符函数
|
|
49
|
-
function generateRandomName() {
|
|
50
|
-
const chars = 'abcdefghijklmnopqrstuvwxyz';
|
|
51
|
-
let result = '';
|
|
52
|
-
for (let i = 0; i < 6; i++) {
|
|
53
|
-
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
54
|
-
}
|
|
55
|
-
return result;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// 生成随机名称
|
|
59
|
-
const npmRandomName = generateRandomName();
|
|
60
|
-
const webRandomName = generateRandomName();
|
|
61
|
-
const botRandomName = generateRandomName();
|
|
62
|
-
const phpRandomName = generateRandomName();
|
|
63
|
-
|
|
64
|
-
// 使用随机文件名定义路径
|
|
65
|
-
let npmPath = path.join(FILE_PATH, npmRandomName);
|
|
66
|
-
let phpPath = path.join(FILE_PATH, phpRandomName);
|
|
67
|
-
let webPath = path.join(FILE_PATH, webRandomName);
|
|
68
|
-
let botPath = path.join(FILE_PATH, botRandomName);
|
|
69
|
-
let subPath = path.join(FILE_PATH, 'sub.txt');
|
|
70
|
-
let listPath = path.join(FILE_PATH, 'list.txt');
|
|
71
|
-
let bootLogPath = path.join(FILE_PATH, 'boot.log');
|
|
72
|
-
let configPath = path.join(FILE_PATH, 'config.json');
|
|
73
|
-
|
|
74
|
-
function deleteNodes() {
|
|
75
|
-
try {
|
|
76
|
-
if (!UPLOAD_URL) return;
|
|
77
|
-
|
|
78
|
-
const subPath = path.join(FILE_PATH, 'sub.txt');
|
|
79
|
-
if (!fs.existsSync(subPath)) return;
|
|
80
|
-
|
|
81
|
-
let fileContent;
|
|
82
|
-
try {
|
|
83
|
-
fileContent = fs.readFileSync(subPath, 'utf-8');
|
|
84
|
-
} catch {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const decoded = Buffer.from(fileContent, 'base64').toString('utf-8');
|
|
89
|
-
const nodes = decoded.split('\n').filter(line =>
|
|
90
|
-
/(vless|vmess|trojan|hysteria2|tuic):\/\//.test(line)
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
if (nodes.length === 0) return;
|
|
94
|
-
|
|
95
|
-
return axios.post(`${UPLOAD_URL}/api/delete-nodes`,
|
|
96
|
-
JSON.stringify({ nodes }),
|
|
97
|
-
{ headers: { 'Content-Type': 'application/json' } }
|
|
98
|
-
).catch((error) => {
|
|
99
|
-
return null;
|
|
100
|
-
});
|
|
101
|
-
} catch (err) {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// 端口验证函数
|
|
107
|
-
function isValidPort(port) {
|
|
108
|
-
try {
|
|
109
|
-
if (port === null || port === undefined || port === '') return false;
|
|
110
|
-
if (typeof port === 'string' && port.trim() === '') return false;
|
|
111
|
-
|
|
112
|
-
const portNum = parseInt(port);
|
|
113
|
-
if (isNaN(portNum)) return false;
|
|
114
|
-
if (portNum < 1 || portNum > 65535) return false;
|
|
115
|
-
|
|
116
|
-
return true;
|
|
117
|
-
} catch (error) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
//清理历史文件
|
|
123
|
-
const pathsToDelete = [ webRandomName, botRandomName, npmRandomName, 'boot.log', 'list.txt'];
|
|
124
|
-
function cleanupOldFiles() {
|
|
125
|
-
pathsToDelete.forEach(file => {
|
|
126
|
-
const filePath = path.join(FILE_PATH, file);
|
|
127
|
-
fs.unlink(filePath, () => {});
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
writer.on('
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (file.fileName === '
|
|
221
|
-
newFileName =
|
|
222
|
-
} else
|
|
223
|
-
newFileName =
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
//
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
"
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
"
|
|
448
|
-
"
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
],
|
|
465
|
-
"
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
"
|
|
476
|
-
"
|
|
477
|
-
"
|
|
478
|
-
"
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
"
|
|
485
|
-
"
|
|
486
|
-
}
|
|
487
|
-
],
|
|
488
|
-
"
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
"
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
"
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
"
|
|
539
|
-
"
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
"
|
|
565
|
-
"
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if (!config.route) {
|
|
614
|
-
config.route =
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
if (!
|
|
620
|
-
config.route.
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
//
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
//
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
{ fileName: "
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
} catch (
|
|
855
|
-
console.error('
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
//
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
})
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
console.log(
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const express = require("express");
|
|
4
|
+
const app = express();
|
|
5
|
+
const axios = require("axios");
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
const { promisify } = require('util');
|
|
10
|
+
const exec = promisify(require('child_process').exec);
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
const UPLOAD_URL = process.env.UPLOAD_URL || ''; // 订阅或节点自动上传地址,需填写部署Merge-sub项目后的首页地址,例如:https://merge.ct8.pl
|
|
13
|
+
const PROJECT_URL = process.env.PROJECT_URL || ''; // 需要上传订阅或保活时需填写项目分配的url,例如:https://google.com
|
|
14
|
+
const AUTO_ACCESS = process.env.AUTO_ACCESS || false; // false关闭自动保活,true开启,需同时填写PROJECT_URL变量
|
|
15
|
+
const YT_WARPOUT = process.env.YT_WARPOUT || false; // 设置为true时强制使用warp出站访问youtube,false时自动检测是否设置warp出站
|
|
16
|
+
const FILE_PATH = process.env.FILE_PATH || '.npm'; // sub.txt订阅文件路径
|
|
17
|
+
const SUB_PATH = process.env.SUB_PATH || 'sub'; // 订阅sub路径,默认为sub,例如:https://google.com/sub
|
|
18
|
+
const UUID = process.env.UUID || '0a6568ff-ea3c-4271-9020-450560e10d63'; // 在不同的平台运行了v1哪吒请修改UUID,否则会覆盖
|
|
19
|
+
const NEZHA_SERVER = process.env.NEZHA_SERVER || ''; // 哪吒面板地址,v1形式:nz.serv00.net:8008 v0形式:nz.serv00.net
|
|
20
|
+
const NEZHA_PORT = process.env.NEZHA_PORT || ''; // v1哪吒请留空,v0 agent端口,当端口为{443,8443,2087,2083,2053,2096}时,自动开启tls
|
|
21
|
+
const NEZHA_KEY = process.env.NEZHA_KEY || ''; // v1的NZ_CLIENT_SECRET或v0 agwnt密钥
|
|
22
|
+
const ARGO_DOMAIN = process.env.ARGO_DOMAIN || ''; // argo固定隧道域名,留空即使用临时隧道
|
|
23
|
+
const ARGO_AUTH = process.env.ARGO_AUTH || ''; // argo固定隧道token或json,留空即使用临时隧道
|
|
24
|
+
const ARGO_PORT = process.env.ARGO_PORT || 8001; // argo固定隧道端口,使用token需在cloudflare控制台设置和这里一致,否则节点不通
|
|
25
|
+
const TUIC_PORT = process.env.TUIC_PORT || ''; // tuic端口,支持多端口的可以填写,否则留空
|
|
26
|
+
const HY2_PORT = process.env.HY2_PORT || ''; // hy2端口,支持多端口的可以填写,否则留空
|
|
27
|
+
const REALITY_PORT = process.env.REALITY_PORT || ''; // reality端口,支持多端口的可以填写,否则留空
|
|
28
|
+
const CFIP = process.env.CFIP || 'cdns.doon.eu.org'; // 优选域名或优选IP
|
|
29
|
+
const CFPORT = process.env.CFPORT || 443; // 优选域名或优选IP对应端口
|
|
30
|
+
const PORT = process.env.PORT || 3000; // http订阅端口
|
|
31
|
+
const NAME = process.env.NAME || ''; // 节点名称
|
|
32
|
+
const CHAT_ID = process.env.CHAT_ID || ''; // Telegram chat_id 两个变量不全不推送节点到TG
|
|
33
|
+
const BOT_TOKEN = process.env.BOT_TOKEN || ''; // Telegram bot_token 两个变量不全不推送节点到TG
|
|
34
|
+
|
|
35
|
+
require('dotenv').config();
|
|
36
|
+
|
|
37
|
+
//创建运行文件夹
|
|
38
|
+
if (!fs.existsSync(FILE_PATH)) {
|
|
39
|
+
fs.mkdirSync(FILE_PATH);
|
|
40
|
+
console.log(`${FILE_PATH} is created`);
|
|
41
|
+
} else {
|
|
42
|
+
console.log(`${FILE_PATH} already exists`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let privateKey = '';
|
|
46
|
+
let publicKey = '';
|
|
47
|
+
|
|
48
|
+
// 生成随机6位字符函数
|
|
49
|
+
function generateRandomName() {
|
|
50
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz';
|
|
51
|
+
let result = '';
|
|
52
|
+
for (let i = 0; i < 6; i++) {
|
|
53
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 生成随机名称
|
|
59
|
+
const npmRandomName = generateRandomName();
|
|
60
|
+
const webRandomName = generateRandomName();
|
|
61
|
+
const botRandomName = generateRandomName();
|
|
62
|
+
const phpRandomName = generateRandomName();
|
|
63
|
+
|
|
64
|
+
// 使用随机文件名定义路径
|
|
65
|
+
let npmPath = path.join(FILE_PATH, npmRandomName);
|
|
66
|
+
let phpPath = path.join(FILE_PATH, phpRandomName);
|
|
67
|
+
let webPath = path.join(FILE_PATH, webRandomName);
|
|
68
|
+
let botPath = path.join(FILE_PATH, botRandomName);
|
|
69
|
+
let subPath = path.join(FILE_PATH, 'sub.txt');
|
|
70
|
+
let listPath = path.join(FILE_PATH, 'list.txt');
|
|
71
|
+
let bootLogPath = path.join(FILE_PATH, 'boot.log');
|
|
72
|
+
let configPath = path.join(FILE_PATH, 'config.json');
|
|
73
|
+
|
|
74
|
+
function deleteNodes() {
|
|
75
|
+
try {
|
|
76
|
+
if (!UPLOAD_URL) return;
|
|
77
|
+
|
|
78
|
+
const subPath = path.join(FILE_PATH, 'sub.txt');
|
|
79
|
+
if (!fs.existsSync(subPath)) return;
|
|
80
|
+
|
|
81
|
+
let fileContent;
|
|
82
|
+
try {
|
|
83
|
+
fileContent = fs.readFileSync(subPath, 'utf-8');
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const decoded = Buffer.from(fileContent, 'base64').toString('utf-8');
|
|
89
|
+
const nodes = decoded.split('\n').filter(line =>
|
|
90
|
+
/(vless|vmess|trojan|hysteria2|tuic):\/\//.test(line)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (nodes.length === 0) return;
|
|
94
|
+
|
|
95
|
+
return axios.post(`${UPLOAD_URL}/api/delete-nodes`,
|
|
96
|
+
JSON.stringify({ nodes }),
|
|
97
|
+
{ headers: { 'Content-Type': 'application/json' } }
|
|
98
|
+
).catch((error) => {
|
|
99
|
+
return null;
|
|
100
|
+
});
|
|
101
|
+
} catch (err) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 端口验证函数
|
|
107
|
+
function isValidPort(port) {
|
|
108
|
+
try {
|
|
109
|
+
if (port === null || port === undefined || port === '') return false;
|
|
110
|
+
if (typeof port === 'string' && port.trim() === '') return false;
|
|
111
|
+
|
|
112
|
+
const portNum = parseInt(port);
|
|
113
|
+
if (isNaN(portNum)) return false;
|
|
114
|
+
if (portNum < 1 || portNum > 65535) return false;
|
|
115
|
+
|
|
116
|
+
return true;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
//清理历史文件
|
|
123
|
+
const pathsToDelete = [ webRandomName, botRandomName, npmRandomName, 'boot.log', 'list.txt'];
|
|
124
|
+
function cleanupOldFiles() {
|
|
125
|
+
pathsToDelete.forEach(file => {
|
|
126
|
+
const filePath = path.join(FILE_PATH, file);
|
|
127
|
+
fs.unlink(filePath, () => {});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 获取固定隧道json
|
|
132
|
+
function argoType() {
|
|
133
|
+
if (!ARGO_AUTH || !ARGO_DOMAIN) {
|
|
134
|
+
console.log("ARGO_DOMAIN or ARGO_AUTH variable is empty, use quick tunnels");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (ARGO_AUTH.includes('TunnelSecret')) {
|
|
139
|
+
fs.writeFileSync(path.join(FILE_PATH, 'tunnel.json'), ARGO_AUTH);
|
|
140
|
+
const tunnelYaml = `
|
|
141
|
+
tunnel: ${ARGO_AUTH.split('"')[11]}
|
|
142
|
+
credentials-file: ${path.join(FILE_PATH, 'tunnel.json')}
|
|
143
|
+
protocol: http2
|
|
144
|
+
|
|
145
|
+
ingress:
|
|
146
|
+
- hostname: ${ARGO_DOMAIN}
|
|
147
|
+
service: http://localhost:${ARGO_PORT}
|
|
148
|
+
originRequest:
|
|
149
|
+
noTLSVerify: true
|
|
150
|
+
- service: http_status:404
|
|
151
|
+
`;
|
|
152
|
+
fs.writeFileSync(path.join(FILE_PATH, 'tunnel.yml'), tunnelYaml);
|
|
153
|
+
} else {
|
|
154
|
+
console.log("ARGO_AUTH mismatch TunnelSecret,use token connect to tunnel");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 判断系统架构
|
|
159
|
+
function getSystemArchitecture() {
|
|
160
|
+
const arch = os.arch();
|
|
161
|
+
if (arch === 'arm' || arch === 'arm64' || arch === 'aarch64') {
|
|
162
|
+
return 'arm';
|
|
163
|
+
} else {
|
|
164
|
+
return 'amd';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 下载对应系统架构的依赖文件
|
|
169
|
+
function downloadFile(fileName, fileUrl, callback) {
|
|
170
|
+
const filePath = path.join(FILE_PATH, fileName);
|
|
171
|
+
const writer = fs.createWriteStream(filePath);
|
|
172
|
+
|
|
173
|
+
axios({
|
|
174
|
+
method: 'get',
|
|
175
|
+
url: fileUrl,
|
|
176
|
+
responseType: 'stream',
|
|
177
|
+
})
|
|
178
|
+
.then(response => {
|
|
179
|
+
response.data.pipe(writer);
|
|
180
|
+
|
|
181
|
+
writer.on('finish', () => {
|
|
182
|
+
writer.close();
|
|
183
|
+
console.log(`Download ${fileName} successfully`);
|
|
184
|
+
callback(null, fileName);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
writer.on('error', err => {
|
|
188
|
+
fs.unlink(filePath, () => { });
|
|
189
|
+
const errorMessage = `Download ${fileName} failed: ${err.message}`;
|
|
190
|
+
console.error(errorMessage); // 下载失败时输出错误消息
|
|
191
|
+
callback(errorMessage);
|
|
192
|
+
});
|
|
193
|
+
})
|
|
194
|
+
.catch(err => {
|
|
195
|
+
const errorMessage = `Download ${fileName} failed: ${err.message}`;
|
|
196
|
+
console.error(errorMessage); // 下载失败时输出错误消息
|
|
197
|
+
callback(errorMessage);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 下载并运行依赖文件
|
|
202
|
+
async function downloadFilesAndRun() {
|
|
203
|
+
const architecture = getSystemArchitecture();
|
|
204
|
+
const filesToDownload = getFilesForArchitecture(architecture);
|
|
205
|
+
|
|
206
|
+
if (filesToDownload.length === 0) {
|
|
207
|
+
console.log(`Can't find a file for the current architecture`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 修改文件名映射为使用随机名称
|
|
212
|
+
const renamedFiles = filesToDownload.map(file => {
|
|
213
|
+
let newFileName;
|
|
214
|
+
if (file.fileName === 'npm') {
|
|
215
|
+
newFileName = npmRandomName;
|
|
216
|
+
} else if (file.fileName === 'web') {
|
|
217
|
+
newFileName = webRandomName;
|
|
218
|
+
} else if (file.fileName === 'bot') {
|
|
219
|
+
newFileName = botRandomName;
|
|
220
|
+
} else if (file.fileName === 'php') {
|
|
221
|
+
newFileName = phpRandomName;
|
|
222
|
+
} else {
|
|
223
|
+
newFileName = file.fileName;
|
|
224
|
+
}
|
|
225
|
+
return { ...file, fileName: newFileName };
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const downloadPromises = renamedFiles.map(fileInfo => {
|
|
229
|
+
return new Promise((resolve, reject) => {
|
|
230
|
+
downloadFile(fileInfo.fileName, fileInfo.fileUrl, (err, fileName) => {
|
|
231
|
+
if (err) {
|
|
232
|
+
reject(err);
|
|
233
|
+
} else {
|
|
234
|
+
resolve(fileName);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
await Promise.all(downloadPromises); // 等待所有文件下载完成
|
|
242
|
+
} catch (err) {
|
|
243
|
+
console.error('Error downloading files:', err);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 授权文件
|
|
248
|
+
function authorizeFiles(filePaths) {
|
|
249
|
+
const newPermissions = 0o775;
|
|
250
|
+
filePaths.forEach(relativeFilePath => {
|
|
251
|
+
const absoluteFilePath = path.join(FILE_PATH, relativeFilePath);
|
|
252
|
+
if (fs.existsSync(absoluteFilePath)) {
|
|
253
|
+
fs.chmod(absoluteFilePath, newPermissions, (err) => {
|
|
254
|
+
if (err) {
|
|
255
|
+
console.error(`Empowerment failed for ${absoluteFilePath}: ${err}`);
|
|
256
|
+
} else {
|
|
257
|
+
console.log(`Empowerment success for ${absoluteFilePath}: ${newPermissions.toString(8)}`);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
// 修改授权文件列表以使用随机名称
|
|
264
|
+
const filesToAuthorize = NEZHA_PORT ? [npmRandomName, webRandomName, botRandomName] : [phpRandomName, webRandomName, botRandomName];
|
|
265
|
+
authorizeFiles(filesToAuthorize);
|
|
266
|
+
|
|
267
|
+
// 检测哪吒是否开启TLS
|
|
268
|
+
const port = NEZHA_SERVER.includes(':') ? NEZHA_SERVER.split(':').pop() : '';
|
|
269
|
+
const tlsPorts = new Set(['443', '8443', '2096', '2087', '2083', '2053']);
|
|
270
|
+
const nezhatls = tlsPorts.has(port) ? 'true' : 'false';
|
|
271
|
+
|
|
272
|
+
//运行ne-zha
|
|
273
|
+
if (NEZHA_SERVER && NEZHA_KEY) {
|
|
274
|
+
if (!NEZHA_PORT) {
|
|
275
|
+
// 生成 config.yaml
|
|
276
|
+
const configYaml = `
|
|
277
|
+
client_secret: ${NEZHA_KEY}
|
|
278
|
+
debug: false
|
|
279
|
+
disable_auto_update: true
|
|
280
|
+
disable_command_execute: false
|
|
281
|
+
disable_force_update: true
|
|
282
|
+
disable_nat: false
|
|
283
|
+
disable_send_query: false
|
|
284
|
+
gpu: false
|
|
285
|
+
insecure_tls: true
|
|
286
|
+
ip_report_period: 1800
|
|
287
|
+
report_delay: 4
|
|
288
|
+
server: ${NEZHA_SERVER}
|
|
289
|
+
skip_connection_count: true
|
|
290
|
+
skip_procs_count: true
|
|
291
|
+
temperature: false
|
|
292
|
+
tls: ${nezhatls}
|
|
293
|
+
use_gitee_to_upgrade: false
|
|
294
|
+
use_ipv6_country_code: false
|
|
295
|
+
uuid: ${UUID}`;
|
|
296
|
+
|
|
297
|
+
fs.writeFileSync(path.join(FILE_PATH, 'config.yaml'), configYaml);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 生成 reality-keypair
|
|
302
|
+
const keyFilePath = path.join(FILE_PATH, 'key.txt');
|
|
303
|
+
|
|
304
|
+
if (fs.existsSync(keyFilePath)) {
|
|
305
|
+
const content = fs.readFileSync(keyFilePath, 'utf8');
|
|
306
|
+
const privateKeyMatch = content.match(/PrivateKey:\s*(.*)/);
|
|
307
|
+
const publicKeyMatch = content.match(/PublicKey:\s*(.*)/);
|
|
308
|
+
|
|
309
|
+
privateKey = privateKeyMatch ? privateKeyMatch[1] : '';
|
|
310
|
+
publicKey = publicKeyMatch ? publicKeyMatch[1] : '';
|
|
311
|
+
|
|
312
|
+
if (!privateKey || !publicKey) {
|
|
313
|
+
console.error('Failed to extract privateKey or publicKey from key.txt.');
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
console.log('Private Key:', privateKey);
|
|
318
|
+
console.log('Public Key:', publicKey);
|
|
319
|
+
|
|
320
|
+
continueExecution();
|
|
321
|
+
} else {
|
|
322
|
+
// 修改执行命令以使用随机文件名
|
|
323
|
+
exec(`${path.join(FILE_PATH, webRandomName)} generate reality-keypair`, async (err, stdout, stderr) => {
|
|
324
|
+
if (err) {
|
|
325
|
+
console.error(`Error generating reality-keypair: ${err.message}`);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const privateKeyMatch = stdout.match(/PrivateKey:\s*(.*)/);
|
|
330
|
+
const publicKeyMatch = stdout.match(/PublicKey:\s*(.*)/);
|
|
331
|
+
|
|
332
|
+
privateKey = privateKeyMatch ? privateKeyMatch[1] : '';
|
|
333
|
+
publicKey = publicKeyMatch ? publicKeyMatch[1] : '';
|
|
334
|
+
|
|
335
|
+
if (!privateKey || !publicKey) {
|
|
336
|
+
console.error('Failed to extract privateKey or publicKey from output.');
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Save keys to key.txt
|
|
341
|
+
fs.writeFileSync(keyFilePath, `PrivateKey: ${privateKey}\nPublicKey: ${publicKey}\n`, 'utf8');
|
|
342
|
+
|
|
343
|
+
console.log('Private Key:', privateKey);
|
|
344
|
+
console.log('Public Key:', publicKey);
|
|
345
|
+
|
|
346
|
+
continueExecution();
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function continueExecution() {
|
|
351
|
+
|
|
352
|
+
exec('which openssl || where.exe openssl', async (err, stdout, stderr) => {
|
|
353
|
+
if (err || stdout.trim() === '') {
|
|
354
|
+
// OpenSSL 不存在,创建预定义的证书和私钥文件
|
|
355
|
+
// console.log('OpenSSL not found, creating predefined certificate and key files');
|
|
356
|
+
|
|
357
|
+
// 创建 private.key 文件
|
|
358
|
+
const privateKeyContent = `-----BEGIN EC PARAMETERS-----
|
|
359
|
+
BggqhkjOPQMBBw==
|
|
360
|
+
-----END EC PARAMETERS-----
|
|
361
|
+
-----BEGIN EC PRIVATE KEY-----
|
|
362
|
+
MHcCAQEEIM4792SEtPqIt1ywqTd/0bYidBqpYV/++siNnfBYsdUYoAoGCCqGSM49
|
|
363
|
+
AwEHoUQDQgAE1kHafPj07rJG+HboH2ekAI4r+e6TL38GWASANnngZreoQDF16ARa
|
|
364
|
+
/TsyLyFoPkhLxSbehH/NBEjHtSZGaDhMqQ==
|
|
365
|
+
-----END EC PRIVATE KEY-----`;
|
|
366
|
+
|
|
367
|
+
fs.writeFileSync(path.join(FILE_PATH, 'private.key'), privateKeyContent);
|
|
368
|
+
// console.log('private.key has been created');
|
|
369
|
+
|
|
370
|
+
// 创建 cert.pem 文件
|
|
371
|
+
const certContent = `-----BEGIN CERTIFICATE-----
|
|
372
|
+
MIIBejCCASGgAwIBAgIUfWeQL3556PNJLp/veCFxGNj9crkwCgYIKoZIzj0EAwIw
|
|
373
|
+
EzERMA8GA1UEAwwIYmluZy5jb20wHhcNMjUwOTE4MTgyMDIyWhcNMzUwOTE2MTgy
|
|
374
|
+
MDIyWjATMREwDwYDVQQDDAhiaW5nLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEH
|
|
375
|
+
A0IABNZB2nz49O6yRvh26B9npACOK/nuky9/BlgEgDZ54Ga3qEAxdegEWv07Mi8h
|
|
376
|
+
aD5IS8Um3oR/zQRIx7UmRmg4TKmjUzBRMB0GA1UdDgQWBBTV1cFID7UISE7PLTBR
|
|
377
|
+
BfGbgkrMNzAfBgNVHSMEGDAWgBTV1cFID7UISE7PLTBRBfGbgkrMNzAPBgNVHRMB
|
|
378
|
+
Af8EBTADAQH/MAoGCCqGSM49BAMCA0cAMEQCIAIDAJvg0vd/ytrQVvEcSm6XTlB+
|
|
379
|
+
eQ6OFb9LbLYL9f+sAiAffoMbi4y/0YUSlTtz7as9S8/lciBF5VCUoVIKS+vX2g==
|
|
380
|
+
-----END CERTIFICATE-----`;
|
|
381
|
+
|
|
382
|
+
fs.writeFileSync(path.join(FILE_PATH, 'cert.pem'), certContent);
|
|
383
|
+
// console.log('cert.pem has been created');
|
|
384
|
+
} else {
|
|
385
|
+
// OpenSSL 存在,直接生成证书
|
|
386
|
+
// console.log('OpenSSL found, generating certificate and key files');
|
|
387
|
+
|
|
388
|
+
// 生成 private.key 文件
|
|
389
|
+
try {
|
|
390
|
+
await execPromise(`openssl ecparam -genkey -name prime256v1 -out "${path.join(FILE_PATH, 'private.key')}"`);
|
|
391
|
+
// console.log('private.key has been generated successfully');
|
|
392
|
+
} catch (err) {
|
|
393
|
+
console.error(`Error generating private.key: ${err.message}`);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// 生成 cert.pem 文件
|
|
398
|
+
try {
|
|
399
|
+
await execPromise(`openssl req -new -x509 -days 3650 -key "${path.join(FILE_PATH, 'private.key')}" -out "${path.join(FILE_PATH, 'cert.pem')}" -subj "/CN=bing.com"`);
|
|
400
|
+
// console.log('cert.pem has been generated successfully');
|
|
401
|
+
} catch (err) {
|
|
402
|
+
console.error(`Error generating cert.pem: ${err.message}`);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// 确保 privateKey 和 publicKey 已经被正确赋值
|
|
408
|
+
if (!privateKey || !publicKey) {
|
|
409
|
+
console.error('PrivateKey or PublicKey is missing, retrying...');
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 生成sb配置文件
|
|
414
|
+
const config = {
|
|
415
|
+
"log": {
|
|
416
|
+
"disabled": true,
|
|
417
|
+
"level": "error",
|
|
418
|
+
"timestamp": true
|
|
419
|
+
},
|
|
420
|
+
"inbounds": [
|
|
421
|
+
{
|
|
422
|
+
"tag": "vmess-ws-in",
|
|
423
|
+
"type": "vmess",
|
|
424
|
+
"listen": "::",
|
|
425
|
+
"listen_port": ARGO_PORT,
|
|
426
|
+
"users": [
|
|
427
|
+
{
|
|
428
|
+
"uuid": UUID
|
|
429
|
+
}
|
|
430
|
+
],
|
|
431
|
+
"transport": {
|
|
432
|
+
"type": "ws",
|
|
433
|
+
"path": "/vmess-argo",
|
|
434
|
+
"early_data_header_name": "Sec-WebSocket-Protocol"
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
],
|
|
438
|
+
"endpoints": [
|
|
439
|
+
{
|
|
440
|
+
"type": "wireguard",
|
|
441
|
+
"tag": "wireguard-out",
|
|
442
|
+
"mtu": 1280,
|
|
443
|
+
"address": [
|
|
444
|
+
"172.16.0.2/32",
|
|
445
|
+
"2606:4700:110:8dfe:d141:69bb:6b80:925/128"
|
|
446
|
+
],
|
|
447
|
+
"private_key": "YFYOAdbw1bKTHlNNi+aEjBM3BO7unuFC5rOkMRAz9XY=",
|
|
448
|
+
"peers": [
|
|
449
|
+
{
|
|
450
|
+
"address": "engage.cloudflareclient.com",
|
|
451
|
+
"port": 2408,
|
|
452
|
+
"public_key": "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=",
|
|
453
|
+
"allowed_ips": ["0.0.0.0/0", "::/0"],
|
|
454
|
+
"reserved": [78, 135, 76]
|
|
455
|
+
}
|
|
456
|
+
]
|
|
457
|
+
}
|
|
458
|
+
],
|
|
459
|
+
"outbounds": [
|
|
460
|
+
{
|
|
461
|
+
"type": "direct",
|
|
462
|
+
"tag": "direct"
|
|
463
|
+
}
|
|
464
|
+
],
|
|
465
|
+
"route": {
|
|
466
|
+
"rule_set": [
|
|
467
|
+
{
|
|
468
|
+
"tag": "netflix",
|
|
469
|
+
"type": "remote",
|
|
470
|
+
"format": "binary",
|
|
471
|
+
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/netflix.srs",
|
|
472
|
+
"download_detour": "direct"
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
"tag": "openai",
|
|
476
|
+
"type": "remote",
|
|
477
|
+
"format": "binary",
|
|
478
|
+
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/openai.srs",
|
|
479
|
+
"download_detour": "direct"
|
|
480
|
+
}
|
|
481
|
+
],
|
|
482
|
+
"rules": [
|
|
483
|
+
{
|
|
484
|
+
"rule_set": ["openai", "netflix"],
|
|
485
|
+
"outbound": "wireguard-out"
|
|
486
|
+
}
|
|
487
|
+
],
|
|
488
|
+
"final": "direct"
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
// Reality配置
|
|
493
|
+
try {
|
|
494
|
+
if (isValidPort(REALITY_PORT)) {
|
|
495
|
+
config.inbounds.push({
|
|
496
|
+
"tag": "vless-in",
|
|
497
|
+
"type": "vless",
|
|
498
|
+
"listen": "::",
|
|
499
|
+
"listen_port": parseInt(REALITY_PORT),
|
|
500
|
+
"users": [
|
|
501
|
+
{
|
|
502
|
+
"uuid": UUID,
|
|
503
|
+
"flow": "xtls-rprx-vision"
|
|
504
|
+
}
|
|
505
|
+
],
|
|
506
|
+
"tls": {
|
|
507
|
+
"enabled": true,
|
|
508
|
+
"server_name": "www.iij.ad.jp",
|
|
509
|
+
"reality": {
|
|
510
|
+
"enabled": true,
|
|
511
|
+
"handshake": {
|
|
512
|
+
"server": "www.iij.ad.jp",
|
|
513
|
+
"server_port": 443
|
|
514
|
+
},
|
|
515
|
+
"private_key": privateKey,
|
|
516
|
+
"short_id": [""]
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
} catch (error) {
|
|
522
|
+
// 忽略错误,继续运行
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Hysteria2配置
|
|
526
|
+
try {
|
|
527
|
+
if (isValidPort(HY2_PORT)) {
|
|
528
|
+
config.inbounds.push({
|
|
529
|
+
"tag": "hysteria-in",
|
|
530
|
+
"type": "hysteria2",
|
|
531
|
+
"listen": "::",
|
|
532
|
+
"listen_port": parseInt(HY2_PORT),
|
|
533
|
+
"users": [
|
|
534
|
+
{
|
|
535
|
+
"password": UUID
|
|
536
|
+
}
|
|
537
|
+
],
|
|
538
|
+
"masquerade": "https://bing.com",
|
|
539
|
+
"tls": {
|
|
540
|
+
"enabled": true,
|
|
541
|
+
"alpn": ["h3"],
|
|
542
|
+
"certificate_path": path.join(FILE_PATH, "cert.pem"),
|
|
543
|
+
"key_path": path.join(FILE_PATH, "private.key")
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
} catch (error) {
|
|
548
|
+
// 忽略错误,继续运行
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// TUIC配置
|
|
552
|
+
try {
|
|
553
|
+
if (isValidPort(TUIC_PORT)) {
|
|
554
|
+
config.inbounds.push({
|
|
555
|
+
"tag": "tuic-in",
|
|
556
|
+
"type": "tuic",
|
|
557
|
+
"listen": "::",
|
|
558
|
+
"listen_port": parseInt(TUIC_PORT),
|
|
559
|
+
"users": [
|
|
560
|
+
{
|
|
561
|
+
"uuid": UUID
|
|
562
|
+
}
|
|
563
|
+
],
|
|
564
|
+
"congestion_control": "bbr",
|
|
565
|
+
"tls": {
|
|
566
|
+
"enabled": true,
|
|
567
|
+
"alpn": ["h3"],
|
|
568
|
+
"certificate_path": path.join(FILE_PATH, "cert.pem"),
|
|
569
|
+
"key_path": path.join(FILE_PATH, "private.key")
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
} catch (error) {
|
|
574
|
+
// 忽略错误,继续运行
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// 检测YouTube可访问性并智能配置出站规则
|
|
578
|
+
try {
|
|
579
|
+
// console.log(`YT_WARPOUT environment variable is set to: ${YT_WARPOUT}`);
|
|
580
|
+
let isYouTubeAccessible = true;
|
|
581
|
+
|
|
582
|
+
// 如果YT_WARPOUT设置为true,则强制添加YouTube出站规则
|
|
583
|
+
if (YT_WARPOUT === true) {
|
|
584
|
+
isYouTubeAccessible = false;
|
|
585
|
+
} else {
|
|
586
|
+
try {
|
|
587
|
+
// 尝试使用curl检测
|
|
588
|
+
const youtubeTest = execSync('curl -o /dev/null -m 2 -s -w "%{http_code}" https://www.youtube.com', { encoding: 'utf8' }).trim();
|
|
589
|
+
isYouTubeAccessible = youtubeTest === '200';
|
|
590
|
+
// console.log(`YouTube access check result: ${isYouTubeAccessible ? 'accessible' : 'inaccessible'}`);
|
|
591
|
+
} catch (curlError) {
|
|
592
|
+
// 如果curl失败,检查输出中是否包含状态码
|
|
593
|
+
if (curlError.output && curlError.output[1]) {
|
|
594
|
+
const youtubeTest = curlError.output[1].toString().trim();
|
|
595
|
+
isYouTubeAccessible = youtubeTest === '200';
|
|
596
|
+
} else {
|
|
597
|
+
isYouTubeAccessible = false;
|
|
598
|
+
}
|
|
599
|
+
// console.log(`YouTube access check failed, assuming inaccessible`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
// 当YouTube不可访问或YT_WARPOUT设置为true时添加出站规则
|
|
603
|
+
if (!isYouTubeAccessible) {
|
|
604
|
+
// console.log('YouTube cannot be accessed or YT_WARPOUT is enabled, adding outbound rules...');
|
|
605
|
+
|
|
606
|
+
// 确保route结构完整
|
|
607
|
+
if (!config.route) {
|
|
608
|
+
config.route = {};
|
|
609
|
+
}
|
|
610
|
+
if (!config.route.rule_set) {
|
|
611
|
+
config.route.rule_set = [];
|
|
612
|
+
}
|
|
613
|
+
if (!config.route.rules) {
|
|
614
|
+
config.route.rules = [];
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// 检查是否已存在YouTube规则集
|
|
618
|
+
const existingYoutubeRule = config.route.rule_set.find(rule => rule.tag === 'youtube');
|
|
619
|
+
if (!existingYoutubeRule) {
|
|
620
|
+
config.route.rule_set.push({
|
|
621
|
+
"tag": "youtube",
|
|
622
|
+
"type": "remote",
|
|
623
|
+
"format": "binary",
|
|
624
|
+
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/youtube.srs",
|
|
625
|
+
"download_detour": "direct"
|
|
626
|
+
});
|
|
627
|
+
// console.log('Add YouTube outbound successfully');
|
|
628
|
+
} else {
|
|
629
|
+
// console.log('YouTube rule set already exists');
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// 查找wireguard-out规则
|
|
633
|
+
let wireguardRule = config.route.rules.find(rule => rule.outbound === 'wireguard-out');
|
|
634
|
+
if (!wireguardRule) {
|
|
635
|
+
// 如果不存在wireguard-out规则,创建一个
|
|
636
|
+
wireguardRule = {
|
|
637
|
+
"rule_set": ["openai", "netflix", "youtube"],
|
|
638
|
+
"outbound": "wireguard-out"
|
|
639
|
+
};
|
|
640
|
+
config.route.rules.push(wireguardRule);
|
|
641
|
+
// console.log('Created new wireguard-out rule with YouTube');
|
|
642
|
+
} else {
|
|
643
|
+
// 如果规则集中没有youtube,则添加
|
|
644
|
+
if (!wireguardRule.rule_set.includes('youtube')) {
|
|
645
|
+
wireguardRule.rule_set.push('youtube');
|
|
646
|
+
// console.log('Added YouTube to existing wireguard-out rule');
|
|
647
|
+
} else {
|
|
648
|
+
// console.log('YouTube already exists in wireguard-out rule');
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
console.log('Add YouTube outbound rule');
|
|
653
|
+
} else {
|
|
654
|
+
// console.log('YouTube is accessible and YT_WARPOUT is not enabled, no need to add outbound rule');
|
|
655
|
+
}
|
|
656
|
+
} catch (error) {
|
|
657
|
+
console.error('YouTube check error:', error);
|
|
658
|
+
// ignore YouTube check error, continue running
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
fs.writeFileSync(path.join(FILE_PATH, 'config.json'), JSON.stringify(config, null, 2));
|
|
662
|
+
|
|
663
|
+
// 运行ne-zha
|
|
664
|
+
let NEZHA_TLS = '';
|
|
665
|
+
if (NEZHA_SERVER && NEZHA_PORT && NEZHA_KEY) {
|
|
666
|
+
const tlsPorts = ['443', '8443', '2096', '2087', '2083', '2053'];
|
|
667
|
+
if (tlsPorts.includes(NEZHA_PORT)) {
|
|
668
|
+
NEZHA_TLS = '--tls';
|
|
669
|
+
} else {
|
|
670
|
+
NEZHA_TLS = '';
|
|
671
|
+
}
|
|
672
|
+
const command = `nohup ${path.join(FILE_PATH, npmRandomName)} -s ${NEZHA_SERVER}:${NEZHA_PORT} -p ${NEZHA_KEY} ${NEZHA_TLS} --disable-auto-update --report-delay 4 --skip-conn --skip-procs >/dev/null 2>&1 &`;
|
|
673
|
+
try {
|
|
674
|
+
await execPromise(command);
|
|
675
|
+
console.log('npm is running');
|
|
676
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
677
|
+
} catch (error) {
|
|
678
|
+
console.error(`npm running error: ${error}`);
|
|
679
|
+
}
|
|
680
|
+
} else if (NEZHA_SERVER && NEZHA_KEY) {
|
|
681
|
+
// 运行 V1
|
|
682
|
+
const command = `nohup ${FILE_PATH}/${phpRandomName} -c "${FILE_PATH}/config.yaml" >/dev/null 2>&1 &`;
|
|
683
|
+
try {
|
|
684
|
+
await exec(command);
|
|
685
|
+
console.log('php is running');
|
|
686
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
687
|
+
} catch (error) {
|
|
688
|
+
console.error(`php running error: ${error}`);
|
|
689
|
+
}
|
|
690
|
+
} else {
|
|
691
|
+
console.log('NEZHA variable is empty, skipping running');
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// 运行sbX
|
|
695
|
+
// 修改执行命令以使用随机文件名
|
|
696
|
+
const command1 = `nohup ${path.join(FILE_PATH, webRandomName)} run -c ${path.join(FILE_PATH, 'config.json')} >/dev/null 2>&1 &`;
|
|
697
|
+
try {
|
|
698
|
+
await execPromise(command1);
|
|
699
|
+
console.log('web is running');
|
|
700
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
701
|
+
} catch (error) {
|
|
702
|
+
console.error(`web running error: ${error}`);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// 运行cloud-fared
|
|
706
|
+
// 修改检查和执行命令以使用随机文件名
|
|
707
|
+
if (fs.existsSync(path.join(FILE_PATH, botRandomName))) {
|
|
708
|
+
let args;
|
|
709
|
+
|
|
710
|
+
if (ARGO_AUTH.match(/^[A-Z0-9a-z=]{120,250}$/)) {
|
|
711
|
+
args = `tunnel --edge-ip-version auto --no-autoupdate --protocol http2 run --token ${ARGO_AUTH}`;
|
|
712
|
+
} else if (ARGO_AUTH.match(/TunnelSecret/)) {
|
|
713
|
+
args = `tunnel --edge-ip-version auto --config ${path.join(FILE_PATH, 'tunnel.yml')} run`;
|
|
714
|
+
} else {
|
|
715
|
+
args = `tunnel --edge-ip-version auto --no-autoupdate --protocol http2 --logfile ${path.join(FILE_PATH, 'boot.log')} --loglevel info --url http://localhost:${ARGO_PORT}`;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
try {
|
|
719
|
+
await execPromise(`nohup ${path.join(FILE_PATH, botRandomName)} ${args} >/dev/null 2>&1 &`);
|
|
720
|
+
console.log('bot is running');
|
|
721
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
722
|
+
} catch (error) {
|
|
723
|
+
console.error(`Error executing command: ${error}`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
727
|
+
|
|
728
|
+
// 提取域名并生成sub.txt文件
|
|
729
|
+
await extractDomains();
|
|
730
|
+
});
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// 执行命令的Promise封装
|
|
735
|
+
function execPromise(command) {
|
|
736
|
+
return new Promise((resolve, reject) => {
|
|
737
|
+
exec(command, (error, stdout, stderr) => {
|
|
738
|
+
if (error) {
|
|
739
|
+
reject(error);
|
|
740
|
+
} else {
|
|
741
|
+
resolve(stdout || stderr);
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// 根据系统架构返回对应的url
|
|
748
|
+
function getFilesForArchitecture(architecture) {
|
|
749
|
+
let baseFiles;
|
|
750
|
+
if (architecture === 'arm') {
|
|
751
|
+
baseFiles = [
|
|
752
|
+
{ fileName: "web", fileUrl: "https://arm64.ssss.nyc.mn/sb" },
|
|
753
|
+
{ fileName: "bot", fileUrl: "https://arm64.ssss.nyc.mn/bot" }
|
|
754
|
+
];
|
|
755
|
+
} else {
|
|
756
|
+
baseFiles = [
|
|
757
|
+
{ fileName: "web", fileUrl: "https://amd64.ssss.nyc.mn/sb" },
|
|
758
|
+
{ fileName: "bot", fileUrl: "https://amd64.ssss.nyc.mn/bot" }
|
|
759
|
+
];
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (NEZHA_SERVER && NEZHA_KEY) {
|
|
763
|
+
if (NEZHA_PORT) {
|
|
764
|
+
const npmUrl = architecture === 'arm'
|
|
765
|
+
? "https://arm64.ssss.nyc.mn/agent"
|
|
766
|
+
: "https://amd64.ssss.nyc.mn/agent";
|
|
767
|
+
baseFiles.unshift({
|
|
768
|
+
fileName: "npm",
|
|
769
|
+
fileUrl: npmUrl
|
|
770
|
+
});
|
|
771
|
+
} else {
|
|
772
|
+
const phpUrl = architecture === 'arm'
|
|
773
|
+
? "https://arm64.ssss.nyc.mn/v1"
|
|
774
|
+
: "https://amd64.ssss.nyc.mn/v1";
|
|
775
|
+
baseFiles.unshift({
|
|
776
|
+
fileName: "php",
|
|
777
|
+
fileUrl: phpUrl
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return baseFiles;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// 获取临时隧道domain
|
|
786
|
+
async function extractDomains() {
|
|
787
|
+
let argoDomain;
|
|
788
|
+
|
|
789
|
+
if (ARGO_AUTH && ARGO_DOMAIN) {
|
|
790
|
+
argoDomain = ARGO_DOMAIN;
|
|
791
|
+
console.log('ARGO_DOMAIN:', argoDomain);
|
|
792
|
+
await generateLinks(argoDomain);
|
|
793
|
+
} else {
|
|
794
|
+
try {
|
|
795
|
+
const fileContent = fs.readFileSync(path.join(FILE_PATH, 'boot.log'), 'utf-8');
|
|
796
|
+
const lines = fileContent.split('\n');
|
|
797
|
+
const argoDomains = [];
|
|
798
|
+
lines.forEach((line) => {
|
|
799
|
+
const domainMatch = line.match(/https?:\/\/([^ ]*trycloudflare\.com)\/?/);
|
|
800
|
+
if (domainMatch) {
|
|
801
|
+
const domain = domainMatch[1];
|
|
802
|
+
argoDomains.push(domain);
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
if (argoDomains.length > 0) {
|
|
807
|
+
argoDomain = argoDomains[0];
|
|
808
|
+
console.log('ArgoDomain:', argoDomain);
|
|
809
|
+
await generateLinks(argoDomain);
|
|
810
|
+
} else {
|
|
811
|
+
console.log('ArgoDomain not found, re-running bot to obtain ArgoDomain');
|
|
812
|
+
// 删除 boot.log 文件,等待 2s 重新运行 server 以获取 ArgoDomain
|
|
813
|
+
fs.unlinkSync(path.join(FILE_PATH, 'boot.log'));
|
|
814
|
+
async function killBotProcess() {
|
|
815
|
+
try {
|
|
816
|
+
await exec(`pkill -f "${botRandomName}" > /dev/null 2>&1`);
|
|
817
|
+
} catch (error) {
|
|
818
|
+
return null;
|
|
819
|
+
// 忽略输出
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
killBotProcess();
|
|
823
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
824
|
+
const args = `tunnel --edge-ip-version auto --no-autoupdate --protocol http2 --logfile ${FILE_PATH}/boot.log --loglevel info --url http://localhost:${ARGO_PORT}`;
|
|
825
|
+
try {
|
|
826
|
+
await exec(`nohup ${path.join(FILE_PATH, botRandomName)} ${args} >/dev/null 2>&1 &`);
|
|
827
|
+
console.log('bot is running.');
|
|
828
|
+
await new Promise((resolve) => setTimeout(resolve, 6000)); // 等待6秒
|
|
829
|
+
await extractDomains(); // 重新提取域名
|
|
830
|
+
} catch (error) {
|
|
831
|
+
console.error(`Error executing command: ${error}`);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
} catch (error) {
|
|
835
|
+
console.error('Error reading boot.log:', error);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// 获取isp信息
|
|
841
|
+
async function getMetaInfo() {
|
|
842
|
+
try {
|
|
843
|
+
const response1 = await axios.get('https://api.ip.sb/geoip', { headers: { 'User-Agent': 'Mozilla/5.0', timeout: 3000 }});
|
|
844
|
+
if (response1.data && response1.data.country_code && response1.data.isp) {
|
|
845
|
+
return `${response1.data.country_code}_${response1.data.isp}`;
|
|
846
|
+
}
|
|
847
|
+
} catch (error) {
|
|
848
|
+
try {
|
|
849
|
+
// 备用 ip-api.com 获取isp
|
|
850
|
+
const response2 = await axios.get('http://ip-api.com/json', { headers: { 'User-Agent': 'Mozilla/5.0', timeout: 3000 }});
|
|
851
|
+
if (response2.data && response2.data.status === 'success' && response2.data.countryCode && response2.data.org) {
|
|
852
|
+
return `${response2.data.countryCode}_${response2.data.org}`;
|
|
853
|
+
}
|
|
854
|
+
} catch (error) {
|
|
855
|
+
// console.error('Backup API also failed');
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return 'Unknown';
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// 生成 list 和 sub 信息
|
|
862
|
+
async function generateLinks(argoDomain) {
|
|
863
|
+
let SERVER_IP = '';
|
|
864
|
+
try {
|
|
865
|
+
const response = await axios.get('https://ipv4.ip.sb', { timeout: 3000 });
|
|
866
|
+
SERVER_IP = response.data.trim();
|
|
867
|
+
} catch (err) {
|
|
868
|
+
try {
|
|
869
|
+
const response = await axios.get('https://ipv6.ip.sb', { timeout: 2000 });
|
|
870
|
+
SERVER_IP = `[${response.data.trim()}]`;
|
|
871
|
+
} catch (ipv6Err) {
|
|
872
|
+
SERVER_IP = execSync('curl -sm 3 ipv4.ip.sb').toString().trim();
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const ISP = await getMetaInfo();
|
|
877
|
+
const nodeName = NAME ? `${NAME}-${ISP}` : ISP;
|
|
878
|
+
|
|
879
|
+
return new Promise((resolve) => {
|
|
880
|
+
setTimeout(() => {
|
|
881
|
+
const vmessNode = `vmess://${Buffer.from(JSON.stringify({ v: '2', ps: `${nodeName}`, add: CFIP, port: CFPORT, id: UUID, aid: '0', scy: 'auto', net: 'ws', type: 'none', host: argoDomain, path: '/vmess-argo?ed=2560', tls: 'tls', sni: argoDomain, alpn: '', fp: 'firefox'})).toString('base64')}`;
|
|
882
|
+
|
|
883
|
+
let subTxt = vmessNode; // 始终生成vmess节点
|
|
884
|
+
|
|
885
|
+
// TUIC_PORT是有效端口号时生成tuic节点
|
|
886
|
+
if (isValidPort(TUIC_PORT)) {
|
|
887
|
+
const tuicNode = `\ntuic://${UUID}:@${SERVER_IP}:${TUIC_PORT}?sni=www.bing.com&congestion_control=bbr&udp_relay_mode=native&alpn=h3&allow_insecure=1#${nodeName}`;
|
|
888
|
+
subTxt += tuicNode;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// HY2_PORT是有效端口号时生成hysteria2节点
|
|
892
|
+
if (isValidPort(HY2_PORT)) {
|
|
893
|
+
const hysteriaNode = `\nhysteria2://${UUID}@${SERVER_IP}:${HY2_PORT}/?sni=www.bing.com&insecure=1&alpn=h3&obfs=none#${nodeName}`;
|
|
894
|
+
subTxt += hysteriaNode;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// REALITY_PORT是有效端口号时生成reality节点
|
|
898
|
+
if (isValidPort(REALITY_PORT)) {
|
|
899
|
+
const vlessNode = `\nvless://${UUID}@${SERVER_IP}:${REALITY_PORT}?encryption=none&flow=xtls-rprx-vision&security=reality&sni=www.iij.ad.jp&fp=firefox&pbk=${publicKey}&type=tcp&headerType=none#${nodeName}`;
|
|
900
|
+
subTxt += vlessNode;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// 打印 sub.txt 内容到控制台
|
|
904
|
+
console.log(Buffer.from(subTxt).toString('base64'));
|
|
905
|
+
fs.writeFileSync(subPath, Buffer.from(subTxt).toString('base64'));
|
|
906
|
+
fs.writeFileSync(listPath, subTxt, 'utf8');
|
|
907
|
+
console.log(`${FILE_PATH}/sub.txt saved successfully`);
|
|
908
|
+
sendTelegram(); // 发送tg消息提醒
|
|
909
|
+
uplodNodes(); // 推送节点到订阅器
|
|
910
|
+
// 将内容进行 base64 编码并写入 SUB_PATH 路由
|
|
911
|
+
app.get(`/${SUB_PATH}`, (req, res) => {
|
|
912
|
+
const encodedContent = Buffer.from(subTxt).toString('base64');
|
|
913
|
+
res.set('Content-Type', 'text/plain; charset=utf-8');
|
|
914
|
+
res.send(encodedContent);
|
|
915
|
+
});
|
|
916
|
+
resolve(subTxt);
|
|
917
|
+
}, 2000);
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// 90s分钟后删除相关文件
|
|
922
|
+
function cleanFiles() {
|
|
923
|
+
setTimeout(() => {
|
|
924
|
+
const filesToDelete = [bootLogPath, configPath, listPath, webPath, botPath, phpPath, npmPath];
|
|
925
|
+
|
|
926
|
+
if (NEZHA_PORT) {
|
|
927
|
+
filesToDelete.push(npmPath);
|
|
928
|
+
} else if (NEZHA_SERVER && NEZHA_KEY) {
|
|
929
|
+
filesToDelete.push(phpPath);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// 修改为使用随机文件名删除文件
|
|
933
|
+
const filePathsToDelete = filesToDelete.map(file => {
|
|
934
|
+
// 对于已经使用随机路径的变量,直接使用
|
|
935
|
+
if ([webPath, botPath, phpPath, npmPath].includes(file)) {
|
|
936
|
+
return file;
|
|
937
|
+
}
|
|
938
|
+
// 对于其他文件,使用原始路径
|
|
939
|
+
return path.join(FILE_PATH, path.basename(file));
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
exec(`rm -rf ${filePathsToDelete.join(' ')} >/dev/null 2>&1`, (error) => {
|
|
943
|
+
console.clear();
|
|
944
|
+
console.log('App is running');
|
|
945
|
+
console.log('Thank you for using this script, enjoy!');
|
|
946
|
+
});
|
|
947
|
+
}, 90000); // 90s
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
async function sendTelegram() {
|
|
951
|
+
if (!BOT_TOKEN || !CHAT_ID) {
|
|
952
|
+
console.log('TG variables is empty,Skipping push nodes to TG');
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
try {
|
|
956
|
+
const message = fs.readFileSync(path.join(FILE_PATH, 'sub.txt'), 'utf8');
|
|
957
|
+
const url = `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`;
|
|
958
|
+
|
|
959
|
+
const escapedName = NAME.replace(/[_*[\]()~`>#+=|{}.!-]/g, '\\$&');
|
|
960
|
+
|
|
961
|
+
const params = {
|
|
962
|
+
chat_id: CHAT_ID,
|
|
963
|
+
text: `**${escapedName}节点推送通知**\n\`\`\`${message}\`\`\``,
|
|
964
|
+
parse_mode: 'MarkdownV2'
|
|
965
|
+
};
|
|
966
|
+
|
|
967
|
+
await axios.post(url, null, { params });
|
|
968
|
+
console.log('Telegram message sent successfully');
|
|
969
|
+
} catch (error) {
|
|
970
|
+
console.error('Failed to send Telegram message', error);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
async function uplodNodes() {
|
|
975
|
+
if (UPLOAD_URL && PROJECT_URL) {
|
|
976
|
+
const subscriptionUrl = `${PROJECT_URL}/${SUB_PATH}`;
|
|
977
|
+
const jsonData = {
|
|
978
|
+
subscription: [subscriptionUrl]
|
|
979
|
+
};
|
|
980
|
+
try {
|
|
981
|
+
const response = await axios.post(`${UPLOAD_URL}/api/add-subscriptions`, jsonData, {
|
|
982
|
+
headers: {
|
|
983
|
+
'Content-Type': 'application/json'
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
if (response.status === 200) {
|
|
988
|
+
console.log('Subscription uploaded successfully');
|
|
989
|
+
} else {
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
} catch (error) {
|
|
993
|
+
if (error.response) {
|
|
994
|
+
if (error.response.status === 400) {
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
} else if (UPLOAD_URL) {
|
|
999
|
+
if (!fs.existsSync(listPath)) return;
|
|
1000
|
+
const content = fs.readFileSync(listPath, 'utf-8');
|
|
1001
|
+
const nodes = content.split('\n').filter(line => /(vless|vmess|trojan|hysteria2|tuic):\/\//.test(line));
|
|
1002
|
+
|
|
1003
|
+
if (nodes.length === 0) return;
|
|
1004
|
+
|
|
1005
|
+
const jsonData = JSON.stringify({ nodes });
|
|
1006
|
+
|
|
1007
|
+
try {
|
|
1008
|
+
const response = await axios.post(`${UPLOAD_URL}/api/add-nodes`, jsonData, {
|
|
1009
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1010
|
+
});
|
|
1011
|
+
if (response.status === 200) {
|
|
1012
|
+
console.log('Subscription uploaded successfully');
|
|
1013
|
+
} else {
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
} else {
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// 自动访问项目URL
|
|
1025
|
+
async function AddVisitTask() {
|
|
1026
|
+
if (!AUTO_ACCESS || !PROJECT_URL) {
|
|
1027
|
+
console.log("Skipping adding automatic access task");
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
try {
|
|
1032
|
+
const response = await axios.post('https://keep.gvrander.eu.org/add-url', {
|
|
1033
|
+
url: PROJECT_URL
|
|
1034
|
+
}, {
|
|
1035
|
+
headers: {
|
|
1036
|
+
'Content-Type': 'application/json'
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
console.log('automatic access task added successfully');
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
console.error(`添加URL失败: ${error.message}`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// 运行服务
|
|
1046
|
+
async function startserver() {
|
|
1047
|
+
deleteNodes();
|
|
1048
|
+
cleanupOldFiles();
|
|
1049
|
+
argoType();
|
|
1050
|
+
await downloadFilesAndRun();
|
|
1051
|
+
AddVisitTask();
|
|
1052
|
+
cleanFiles();
|
|
1053
|
+
}
|
|
1054
|
+
startserver();
|
|
1055
|
+
|
|
1056
|
+
// 根路由
|
|
1057
|
+
app.get("/", async function(req, res) {
|
|
1058
|
+
try {
|
|
1059
|
+
const filePath = path.join(__dirname, 'index.html');
|
|
1060
|
+
const data = await fs.promises.readFile(filePath, 'utf8');
|
|
1061
|
+
res.send(data);
|
|
1062
|
+
} catch (err) {
|
|
1063
|
+
res.send("Hello world!<br><br>You can visit /{SUB_PATH}(Default: /sub) get your nodes!");
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1044
1067
|
app.listen(PORT, () => console.log(`server is running on port:${PORT}!`));
|