nodejs-argo 2.0.1 → 2.0.3

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.
Files changed (4) hide show
  1. package/LICENSE +1 -1
  2. package/index.js +196 -101
  3. package/package.json +13 -16
  4. package/README.md +0 -195
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 eooce
3
+ Copyright (c) 2024 Your Name
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/index.js CHANGED
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- require('dotenv').config();
4
-
5
3
  const express = require("express");
6
4
  const app = express();
7
5
  const axios = require("axios");
@@ -11,10 +9,10 @@ const path = require("path");
11
9
  const { promisify } = require('util');
12
10
  const exec = promisify(require('child_process').exec);
13
11
  const { execSync } = require('child_process'); // 只填写UPLOAD_URL将上传节点,同时填写UPLOAD_URL和PROJECT_URL将上传订阅
14
- const UPLOAD_URL = process.env.UPLOAD_URL || ''; // 节点或订阅自动上传地址,需填写部署Merge-sub项目后的首页地址,例如:https://merge.serv00.net
12
+ const UPLOAD_URL = process.env.UPLOAD_URL || ''; // 节点或订阅自动上传地址,需填写部署Merge-sub项目后的首页地址,例如:https://merge.xxx.com
15
13
  const PROJECT_URL = process.env.PROJECT_URL || ''; // 需要上传订阅或保活时需填写项目分配的url,例如:https://google.com
16
14
  const AUTO_ACCESS = process.env.AUTO_ACCESS || false; // false关闭自动保活,true开启,需同时填写PROJECT_URL变量
17
- const FILE_PATH = process.env.FILE_PATH || './tmp'; // 运行目录,sub节点文件保存目录
15
+ const FILE_PATH = process.env.FILE_PATH || '.npm'; // 运行目录,sub节点文件保存目录
18
16
  const SUB_PATH = process.env.SUB_PATH || 'sub'; // 订阅路径
19
17
  const PORT = process.env.SERVER_PORT || process.env.PORT || 3000; // http服务订阅端口
20
18
  const UUID = process.env.UUID || '9afd1229-b893-40c1-84dd-51e7ce204913'; // 使用哪吒v1,在不同的平台运行需修改UUID,否则会覆盖
@@ -22,13 +20,15 @@ const NEZHA_SERVER = process.env.NEZHA_SERVER || ''; // 哪吒v1填写形
22
20
  const NEZHA_PORT = process.env.NEZHA_PORT || ''; // 使用哪吒v1请留空,哪吒v0需填写
23
21
  const NEZHA_KEY = process.env.NEZHA_KEY || ''; // 哪吒v1的NZ_CLIENT_SECRET或哪吒v0的agent密钥
24
22
  const ARGO_DOMAIN = process.env.ARGO_DOMAIN || ''; // 固定隧道域名,留空即启用临时隧道
25
- const ARGO_AUTH = process.env.ARGO_AUTH || ''; // 固定隧道密钥json或token,留空即启用临时隧道,json获取地址:https://fscarmen.cloudflare.now.cc
23
+ const ARGO_AUTH = process.env.ARGO_AUTH || ''; // 固定隧道密钥json或token,留空即启用临时隧道,json获取地址:https://json.zone.id
26
24
  const ARGO_PORT = process.env.ARGO_PORT || 8001; // 固定隧道端口,使用token需在cloudflare后台设置和这里一致
27
- const CFIP = process.env.CFIP || 'www.visa.com.tw'; // 节点优选域名或优选ip
25
+ const CFIP = process.env.CFIP || 'cdns.doon.eu.org'; // 节点优选域名或优选ip
28
26
  const CFPORT = process.env.CFPORT || 443; // 节点优选域名或优选ip对应的端口
29
- const NAME = process.env.NAME || 'Vls'; // 节点名称
27
+ const NAME = process.env.NAME || ''; // 节点名称
28
+
29
+ require('dotenv').config();
30
30
 
31
- //创建运行文件夹
31
+ // 创建运行文件夹
32
32
  if (!fs.existsSync(FILE_PATH)) {
33
33
  fs.mkdirSync(FILE_PATH);
34
34
  console.log(`${FILE_PATH} is created`);
@@ -36,10 +36,25 @@ if (!fs.existsSync(FILE_PATH)) {
36
36
  console.log(`${FILE_PATH} already exists`);
37
37
  }
38
38
 
39
- let npmPath = path.join(FILE_PATH, 'npm');
40
- let phpPath = path.join(FILE_PATH, 'php');
41
- let webPath = path.join(FILE_PATH, 'web');
42
- let botPath = path.join(FILE_PATH, 'bot');
39
+ // 生成随机6位字符文件名
40
+ function generateRandomName() {
41
+ const characters = 'abcdefghijklmnopqrstuvwxyz';
42
+ let result = '';
43
+ for (let i = 0; i < 6; i++) {
44
+ result += characters.charAt(Math.floor(Math.random() * characters.length));
45
+ }
46
+ return result;
47
+ }
48
+
49
+ // 全局常量
50
+ const npmName = generateRandomName();
51
+ const webName = generateRandomName();
52
+ const botName = generateRandomName();
53
+ const phpName = generateRandomName();
54
+ let npmPath = path.join(FILE_PATH, npmName);
55
+ let phpPath = path.join(FILE_PATH, phpName);
56
+ let webPath = path.join(FILE_PATH, webName);
57
+ let botPath = path.join(FILE_PATH, botName);
43
58
  let subPath = path.join(FILE_PATH, 'sub.txt');
44
59
  let listPath = path.join(FILE_PATH, 'list.txt');
45
60
  let bootLogPath = path.join(FILE_PATH, 'boot.log');
@@ -65,45 +80,54 @@ function deleteNodes() {
65
80
 
66
81
  if (nodes.length === 0) return;
67
82
 
68
- return axios.post(`${UPLOAD_URL}/api/delete-nodes`,
83
+ axios.post(`${UPLOAD_URL}/api/delete-nodes`,
69
84
  JSON.stringify({ nodes }),
70
85
  { headers: { 'Content-Type': 'application/json' } }
71
86
  ).catch((error) => {
72
87
  return null;
73
88
  });
89
+ return null;
74
90
  } catch (err) {
75
91
  return null;
76
92
  }
77
93
  }
78
94
 
79
- //清理历史文件
95
+ // 清理历史文件
80
96
  function cleanupOldFiles() {
81
- const pathsToDelete = ['web', 'bot', 'npm', 'php', 'sub.txt', 'boot.log'];
82
- pathsToDelete.forEach(file => {
83
- const filePath = path.join(FILE_PATH, file);
84
- fs.unlink(filePath, () => {});
85
- });
97
+ try {
98
+ const files = fs.readdirSync(FILE_PATH);
99
+ files.forEach(file => {
100
+ const filePath = path.join(FILE_PATH, file);
101
+ try {
102
+ const stat = fs.statSync(filePath);
103
+ if (stat.isFile()) {
104
+ fs.unlinkSync(filePath);
105
+ }
106
+ } catch (err) {
107
+ // 忽略所有错误,不记录日志
108
+ }
109
+ });
110
+ } catch (err) {
111
+ // 忽略所有错误,不记录日志
112
+ }
86
113
  }
87
114
 
88
- // 根路由
89
- app.get("/", function(req, res) {
90
- res.send("Hello world!");
91
- });
92
-
93
115
  // 生成xr-ay配置文件
94
- const config = {
95
- log: { access: '/dev/null', error: '/dev/null', loglevel: 'none' },
96
- inbounds: [
97
- { port: ARGO_PORT, protocol: 'vless', settings: { clients: [{ id: UUID, flow: 'xtls-rprx-vision' }], decryption: 'none', fallbacks: [{ dest: 3001 }, { path: "/vless-argo", dest: 3002 }, { path: "/vmess-argo", dest: 3003 }, { path: "/trojan-argo", dest: 3004 }] }, streamSettings: { network: 'tcp' } },
98
- { port: 3001, listen: "127.0.0.1", protocol: "vless", settings: { clients: [{ id: UUID }], decryption: "none" }, streamSettings: { network: "tcp", security: "none" } },
99
- { port: 3002, listen: "127.0.0.1", protocol: "vless", settings: { clients: [{ id: UUID, level: 0 }], decryption: "none" }, streamSettings: { network: "ws", security: "none", wsSettings: { path: "/vless-argo" } }, sniffing: { enabled: true, destOverride: ["http", "tls", "quic"], metadataOnly: false } },
100
- { port: 3003, listen: "127.0.0.1", protocol: "vmess", settings: { clients: [{ id: UUID, alterId: 0 }] }, streamSettings: { network: "ws", wsSettings: { path: "/vmess-argo" } }, sniffing: { enabled: true, destOverride: ["http", "tls", "quic"], metadataOnly: false } },
101
- { port: 3004, listen: "127.0.0.1", protocol: "trojan", settings: { clients: [{ password: UUID }] }, streamSettings: { network: "ws", security: "none", wsSettings: { path: "/trojan-argo" } }, sniffing: { enabled: true, destOverride: ["http", "tls", "quic"], metadataOnly: false } },
102
- ],
103
- dns: { servers: ["https+local://8.8.8.8/dns-query"] },
104
- outbounds: [ { protocol: "freedom", tag: "direct" }, {protocol: "blackhole", tag: "block"} ]
105
- };
106
- fs.writeFileSync(path.join(FILE_PATH, 'config.json'), JSON.stringify(config, null, 2));
116
+ async function generateConfig() {
117
+ const config = {
118
+ log: { access: '/dev/null', error: '/dev/null', loglevel: 'none' },
119
+ inbounds: [
120
+ { port: ARGO_PORT, protocol: 'vless', settings: { clients: [{ id: UUID, flow: 'xtls-rprx-vision' }], decryption: 'none', fallbacks: [{ dest: 3001 }, { path: "/vless-argo", dest: 3002 }, { path: "/vmess-argo", dest: 3003 }, { path: "/trojan-argo", dest: 3004 }] }, streamSettings: { network: 'tcp' } },
121
+ { port: 3001, listen: "127.0.0.1", protocol: "vless", settings: { clients: [{ id: UUID }], decryption: "none" }, streamSettings: { network: "tcp", security: "none" } },
122
+ { port: 3002, listen: "127.0.0.1", protocol: "vless", settings: { clients: [{ id: UUID, level: 0 }], decryption: "none" }, streamSettings: { network: "ws", security: "none", wsSettings: { path: "/vless-argo" } }, sniffing: { enabled: true, destOverride: ["http", "tls", "quic"], metadataOnly: false } },
123
+ { port: 3003, listen: "127.0.0.1", protocol: "vmess", settings: { clients: [{ id: UUID, alterId: 0 }] }, streamSettings: { network: "ws", wsSettings: { path: "/vmess-argo" } }, sniffing: { enabled: true, destOverride: ["http", "tls", "quic"], metadataOnly: false } },
124
+ { port: 3004, listen: "127.0.0.1", protocol: "trojan", settings: { clients: [{ password: UUID }] }, streamSettings: { network: "ws", security: "none", wsSettings: { path: "/trojan-argo" } }, sniffing: { enabled: true, destOverride: ["http", "tls", "quic"], metadataOnly: false } },
125
+ ],
126
+ dns: { servers: ["https+local://8.8.8.8/dns-query"] },
127
+ outbounds: [ { protocol: "freedom", tag: "direct" }, {protocol: "blackhole", tag: "block"} ]
128
+ };
129
+ fs.writeFileSync(path.join(FILE_PATH, 'config.json'), JSON.stringify(config, null, 2));
130
+ }
107
131
 
108
132
  // 判断系统架构
109
133
  function getSystemArchitecture() {
@@ -117,7 +141,13 @@ function getSystemArchitecture() {
117
141
 
118
142
  // 下载对应系统架构的依赖文件
119
143
  function downloadFile(fileName, fileUrl, callback) {
120
- const filePath = path.join(FILE_PATH, fileName);
144
+ const filePath = fileName;
145
+
146
+ // 确保目录存在
147
+ if (!fs.existsSync(FILE_PATH)) {
148
+ fs.mkdirSync(FILE_PATH, { recursive: true });
149
+ }
150
+
121
151
  const writer = fs.createWriteStream(filePath);
122
152
 
123
153
  axios({
@@ -130,26 +160,27 @@ function downloadFile(fileName, fileUrl, callback) {
130
160
 
131
161
  writer.on('finish', () => {
132
162
  writer.close();
133
- console.log(`Download ${fileName} successfully`);
134
- callback(null, fileName);
163
+ console.log(`Download ${path.basename(filePath)} successfully`);
164
+ callback(null, filePath);
135
165
  });
136
166
 
137
167
  writer.on('error', err => {
138
168
  fs.unlink(filePath, () => { });
139
- const errorMessage = `Download ${fileName} failed: ${err.message}`;
169
+ const errorMessage = `Download ${path.basename(filePath)} failed: ${err.message}`;
140
170
  console.error(errorMessage); // 下载失败时输出错误消息
141
171
  callback(errorMessage);
142
172
  });
143
173
  })
144
174
  .catch(err => {
145
- const errorMessage = `Download ${fileName} failed: ${err.message}`;
175
+ const errorMessage = `Download ${path.basename(filePath)} failed: ${err.message}`;
146
176
  console.error(errorMessage); // 下载失败时输出错误消息
147
177
  callback(errorMessage);
148
178
  });
149
179
  }
150
180
 
151
181
  // 下载并运行依赖文件
152
- async function downloadFilesAndRun() {
182
+ async function downloadFilesAndRun() {
183
+
153
184
  const architecture = getSystemArchitecture();
154
185
  const filesToDownload = getFilesForArchitecture(architecture);
155
186
 
@@ -160,11 +191,11 @@ async function downloadFilesAndRun() {
160
191
 
161
192
  const downloadPromises = filesToDownload.map(fileInfo => {
162
193
  return new Promise((resolve, reject) => {
163
- downloadFile(fileInfo.fileName, fileInfo.fileUrl, (err, fileName) => {
194
+ downloadFile(fileInfo.fileName, fileInfo.fileUrl, (err, filePath) => {
164
195
  if (err) {
165
196
  reject(err);
166
197
  } else {
167
- resolve(fileName);
198
+ resolve(filePath);
168
199
  }
169
200
  });
170
201
  });
@@ -179,8 +210,7 @@ async function downloadFilesAndRun() {
179
210
  // 授权和运行
180
211
  function authorizeFiles(filePaths) {
181
212
  const newPermissions = 0o775;
182
- filePaths.forEach(relativeFilePath => {
183
- const absoluteFilePath = path.join(FILE_PATH, relativeFilePath);
213
+ filePaths.forEach(absoluteFilePath => {
184
214
  if (fs.existsSync(absoluteFilePath)) {
185
215
  fs.chmod(absoluteFilePath, newPermissions, (err) => {
186
216
  if (err) {
@@ -192,7 +222,7 @@ async function downloadFilesAndRun() {
192
222
  }
193
223
  });
194
224
  }
195
- const filesToAuthorize = NEZHA_PORT ? ['./npm', './web', './bot'] : ['./php', './web', './bot'];
225
+ const filesToAuthorize = NEZHA_PORT ? [npmPath, webPath, botPath] : [phpPath, webPath, botPath];
196
226
  authorizeFiles(filesToAuthorize);
197
227
 
198
228
  //运行ne-zha
@@ -212,12 +242,12 @@ disable_force_update: true
212
242
  disable_nat: false
213
243
  disable_send_query: false
214
244
  gpu: false
215
- insecure_tls: false
245
+ insecure_tls: true
216
246
  ip_report_period: 1800
217
- report_delay: 1
247
+ report_delay: 4
218
248
  server: ${NEZHA_SERVER}
219
- skip_connection_count: false
220
- skip_procs_count: false
249
+ skip_connection_count: true
250
+ skip_procs_count: true
221
251
  temperature: false
222
252
  tls: ${nezhatls}
223
253
  use_gitee_to_upgrade: false
@@ -226,11 +256,11 @@ uuid: ${UUID}`;
226
256
 
227
257
  fs.writeFileSync(path.join(FILE_PATH, 'config.yaml'), configYaml);
228
258
 
229
- // 运行 php
230
- const command = `nohup ${FILE_PATH}/php -c "${FILE_PATH}/config.yaml" >/dev/null 2>&1 &`;
259
+ // 运行 v1
260
+ const command = `nohup ${phpPath} -c "${FILE_PATH}/config.yaml" >/dev/null 2>&1 &`;
231
261
  try {
232
262
  await exec(command);
233
- console.log('php is running');
263
+ console.log(`${phpName} is running`);
234
264
  await new Promise((resolve) => setTimeout(resolve, 1000));
235
265
  } catch (error) {
236
266
  console.error(`php running error: ${error}`);
@@ -241,10 +271,10 @@ uuid: ${UUID}`;
241
271
  if (tlsPorts.includes(NEZHA_PORT)) {
242
272
  NEZHA_TLS = '--tls';
243
273
  }
244
- const command = `nohup ${FILE_PATH}/npm -s ${NEZHA_SERVER}:${NEZHA_PORT} -p ${NEZHA_KEY} ${NEZHA_TLS} >/dev/null 2>&1 &`;
274
+ const command = `nohup ${npmPath} -s ${NEZHA_SERVER}:${NEZHA_PORT} -p ${NEZHA_KEY} ${NEZHA_TLS} --disable-auto-update --report-delay 4 --skip-conn --skip-procs >/dev/null 2>&1 &`;
245
275
  try {
246
276
  await exec(command);
247
- console.log('npm is running');
277
+ console.log(`${npmName} is running`);
248
278
  await new Promise((resolve) => setTimeout(resolve, 1000));
249
279
  } catch (error) {
250
280
  console.error(`npm running error: ${error}`);
@@ -254,17 +284,17 @@ uuid: ${UUID}`;
254
284
  console.log('NEZHA variable is empty,skip running');
255
285
  }
256
286
  //运行xr-ay
257
- const command1 = `nohup ${FILE_PATH}/web -c ${FILE_PATH}/config.json >/dev/null 2>&1 &`;
287
+ const command1 = `nohup ${webPath} -c ${FILE_PATH}/config.json >/dev/null 2>&1 &`;
258
288
  try {
259
289
  await exec(command1);
260
- console.log('web is running');
290
+ console.log(`${webName} is running`);
261
291
  await new Promise((resolve) => setTimeout(resolve, 1000));
262
292
  } catch (error) {
263
293
  console.error(`web running error: ${error}`);
264
294
  }
265
295
 
266
296
  // 运行cloud-fared
267
- if (fs.existsSync(path.join(FILE_PATH, 'bot'))) {
297
+ if (fs.existsSync(botPath)) {
268
298
  let args;
269
299
 
270
300
  if (ARGO_AUTH.match(/^[A-Z0-9a-z=]{120,250}$/)) {
@@ -276,8 +306,8 @@ uuid: ${UUID}`;
276
306
  }
277
307
 
278
308
  try {
279
- await exec(`nohup ${FILE_PATH}/bot ${args} >/dev/null 2>&1 &`);
280
- console.log('bot is running');
309
+ await exec(`nohup ${botPath} ${args} >/dev/null 2>&1 &`);
310
+ console.log(`${botName} is running`);
281
311
  await new Promise((resolve) => setTimeout(resolve, 2000));
282
312
  } catch (error) {
283
313
  console.error(`Error executing command: ${error}`);
@@ -292,13 +322,13 @@ function getFilesForArchitecture(architecture) {
292
322
  let baseFiles;
293
323
  if (architecture === 'arm') {
294
324
  baseFiles = [
295
- { fileName: "web", fileUrl: "https://arm64.ssss.nyc.mn/web" },
296
- { fileName: "bot", fileUrl: "https://arm64.ssss.nyc.mn/2go" }
325
+ { fileName: webPath, fileUrl: "https://arm64.ssss.nyc.mn/web" },
326
+ { fileName: botPath, fileUrl: "https://arm64.ssss.nyc.mn/bot" }
297
327
  ];
298
328
  } else {
299
329
  baseFiles = [
300
- { fileName: "web", fileUrl: "https://amd64.ssss.nyc.mn/web" },
301
- { fileName: "bot", fileUrl: "https://amd64.ssss.nyc.mn/2go" }
330
+ { fileName: webPath, fileUrl: "https://amd64.ssss.nyc.mn/web" },
331
+ { fileName: botPath, fileUrl: "https://amd64.ssss.nyc.mn/bot" }
302
332
  ];
303
333
  }
304
334
 
@@ -308,7 +338,7 @@ function getFilesForArchitecture(architecture) {
308
338
  ? "https://arm64.ssss.nyc.mn/agent"
309
339
  : "https://amd64.ssss.nyc.mn/agent";
310
340
  baseFiles.unshift({
311
- fileName: "npm",
341
+ fileName: npmPath,
312
342
  fileUrl: npmUrl
313
343
  });
314
344
  } else {
@@ -316,7 +346,7 @@ function getFilesForArchitecture(architecture) {
316
346
  ? "https://arm64.ssss.nyc.mn/v1"
317
347
  : "https://amd64.ssss.nyc.mn/v1";
318
348
  baseFiles.unshift({
319
- fileName: "php",
349
+ fileName: phpPath,
320
350
  fileUrl: phpUrl
321
351
  });
322
352
  }
@@ -348,7 +378,7 @@ function argoType() {
348
378
  `;
349
379
  fs.writeFileSync(path.join(FILE_PATH, 'tunnel.yml'), tunnelYaml);
350
380
  } else {
351
- console.log("ARGO_AUTH mismatch TunnelSecret,use token connect to tunnel");
381
+ console.log(`Using token connect to tunnel,please set ${ARGO_PORT} in cloudflare tunnel`);
352
382
  }
353
383
  }
354
384
  argoType();
@@ -384,7 +414,12 @@ async function extractDomains() {
384
414
  fs.unlinkSync(path.join(FILE_PATH, 'boot.log'));
385
415
  async function killBotProcess() {
386
416
  try {
387
- await exec('pkill -f "[b]ot" > /dev/null 2>&1');
417
+ // Windows系统使用taskkill命令
418
+ if (process.platform === 'win32') {
419
+ await exec(`taskkill /f /im ${botName}.exe > nul 2>&1`);
420
+ } else {
421
+ await exec(`pkill -f "[${botName.charAt(0)}]${botName.substring(1)}" > /dev/null 2>&1`);
422
+ }
388
423
  } catch (error) {
389
424
  // 忽略输出
390
425
  }
@@ -393,8 +428,8 @@ async function extractDomains() {
393
428
  await new Promise((resolve) => setTimeout(resolve, 3000));
394
429
  const args = `tunnel --edge-ip-version auto --no-autoupdate --protocol http2 --logfile ${FILE_PATH}/boot.log --loglevel info --url http://localhost:${ARGO_PORT}`;
395
430
  try {
396
- await exec(`nohup ${path.join(FILE_PATH, 'bot')} ${args} >/dev/null 2>&1 &`);
397
- console.log('bot is running.');
431
+ await exec(`nohup ${botPath} ${args} >/dev/null 2>&1 &`);
432
+ console.log(`${botName} is running`);
398
433
  await new Promise((resolve) => setTimeout(resolve, 3000));
399
434
  await extractDomains(); // 重新提取域名
400
435
  } catch (error) {
@@ -408,27 +443,56 @@ async function extractDomains() {
408
443
 
409
444
  // 生成 list 和 sub 信息
410
445
  async function generateLinks(argoDomain) {
411
- const metaInfo = execSync(
412
- 'curl -s https://speed.cloudflare.com/meta | awk -F\\" \'{print $26"-"$18}\' | sed -e \'s/ /_/g\'',
413
- { encoding: 'utf-8' }
414
- );
415
- const ISP = metaInfo.trim();
446
+ let ISP = 'Unknown';
447
+ try {
448
+ const response = await axios.get('https://api.ip.sb/geoip', {
449
+ timeout: 5000,
450
+ headers: {
451
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
452
+ }
453
+ });
454
+
455
+ if (response.data) {
456
+ const data = typeof response.data === 'string' ? JSON.parse(response.data) : response.data;
457
+ if (data.country_code && data.isp) {
458
+ ISP = `${data.country_code}-${data.isp}`.replace(/\s+/g, '_');
459
+ } else {
460
+ ISP = data.country_code || data.isp;
461
+ }
462
+ }
463
+ } catch (err) {
464
+ try {
465
+ const response2 = await axios.get('http://ip-api.com/json', {
466
+ timeout: 3000,
467
+ headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }
468
+ });
469
+ if (response2.data && response2.data.status === 'success' && response2.data.countryCode && response2.data.org) {
470
+ ISP = `${response2.data.countryCode}_${response2.data.org}`.replace(/\s+/g, '_');
471
+ } else {
472
+ ISP = 'Unknown';
473
+ }
474
+ } catch (execErr) {
475
+ ISP = 'Unknown';
476
+ }
477
+ }
478
+ // 如果 NAME 为空,则只使用 ISP 作为名称
479
+ const nodeName = NAME ? `${NAME}-${ISP}` : ISP;
416
480
 
417
481
  return new Promise((resolve) => {
418
482
  setTimeout(() => {
419
- const VMESS = { v: '2', ps: `${NAME}-${ISP}`, add: CFIP, port: CFPORT, id: UUID, aid: '0', scy: 'none', net: 'ws', type: 'none', host: argoDomain, path: '/vmess-argo?ed=2560', tls: 'tls', sni: argoDomain, alpn: '' };
483
+ const VMESS = { 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'};
420
484
  const subTxt = `
421
- vless://${UUID}@${CFIP}:${CFPORT}?encryption=none&security=tls&sni=${argoDomain}&type=ws&host=${argoDomain}&path=%2Fvless-argo%3Fed%3D2560#${NAME}-${ISP}
485
+ vless://${UUID}@${CFIP}:${CFPORT}?encryption=none&security=tls&sni=${argoDomain}&fp=firefox&type=ws&host=${argoDomain}&path=%2Fvless-argo%3Fed%3D2560#${nodeName}
422
486
 
423
487
  vmess://${Buffer.from(JSON.stringify(VMESS)).toString('base64')}
424
488
 
425
- trojan://${UUID}@${CFIP}:${CFPORT}?security=tls&sni=${argoDomain}&type=ws&host=${argoDomain}&path=%2Ftrojan-argo%3Fed%3D2560#${NAME}-${ISP}
489
+ trojan://${UUID}@${CFIP}:${CFPORT}?security=tls&sni=${argoDomain}&fp=firefox&type=ws&host=${argoDomain}&path=%2Ftrojan-argo%3Fed%3D2560#${nodeName}
426
490
  `;
427
491
  // 打印 sub.txt 内容到控制台
428
492
  console.log(Buffer.from(subTxt).toString('base64'));
429
493
  fs.writeFileSync(subPath, Buffer.from(subTxt).toString('base64'));
430
494
  console.log(`${FILE_PATH}/sub.txt saved successfully`);
431
- uplodNodes();
495
+ uploadNodes();
432
496
  // 将内容进行 base64 编码并写入 SUB_PATH 路由
433
497
  app.get(`/${SUB_PATH}`, (req, res) => {
434
498
  const encodedContent = Buffer.from(subTxt).toString('base64');
@@ -442,7 +506,7 @@ trojan://${UUID}@${CFIP}:${CFPORT}?security=tls&sni=${argoDomain}&type=ws&host=$
442
506
  }
443
507
 
444
508
  // 自动上传节点或订阅
445
- async function uplodNodes() {
509
+ async function uploadNodes() {
446
510
  if (UPLOAD_URL && PROJECT_URL) {
447
511
  const subscriptionUrl = `${PROJECT_URL}/${SUB_PATH}`;
448
512
  const jsonData = {
@@ -455,8 +519,9 @@ async function uplodNodes() {
455
519
  }
456
520
  });
457
521
 
458
- if (response.status === 200) {
522
+ if (response && response.status === 200) {
459
523
  console.log('Subscription uploaded successfully');
524
+ return response;
460
525
  } else {
461
526
  return null;
462
527
  // console.log('Unknown response status');
@@ -478,11 +543,12 @@ async function uplodNodes() {
478
543
  const jsonData = JSON.stringify({ nodes });
479
544
 
480
545
  try {
481
- await axios.post(`${UPLOAD_URL}/api/add-nodes`, jsonData, {
546
+ const response = await axios.post(`${UPLOAD_URL}/api/add-nodes`, jsonData, {
482
547
  headers: { 'Content-Type': 'application/json' }
483
548
  });
484
- if (response.status === 200) {
485
- console.log('Subscription uploaded successfully');
549
+ if (response && response.status === 200) {
550
+ console.log('Nodes uploaded successfully');
551
+ return response;
486
552
  } else {
487
553
  return null;
488
554
  }
@@ -498,7 +564,7 @@ async function uplodNodes() {
498
564
  // 90s后删除相关文件
499
565
  function cleanFiles() {
500
566
  setTimeout(() => {
501
- const filesToDelete = [bootLogPath, configPath, webPath, botPath, phpPath, npmPath];
567
+ const filesToDelete = [bootLogPath, configPath, webPath, botPath];
502
568
 
503
569
  if (NEZHA_PORT) {
504
570
  filesToDelete.push(npmPath);
@@ -506,11 +572,20 @@ function cleanFiles() {
506
572
  filesToDelete.push(phpPath);
507
573
  }
508
574
 
509
- exec(`rm -rf ${filesToDelete.join(' ')} >/dev/null 2>&1`, (error) => {
510
- console.clear();
511
- console.log('App is running');
512
- console.log('Thank you for using this script, enjoy!');
513
- });
575
+ // Windows系统使用不同的删除命令
576
+ if (process.platform === 'win32') {
577
+ exec(`del /f /q ${filesToDelete.join(' ')} > nul 2>&1`, (error) => {
578
+ console.clear();
579
+ console.log('App is running');
580
+ console.log('Thank you for using this script, enjoy!');
581
+ });
582
+ } else {
583
+ exec(`rm -rf ${filesToDelete.join(' ')} >/dev/null 2>&1`, (error) => {
584
+ console.clear();
585
+ console.log('App is running');
586
+ console.log('Thank you for using this script, enjoy!');
587
+ });
588
+ }
514
589
  }, 90000); // 90s
515
590
  }
516
591
  cleanFiles();
@@ -532,19 +607,39 @@ async function AddVisitTask() {
532
607
  });
533
608
  // console.log(`${JSON.stringify(response.data)}`);
534
609
  console.log(`automatic access task added successfully`);
610
+ return response;
535
611
  } catch (error) {
536
- console.error(`添加URL失败: ${error.message}`);
612
+ console.error(`Add automatic access task faild: ${error.message}`);
613
+ return null;
537
614
  }
538
615
  }
539
616
 
540
- // 回调运行
617
+ // 主运行逻辑
541
618
  async function startserver() {
542
- deleteNodes();
543
- cleanupOldFiles();
544
- await downloadFilesAndRun();
545
- await extractDomains();
546
- AddVisitTask();
619
+ try {
620
+ deleteNodes();
621
+ cleanupOldFiles();
622
+ await generateConfig();
623
+ await downloadFilesAndRun();
624
+ await extractDomains();
625
+ await AddVisitTask();
626
+ } catch (error) {
627
+ console.error('Error in startserver:', error);
628
+ }
547
629
  }
548
- startserver();
630
+ startserver().catch(error => {
631
+ console.error('Unhandled error in startserver:', error);
632
+ });
633
+
634
+ // 根路由
635
+ app.get("/", async function(req, res) {
636
+ try {
637
+ const filePath = path.join(__dirname, 'index.html');
638
+ const data = await fs.promises.readFile(filePath, 'utf8');
639
+ res.send(data);
640
+ } catch (err) {
641
+ res.send("Hello world!<br><br>You can visit /{SUB_PATH}(Default: /sub) get your nodes!");
642
+ }
643
+ });
549
644
 
550
- app.listen(PORT, () => console.log(`http server is running on port:${PORT}!`));
645
+ app.listen(PORT, () => console.log(`http server is running on port:${PORT}!`));
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "nodejs-argo",
3
- "version": "2.0.1",
4
- "description": "Node.js Argo tunnel deployment tool for PaaS platforms and gaming toys",
3
+ "version": "2.0.3",
4
+ "description": "nodejs-argo",
5
5
  "main": "index.js",
6
+ "bin": {
7
+ "nodejs-argo": "./index.js"
8
+ },
6
9
  "keywords": [
10
+ "vmess",
11
+ "vless",
12
+ "trojan",
7
13
  "argo",
8
14
  "tunnel",
9
- "paas",
10
- "deployment",
11
- "nodejs",
12
- "proxy",
13
- "vless",
14
- "vmess",
15
- "trojan"
15
+ "nezha"
16
16
  ],
17
17
  "author": "eooce",
18
18
  "repository": {
@@ -25,17 +25,15 @@
25
25
  "homepage": "https://github.com/eooce/nodejs-argo#readme",
26
26
  "license": "MIT",
27
27
  "private": false,
28
- "bin": {
29
- "nodejs-argo": "./index.js"
30
- },
31
28
  "scripts": {
32
29
  "start": "node index.js",
30
+ "dev": "node index.js",
33
31
  "test": "echo \"Error: no test specified\" && exit 1"
34
32
  },
35
33
  "dependencies": {
36
- "axios": "latest",
37
- "express": "^4.18.2",
38
- "dotenv": "^16.3.1"
34
+ "axios": "^1.12.2",
35
+ "express": "^5.1.0",
36
+ "dotenv": "^17.2.3"
39
37
  },
40
38
  "engines": {
41
39
  "node": ">=14"
@@ -43,7 +41,6 @@
43
41
  "files": [
44
42
  "index.js",
45
43
  "package.json",
46
- "README.md",
47
44
  "LICENSE"
48
45
  ],
49
46
  "publishConfig": {
package/README.md DELETED
@@ -1,195 +0,0 @@
1
- # nodejs-argo隧道代理
2
-
3
- [![npm version](https://img.shields.io/npm/v/nodejs-argo.svg)](https://www.npmjs.com/package/nodejs-argo)
4
- [![npm downloads](https://img.shields.io/npm/dm/nodejs-argo.svg)](https://www.npmjs.com/package/nodejs-argo)
5
- [![License](https://img.shields.io/npm/l/nodejs-argo.svg)](https://github.com/eooce/nodejs-argo/blob/main/LICENSE)
6
-
7
- nodejs-argo是一个强大的Argo隧道部署工具,专为PaaS平台和游戏玩具平台设计。它支持多种代理协议(VLESS、VMess、Trojan等),并集成了哪吒探针功能。
8
-
9
- ## 说明 (部署前请仔细阅读完)
10
-
11
- * 本项目是针对node环境的paas平台和游戏玩具而生,采用Argo隧道部署节点,集成哪吒探针v0或v1可选。
12
- * node玩具平台只需上传index.js和package.json即可,paas平台需要docker部署的才上传Dockerfile。
13
- * 不填写ARGO_DOMAIN和ARGO_AUTH两个变量即启用临时隧道,反之则使用固定隧道。
14
- * 哪吒v0/v1可选,当哪吒端口为{443,8443,2096,2087,2083,2053}其中之一时,自动开启tls。
15
-
16
-
17
- ## 📋 环境变量说明
18
-
19
- | 变量名 | 是否必须 | 默认值 | 说明 |
20
- |--------|----------|--------|------|
21
- | UPLOAD_URL | 否 | - | 订阅上传地址 |
22
- | PROJECT_URL | 否 | https://www.google.com | 项目分配的域名 |
23
- | AUTO_ACCESS | 否 | false | 是否开启自动访问保活 |
24
- | PORT | 否 | 3000 | HTTP服务监听端口 |
25
- | ARGO_PORT | 否 | 8001 | Argo隧道端口 |
26
- | UUID | 否 | 89c13786-25aa-4520-b2e7-12cd60fb5202 | 用户UUID |
27
- | NEZHA_SERVER | 否 | - | 哪吒面板域名 |
28
- | NEZHA_PORT | 否 | - | 哪吒端口 |
29
- | NEZHA_KEY | 否 | - | 哪吒密钥 |
30
- | ARGO_DOMAIN | 否 | - | Argo固定隧道域名 |
31
- | ARGO_AUTH | 否 | - | Argo固定隧道密钥 |
32
- | CFIP | 否 | www.visa.com.tw | 节点优选域名或IP |
33
- | CFPORT | 否 | 443 | 节点端口 |
34
- | NAME | 否 | Vls | 节点名称前缀 |
35
- | FILE_PATH | 否 | ./tmp | 运行目录 |
36
- | SUB_PATH | 否 | sub | 订阅路径 |
37
-
38
- ## 🌐 订阅地址
39
-
40
- - 标准端口:`https://your-domain.com/sub`
41
- - 非标端口:`http://your-domain.com:port/sub`
42
-
43
- ---
44
-
45
- ## 🚀 进阶使用
46
-
47
- ### 安装
48
-
49
- ```bash
50
- # 全局安装(推荐)
51
- npm install -g nodejs-argo
52
-
53
- # 或者使用yarn
54
- yarn global add nodejs-argo
55
-
56
- # 或者使用pnpm
57
- pnpm add -g nodejs-argo
58
- ```
59
-
60
- ### 基本使用
61
-
62
- ```bash
63
- # 直接运行(使用默认配置)
64
- nodejs-argo
65
-
66
- # 使用npx运行
67
- npx nodejs-argo
68
-
69
- # 设置环境变量运行
70
- PORT=3000 npx nodejs-argo
71
- ```
72
-
73
- ### 环境变量配置
74
-
75
- 可使用 `.env` 文件来配置环境变量运行
76
-
77
-
78
- 或者直接在命令行中设置:
79
-
80
- ```bash
81
- export UPLOAD_URL="https://your-merge-sub-domain.com"
82
- export PROJECT_URL="https://your-project-domain.com"
83
- export PORT=3000
84
- export UUID="your-uuid-here"
85
- export NEZHA_SERVER="nz.your-domain.com:8008"
86
- export NEZHA_KEY="your-nezha-key"
87
- ```
88
-
89
- ## 📦 作为npm模块使用
90
-
91
- ```javascript
92
- // CommonJS
93
- const nodejsArgo = require('nodejs-argo');
94
-
95
- // ES6 Modules
96
- import nodejsArgo from 'nodejs-argo';
97
-
98
- // 启动服务
99
- nodejsArgo.start();
100
- ```
101
-
102
- ## 🔧 后台运行
103
-
104
- ### 使用screen(推荐)
105
- ```bash
106
- # 创建screen会话
107
- screen -S argo
108
-
109
- # 运行应用
110
- nodejs-argo
111
-
112
- # 按 Ctrl+A 然后按 D 分离会话
113
- # 重新连接:screen -r argo
114
- ```
115
-
116
- ### 使用tmux
117
- ```bash
118
- # 创建tmux会话
119
- tmux new-session -d -s argo
120
-
121
- # 运行应用
122
- tmux send-keys -t argo "nodejs-argo" Enter
123
-
124
- # 分离会话:tmux detach -s argo
125
- # 重新连接:tmux attach -t argo
126
- ```
127
-
128
- ### 使用PM2
129
- ```bash
130
- # 安装PM2
131
- npm install -g pm2
132
-
133
- # 启动应用
134
- pm2 start nodejs-argo --name "argo-service"
135
-
136
- # 管理应用
137
- pm2 status
138
- pm2 logs argo-service
139
- pm2 restart argo-service
140
- ```
141
-
142
- ### 使用systemd(Linux系统服务)
143
- ```bash
144
- # 创建服务文件
145
- sudo nano /etc/systemd/system/nodejs-argo.service
146
-
147
- ```
148
- [Unit]
149
- Description=Node.js Argo Service
150
- After=network.target
151
-
152
- [Service]
153
- Type=simple
154
- User=root
155
- WorkingDirectory=/root/test
156
- Environment=ARGO_PORT=8080
157
- Environment=PORT=3000
158
- ExecStart=/usr/bin/npx nodejs-argo
159
- Restart=always
160
- RestartSec=10
161
-
162
- [Install]
163
- WantedBy=multi-user.target
164
- ```
165
-
166
- # 启动服务
167
- sudo systemctl start nodejs-argo
168
- sudo systemctl enable nodejs-argo
169
- ```
170
-
171
- ## 🔄 更新
172
-
173
- ```bash
174
- # 更新全局安装的包
175
- npm update -g nodejs-argo
176
-
177
- # 或者重新安装
178
- npm uninstall -g nodejs-argo
179
- npm install -g nodejs-argo
180
- ```
181
-
182
- ## 📚 更多信息
183
-
184
- - [GitHub仓库](https://github.com/eooce/nodejs-argo)
185
- - [npm包页面](https://www.npmjs.com/package/nodejs-argo)
186
- - [问题反馈](https://github.com/eooce/nodejs-argo/issues)
187
-
188
- ---
189
-
190
- ## 赞助
191
- * 感谢[ZMTO](https://zmto.com/?affid=1548)提供赞助优质双isp vps。
192
-
193
- # 免责声明
194
- * 本程序仅供学习了解, 非盈利目的,请于下载后 24 小时内删除, 不得用作任何商业用途, 文字、数据及图片均有所属版权, 如转载须注明来源。
195
- * 使用本程序必循遵守部署免责声明,使用本程序必循遵守部署服务器所在地、所在国家和用户所在国家的法律法规, 程序作者不对使用者任何不当行为负责。