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.
- package/LICENSE +1 -1
- package/index.js +196 -101
- package/package.json +13 -16
- package/README.md +0 -195
package/LICENSE
CHANGED
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.
|
|
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 || '
|
|
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://
|
|
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 || '
|
|
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 || '
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
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 =
|
|
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 ${
|
|
134
|
-
callback(null,
|
|
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 ${
|
|
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 ${
|
|
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,
|
|
194
|
+
downloadFile(fileInfo.fileName, fileInfo.fileUrl, (err, filePath) => {
|
|
164
195
|
if (err) {
|
|
165
196
|
reject(err);
|
|
166
197
|
} else {
|
|
167
|
-
resolve(
|
|
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(
|
|
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 ? [
|
|
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:
|
|
245
|
+
insecure_tls: true
|
|
216
246
|
ip_report_period: 1800
|
|
217
|
-
report_delay:
|
|
247
|
+
report_delay: 4
|
|
218
248
|
server: ${NEZHA_SERVER}
|
|
219
|
-
skip_connection_count:
|
|
220
|
-
skip_procs_count:
|
|
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
|
-
// 运行
|
|
230
|
-
const command = `nohup ${
|
|
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(
|
|
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 ${
|
|
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(
|
|
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 ${
|
|
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(
|
|
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(
|
|
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 ${
|
|
280
|
-
console.log(
|
|
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:
|
|
296
|
-
{ fileName:
|
|
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:
|
|
301
|
-
{ fileName:
|
|
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:
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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 ${
|
|
397
|
-
console.log(
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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: `${
|
|
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#${
|
|
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#${
|
|
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
|
-
|
|
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
|
|
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('
|
|
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
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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(
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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.
|
|
4
|
-
"description": "
|
|
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
|
-
"
|
|
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": "
|
|
37
|
-
"express": "^
|
|
38
|
-
"dotenv": "^
|
|
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
|
-
[](https://www.npmjs.com/package/nodejs-argo)
|
|
4
|
-
[](https://www.npmjs.com/package/nodejs-argo)
|
|
5
|
-
[](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
|
-
* 使用本程序必循遵守部署免责声明,使用本程序必循遵守部署服务器所在地、所在国家和用户所在国家的法律法规, 程序作者不对使用者任何不当行为负责。
|