├── Dockerfile ├── LICENSE ├── README.md ├── index.js └── package.json /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:slim 2 | 3 | WORKDIR /app 4 | 5 | COPY . . 6 | 7 | EXPOSE 3000 8 | 9 | RUN apt update -y &&\ 10 | chmod +x index.js &&\ 11 | npm install 12 | 13 | CMD ["node", "index.js"] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 eooce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 赞助 2 | * 感谢[VTEXS](https://console.vtexs.com/?affid=1548)提供赞助优质双isp vps。 3 | 4 | # 说明 (部署前请仔细阅读完) 5 | * 本项目是针对node环境的paas平台和游戏玩具而生,采用Argo隧道部署节点,集成哪吒探针v0或v1可选。 6 | * node玩具平台只需上传index.js和package.json即可,paas平台需要docker部署的才上传Dockerfile。 7 | * 如需是链接github部署,请先删除README.md说明文件。 8 | * 不填写ARGO_DOMAIN和ARGO_AUTH两个变量即启用临时隧道,反之则使用固定隧道。 9 | * 若遇到已获取到临时隧道但节点不通,说明域名被墙,重启即可 10 | * 无需设置NEZHA_TLS,当哪吒端口为{443,8443,2096,2087,2083,2053}其中之一时,自动开启--tls。 11 | * 右边的Releases中已适配FreeBSD,自行下载,类似的平台Serv00,CT8 12 | 13 | * PaaS 平台设置的环境变量,index.js中的1至12行中设置 14 | | 变量名 | 是否必须 | 默认值 | 备注 | 15 | | ------------ | ------ | ------ | ------ | 16 | | UPLOAD_URL | 否 | 填写部署Merge-sub项目后的首页地址 |订阅上传地址,例如:https://merge.serv00.net| 17 | | PROJECT_URL | 否 | https://www.google.com |项目分配的域名| 18 | | AUTO_ACCESS | 否 | flase |flase关闭自动访问保活,true开启,需同时填写PROJECT_URL变量| 19 | | PORT | 否 | 3000 |http服务监听端口,也是订阅端口 | 20 | | ARGO_PORT | 否 | 8001 |argo隧道端口,固定隧道token需和cloudflare后台设置的一致| 21 | | UUID | 否 | 89c13786-25aa-4520-b2e7-12cd60fb5202|UUID,使用哪吒v1在不同的平台部署需要修改| 22 | | NEZHA_SERVER | 否 | | 哪吒面板域名,v1:nz.aaa.com:8008 v0: nz.aaa.com | 23 | | NEZHA_PORT | 否 | | 哪吒v1没有此项,哪吒v0端口为{443,8443,2096,2087,2083,2053}其中之一时,开启tls| 24 | | NEZHA_KEY | 否 | | 哪吒v1 或v0 密钥 | 25 | | ARGO_DOMAIN | 否 | | argo固定隧道域名 | 26 | | ARGO_AUTH | 否 | | argo固定隧道json或token | 27 | | CFIP | 否 |skk.moe | 节点优选域名或ip | 28 | | CFPORT | 否 | 443 |节点端口 | 29 | | NAME | 否 | Vls | 节点名称前缀,例如:Koyeb Fly | 30 | | FILE_PATH | 否 | tmp | 运行目录,节点存放路径 | 31 | | SUB_PATH | 否 | sub | 节点订阅路径 | 32 | 33 | # 节点输出 34 | * 输出sub.txt节点文件,默认存放路径为tmp 35 | * 订阅:分配的域名/${SUB_PATH};例如https://www.google.com/${SUB_PATH} 36 | * 非标端口订阅(游戏类):分配的域名:端口/${SUB_PATH},前缀是http,例如http://www.google.com:1234/${SUB_PATH} 37 | 38 | # 其他 39 | * 本项目已添加自动访问保活功能,仅支持不重启停机的平台,需在第2行中添加项目分配的域名。建议配合外部自动访问保活,保活项目地址:https://github.com/eooce/Auto-keep-online 40 | * Replit,Codesanbox,Glitch,Render,koyeb,Fly,Northfrank,back4app,Alwaysdate,Zeabur,Doprax及数十个游戏玩具平台均已测试ok。 41 | * Render及其他比较严格的容器平台,请使用docker image部署,Dockerfile地址:https://github.com/eooce/nodejs-argo-image 42 | 43 | # vps一键部署命令 44 | * 3000端口改为可用的的开放端口,母鸡可忽略,对应哪吒变量也可更改,不需要哪吒可忽略 45 | * 其他变量可自行添加在哪吒变量后面,参考上方变量表,例如固定隧道等,每个变量之间有一个空格 46 | * 订阅:ip:端口/sub 47 | ``` 48 | apt-get update && apt-get install -y curl nodejs npm screen && curl -O https://raw.githubusercontent.com/eooce/nodejs-argo/main/index.js && curl -O https://raw.githubusercontent.com/eooce/nodejs-argo/main/package.json && npm install && chmod +x index.js && NAME=Vls PORT=3000 NEZHA_SERVER=nz.abcd.cn NEZHA_PORT=5555 NEZHA_KEY=12345678 screen node index.js 49 | ``` 50 | 51 | 52 | # 免责声明 53 | * 本程序仅供学习了解, 非盈利目的,请于下载后 24 小时内删除, 不得用作任何商业用途, 文字、数据及图片均有所属版权, 如转载须注明来源。 54 | * 使用本程序必循遵守部署免责声明,使用本程序必循遵守部署服务器所在地、所在国家和用户所在国家的法律法规, 程序作者不对使用者任何不当行为负责。 55 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const app = express(); 3 | const axios = require("axios"); 4 | const os = require('os'); 5 | const fs = require("fs"); 6 | const path = require("path"); 7 | const { promisify } = require('util'); 8 | const exec = promisify(require('child_process').exec); 9 | const { execSync } = require('child_process'); // 只填写UPLOAD_URL将上传节点,同时填写UPLOAD_URL和PROJECT_URL将上传订阅 10 | const UPLOAD_URL = process.env.UPLOAD_URL || ''; // 节点或订阅自动上传地址,需填写部署Merge-sub项目后的首页地址,例如:https://merge.serv00.net 11 | const PROJECT_URL = process.env.PROJECT_URL || ''; // 需要上传订阅或保活时需填写项目分配的url,例如:https://google.com 12 | const AUTO_ACCESS = process.env.AUTO_ACCESS || false; // false关闭自动保活,true开启,需同时填写PROJECT_URL变量 13 | const FILE_PATH = process.env.FILE_PATH || './tmp'; // 运行目录,sub节点文件保存目录 14 | const SUB_PATH = process.env.SUB_PATH || 'sub'; // 订阅路径 15 | const PORT = process.env.SERVER_PORT || process.env.PORT || 3000; // http服务订阅端口 16 | const UUID = process.env.UUID || '9afd1229-b893-40c1-84dd-51e7ce204913'; // 使用哪吒v1,在不同的平台运行需修改UUID,否则会覆盖 17 | const NEZHA_SERVER = process.env.NEZHA_SERVER || ''; // 哪吒v1填写形式: nz.abc.com:8008 哪吒v0填写形式:nz.abc.com 18 | const NEZHA_PORT = process.env.NEZHA_PORT || ''; // 使用哪吒v1请留空,哪吒v0需填写 19 | const NEZHA_KEY = process.env.NEZHA_KEY || ''; // 哪吒v1的NZ_CLIENT_SECRET或哪吒v0的agent密钥 20 | const ARGO_DOMAIN = process.env.ARGO_DOMAIN || ''; // 固定隧道域名,留空即启用临时隧道 21 | const ARGO_AUTH = process.env.ARGO_AUTH || ''; // 固定隧道密钥json或token,留空即启用临时隧道,json获取地址:https://fscarmen.cloudflare.now.cc 22 | const ARGO_PORT = process.env.ARGO_PORT || 8001; // 固定隧道端口,使用token需在cloudflare后台设置和这里一致 23 | const CFIP = process.env.CFIP || 'www.visa.com.sg'; // 节点优选域名或优选ip 24 | const CFPORT = process.env.CFPORT || 443; // 节点优选域名或优选ip对应的端口 25 | const NAME = process.env.NAME || 'Vls'; // 节点名称 26 | 27 | //创建运行文件夹 28 | if (!fs.existsSync(FILE_PATH)) { 29 | fs.mkdirSync(FILE_PATH); 30 | console.log(`${FILE_PATH} is created`); 31 | } else { 32 | console.log(`${FILE_PATH} already exists`); 33 | } 34 | 35 | let npmPath = path.join(FILE_PATH, 'npm'); 36 | let phpPath = path.join(FILE_PATH, 'php'); 37 | let webPath = path.join(FILE_PATH, 'web'); 38 | let botPath = path.join(FILE_PATH, 'bot'); 39 | let subPath = path.join(FILE_PATH, 'sub.txt'); 40 | let listPath = path.join(FILE_PATH, 'list.txt'); 41 | let bootLogPath = path.join(FILE_PATH, 'boot.log'); 42 | let configPath = path.join(FILE_PATH, 'config.json'); 43 | 44 | // 如果订阅器上存在历史运行节点则先删除 45 | function deleteNodes() { 46 | try { 47 | if (!UPLOAD_URL) return; 48 | if (!fs.existsSync(subPath)) return; 49 | 50 | let fileContent; 51 | try { 52 | fileContent = fs.readFileSync(subPath, 'utf-8'); 53 | } catch { 54 | return null; 55 | } 56 | 57 | const decoded = Buffer.from(fileContent, 'base64').toString('utf-8'); 58 | const nodes = decoded.split('\n').filter(line => 59 | /(vless|vmess|trojan|hysteria2|tuic):\/\//.test(line) 60 | ); 61 | 62 | if (nodes.length === 0) return; 63 | 64 | return axios.post(`${UPLOAD_URL}/api/delete-nodes`, 65 | JSON.stringify({ nodes }), 66 | { headers: { 'Content-Type': 'application/json' } } 67 | ).catch((error) => { 68 | return null; 69 | }); 70 | } catch (err) { 71 | return null; 72 | } 73 | } 74 | 75 | //清理历史文件 76 | function cleanupOldFiles() { 77 | const pathsToDelete = ['web', 'bot', 'npm', 'php', 'sub.txt', 'boot.log']; 78 | pathsToDelete.forEach(file => { 79 | const filePath = path.join(FILE_PATH, file); 80 | fs.unlink(filePath, () => {}); 81 | }); 82 | } 83 | 84 | // 根路由 85 | app.get("/", function(req, res) { 86 | res.send("Hello world!"); 87 | }); 88 | 89 | // 生成xr-ay配置文件 90 | const config = { 91 | log: { access: '/dev/null', error: '/dev/null', loglevel: 'none' }, 92 | inbounds: [ 93 | { 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' } }, 94 | { port: 3001, listen: "127.0.0.1", protocol: "vless", settings: { clients: [{ id: UUID }], decryption: "none" }, streamSettings: { network: "tcp", security: "none" } }, 95 | { 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 } }, 96 | { 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 } }, 97 | { 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 } }, 98 | ], 99 | dns: { servers: ["https+local://8.8.8.8/dns-query"] }, 100 | outbounds: [ { protocol: "freedom", tag: "direct" }, {protocol: "blackhole", tag: "block"} ] 101 | }; 102 | fs.writeFileSync(path.join(FILE_PATH, 'config.json'), JSON.stringify(config, null, 2)); 103 | 104 | // 判断系统架构 105 | function getSystemArchitecture() { 106 | const arch = os.arch(); 107 | if (arch === 'arm' || arch === 'arm64' || arch === 'aarch64') { 108 | return 'arm'; 109 | } else { 110 | return 'amd'; 111 | } 112 | } 113 | 114 | // 下载对应系统架构的依赖文件 115 | function downloadFile(fileName, fileUrl, callback) { 116 | const filePath = path.join(FILE_PATH, fileName); 117 | const writer = fs.createWriteStream(filePath); 118 | 119 | axios({ 120 | method: 'get', 121 | url: fileUrl, 122 | responseType: 'stream', 123 | }) 124 | .then(response => { 125 | response.data.pipe(writer); 126 | 127 | writer.on('finish', () => { 128 | writer.close(); 129 | console.log(`Download ${fileName} successfully`); 130 | callback(null, fileName); 131 | }); 132 | 133 | writer.on('error', err => { 134 | fs.unlink(filePath, () => { }); 135 | const errorMessage = `Download ${fileName} failed: ${err.message}`; 136 | console.error(errorMessage); // 下载失败时输出错误消息 137 | callback(errorMessage); 138 | }); 139 | }) 140 | .catch(err => { 141 | const errorMessage = `Download ${fileName} failed: ${err.message}`; 142 | console.error(errorMessage); // 下载失败时输出错误消息 143 | callback(errorMessage); 144 | }); 145 | } 146 | 147 | // 下载并运行依赖文件 148 | async function downloadFilesAndRun() { 149 | const architecture = getSystemArchitecture(); 150 | const filesToDownload = getFilesForArchitecture(architecture); 151 | 152 | if (filesToDownload.length === 0) { 153 | console.log(`Can't find a file for the current architecture`); 154 | return; 155 | } 156 | 157 | const downloadPromises = filesToDownload.map(fileInfo => { 158 | return new Promise((resolve, reject) => { 159 | downloadFile(fileInfo.fileName, fileInfo.fileUrl, (err, fileName) => { 160 | if (err) { 161 | reject(err); 162 | } else { 163 | resolve(fileName); 164 | } 165 | }); 166 | }); 167 | }); 168 | 169 | try { 170 | await Promise.all(downloadPromises); 171 | } catch (err) { 172 | console.error('Error downloading files:', err); 173 | return; 174 | } 175 | // 授权和运行 176 | function authorizeFiles(filePaths) { 177 | const newPermissions = 0o775; 178 | filePaths.forEach(relativeFilePath => { 179 | const absoluteFilePath = path.join(FILE_PATH, relativeFilePath); 180 | if (fs.existsSync(absoluteFilePath)) { 181 | fs.chmod(absoluteFilePath, newPermissions, (err) => { 182 | if (err) { 183 | console.error(`Empowerment failed for ${absoluteFilePath}: ${err}`); 184 | } else { 185 | console.log(`Empowerment success for ${absoluteFilePath}: ${newPermissions.toString(8)}`); 186 | } 187 | }); 188 | } 189 | }); 190 | } 191 | const filesToAuthorize = NEZHA_PORT ? ['./npm', './web', './bot'] : ['./php', './web', './bot']; 192 | authorizeFiles(filesToAuthorize); 193 | 194 | //运行ne-zha 195 | if (NEZHA_SERVER && NEZHA_KEY) { 196 | if (!NEZHA_PORT) { 197 | // 检测哪吒是否开启TLS 198 | const port = NEZHA_SERVER.includes(':') ? NEZHA_SERVER.split(':').pop() : ''; 199 | const tlsPorts = new Set(['443', '8443', '2096', '2087', '2083', '2053']); 200 | const nezhatls = tlsPorts.has(port) ? 'true' : 'false'; 201 | // 生成 config.yaml 202 | const configYaml = ` 203 | client_secret: ${NEZHA_KEY} 204 | debug: false 205 | disable_auto_update: true 206 | disable_command_execute: false 207 | disable_force_update: true 208 | disable_nat: false 209 | disable_send_query: false 210 | gpu: false 211 | insecure_tls: false 212 | ip_report_period: 1800 213 | report_delay: 1 214 | server: ${NEZHA_SERVER} 215 | skip_connection_count: false 216 | skip_procs_count: false 217 | temperature: false 218 | tls: ${nezhatls} 219 | use_gitee_to_upgrade: false 220 | use_ipv6_country_code: false 221 | uuid: ${UUID}`; 222 | 223 | fs.writeFileSync(path.join(FILE_PATH, 'config.yaml'), configYaml); 224 | 225 | // 运行 php 226 | const command = `nohup ${FILE_PATH}/php -c "${FILE_PATH}/config.yaml" >/dev/null 2>&1 &`; 227 | try { 228 | await exec(command); 229 | console.log('php is running'); 230 | await new Promise((resolve) => setTimeout(resolve, 1000)); 231 | } catch (error) { 232 | console.error(`php running error: ${error}`); 233 | } 234 | } else { 235 | let NEZHA_TLS = ''; 236 | const tlsPorts = ['443', '8443', '2096', '2087', '2083', '2053']; 237 | if (tlsPorts.includes(NEZHA_PORT)) { 238 | NEZHA_TLS = '--tls'; 239 | } 240 | const command = `nohup ${FILE_PATH}/npm -s ${NEZHA_SERVER}:${NEZHA_PORT} -p ${NEZHA_KEY} ${NEZHA_TLS} >/dev/null 2>&1 &`; 241 | try { 242 | await exec(command); 243 | console.log('npm is running'); 244 | await new Promise((resolve) => setTimeout(resolve, 1000)); 245 | } catch (error) { 246 | console.error(`npm running error: ${error}`); 247 | } 248 | } 249 | } else { 250 | console.log('NEZHA variable is empty,skip running'); 251 | } 252 | //运行xr-ay 253 | const command1 = `nohup ${FILE_PATH}/web -c ${FILE_PATH}/config.json >/dev/null 2>&1 &`; 254 | try { 255 | await exec(command1); 256 | console.log('web is running'); 257 | await new Promise((resolve) => setTimeout(resolve, 1000)); 258 | } catch (error) { 259 | console.error(`web running error: ${error}`); 260 | } 261 | 262 | // 运行cloud-fared 263 | if (fs.existsSync(path.join(FILE_PATH, 'bot'))) { 264 | let args; 265 | 266 | if (ARGO_AUTH.match(/^[A-Z0-9a-z=]{120,250}$/)) { 267 | args = `tunnel --edge-ip-version auto --no-autoupdate --protocol http2 run --token ${ARGO_AUTH}`; 268 | } else if (ARGO_AUTH.match(/TunnelSecret/)) { 269 | args = `tunnel --edge-ip-version auto --config ${FILE_PATH}/tunnel.yml run`; 270 | } else { 271 | args = `tunnel --edge-ip-version auto --no-autoupdate --protocol http2 --logfile ${FILE_PATH}/boot.log --loglevel info --url http://localhost:${ARGO_PORT}`; 272 | } 273 | 274 | try { 275 | await exec(`nohup ${FILE_PATH}/bot ${args} >/dev/null 2>&1 &`); 276 | console.log('bot is running'); 277 | await new Promise((resolve) => setTimeout(resolve, 2000)); 278 | } catch (error) { 279 | console.error(`Error executing command: ${error}`); 280 | } 281 | } 282 | await new Promise((resolve) => setTimeout(resolve, 5000)); 283 | 284 | } 285 | 286 | //根据系统架构返回对应的url 287 | function getFilesForArchitecture(architecture) { 288 | let baseFiles; 289 | if (architecture === 'arm') { 290 | baseFiles = [ 291 | { fileName: "web", fileUrl: "https://arm64.ssss.nyc.mn/web" }, 292 | { fileName: "bot", fileUrl: "https://arm64.ssss.nyc.mn/2go" } 293 | ]; 294 | } else { 295 | baseFiles = [ 296 | { fileName: "web", fileUrl: "https://amd64.ssss.nyc.mn/web" }, 297 | { fileName: "bot", fileUrl: "https://amd64.ssss.nyc.mn/2go" } 298 | ]; 299 | } 300 | 301 | if (NEZHA_SERVER && NEZHA_KEY) { 302 | if (NEZHA_PORT) { 303 | const npmUrl = architecture === 'arm' 304 | ? "https://arm64.ssss.nyc.mn/agent" 305 | : "https://amd64.ssss.nyc.mn/agent"; 306 | baseFiles.unshift({ 307 | fileName: "npm", 308 | fileUrl: npmUrl 309 | }); 310 | } else { 311 | const phpUrl = architecture === 'arm' 312 | ? "https://arm64.ssss.nyc.mn/v1" 313 | : "https://amd64.ssss.nyc.mn/v1"; 314 | baseFiles.unshift({ 315 | fileName: "php", 316 | fileUrl: phpUrl 317 | }); 318 | } 319 | } 320 | 321 | return baseFiles; 322 | } 323 | 324 | // 获取固定隧道json 325 | function argoType() { 326 | if (!ARGO_AUTH || !ARGO_DOMAIN) { 327 | console.log("ARGO_DOMAIN or ARGO_AUTH variable is empty, use quick tunnels"); 328 | return; 329 | } 330 | 331 | if (ARGO_AUTH.includes('TunnelSecret')) { 332 | fs.writeFileSync(path.join(FILE_PATH, 'tunnel.json'), ARGO_AUTH); 333 | const tunnelYaml = ` 334 | tunnel: ${ARGO_AUTH.split('"')[11]} 335 | credentials-file: ${path.join(FILE_PATH, 'tunnel.json')} 336 | protocol: http2 337 | 338 | ingress: 339 | - hostname: ${ARGO_DOMAIN} 340 | service: http://localhost:${ARGO_PORT} 341 | originRequest: 342 | noTLSVerify: true 343 | - service: http_status:404 344 | `; 345 | fs.writeFileSync(path.join(FILE_PATH, 'tunnel.yml'), tunnelYaml); 346 | } else { 347 | console.log("ARGO_AUTH mismatch TunnelSecret,use token connect to tunnel"); 348 | } 349 | } 350 | argoType(); 351 | 352 | // 获取临时隧道domain 353 | async function extractDomains() { 354 | let argoDomain; 355 | 356 | if (ARGO_AUTH && ARGO_DOMAIN) { 357 | argoDomain = ARGO_DOMAIN; 358 | console.log('ARGO_DOMAIN:', argoDomain); 359 | await generateLinks(argoDomain); 360 | } else { 361 | try { 362 | const fileContent = fs.readFileSync(path.join(FILE_PATH, 'boot.log'), 'utf-8'); 363 | const lines = fileContent.split('\n'); 364 | const argoDomains = []; 365 | lines.forEach((line) => { 366 | const domainMatch = line.match(/https?:\/\/([^ ]*trycloudflare\.com)\/?/); 367 | if (domainMatch) { 368 | const domain = domainMatch[1]; 369 | argoDomains.push(domain); 370 | } 371 | }); 372 | 373 | if (argoDomains.length > 0) { 374 | argoDomain = argoDomains[0]; 375 | console.log('ArgoDomain:', argoDomain); 376 | await generateLinks(argoDomain); 377 | } else { 378 | console.log('ArgoDomain not found, re-running bot to obtain ArgoDomain'); 379 | // 删除 boot.log 文件,等待 2s 重新运行 server 以获取 ArgoDomain 380 | fs.unlinkSync(path.join(FILE_PATH, 'boot.log')); 381 | async function killBotProcess() { 382 | try { 383 | await exec('pkill -f "[b]ot" > /dev/null 2>&1'); 384 | } catch (error) { 385 | // 忽略输出 386 | } 387 | } 388 | killBotProcess(); 389 | await new Promise((resolve) => setTimeout(resolve, 3000)); 390 | const args = `tunnel --edge-ip-version auto --no-autoupdate --protocol http2 --logfile ${FILE_PATH}/boot.log --loglevel info --url http://localhost:${ARGO_PORT}`; 391 | try { 392 | await exec(`nohup ${path.join(FILE_PATH, 'bot')} ${args} >/dev/null 2>&1 &`); 393 | console.log('bot is running.'); 394 | await new Promise((resolve) => setTimeout(resolve, 3000)); 395 | await extractDomains(); // 重新提取域名 396 | } catch (error) { 397 | console.error(`Error executing command: ${error}`); 398 | } 399 | } 400 | } catch (error) { 401 | console.error('Error reading boot.log:', error); 402 | } 403 | } 404 | 405 | // 生成 list 和 sub 信息 406 | async function generateLinks(argoDomain) { 407 | const metaInfo = execSync( 408 | 'curl -s https://speed.cloudflare.com/meta | awk -F\\" \'{print $26"-"$18}\' | sed -e \'s/ /_/g\'', 409 | { encoding: 'utf-8' } 410 | ); 411 | const ISP = metaInfo.trim(); 412 | 413 | return new Promise((resolve) => { 414 | setTimeout(() => { 415 | 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: '' }; 416 | const subTxt = ` 417 | vless://${UUID}@${CFIP}:${CFPORT}?encryption=none&security=tls&sni=${argoDomain}&type=ws&host=${argoDomain}&path=%2Fvless-argo%3Fed%3D2560#${NAME}-${ISP} 418 | 419 | vmess://${Buffer.from(JSON.stringify(VMESS)).toString('base64')} 420 | 421 | trojan://${UUID}@${CFIP}:${CFPORT}?security=tls&sni=${argoDomain}&type=ws&host=${argoDomain}&path=%2Ftrojan-argo%3Fed%3D2560#${NAME}-${ISP} 422 | `; 423 | // 打印 sub.txt 内容到控制台 424 | console.log(Buffer.from(subTxt).toString('base64')); 425 | fs.writeFileSync(subPath, Buffer.from(subTxt).toString('base64')); 426 | console.log(`${FILE_PATH}/sub.txt saved successfully`); 427 | uplodNodes(); 428 | // 将内容进行 base64 编码并写入 SUB_PATH 路由 429 | app.get(`/${SUB_PATH}`, (req, res) => { 430 | const encodedContent = Buffer.from(subTxt).toString('base64'); 431 | res.set('Content-Type', 'text/plain; charset=utf-8'); 432 | res.send(encodedContent); 433 | }); 434 | resolve(subTxt); 435 | }, 2000); 436 | }); 437 | } 438 | } 439 | 440 | // 自动上传节点或订阅 441 | async function uplodNodes() { 442 | if (UPLOAD_URL && PROJECT_URL) { 443 | const subscriptionUrl = `${PROJECT_URL}/${SUB_PATH}`; 444 | const jsonData = { 445 | subscription: [subscriptionUrl] 446 | }; 447 | try { 448 | const response = await axios.post(`${UPLOAD_URL}/api/add-subscriptions`, jsonData, { 449 | headers: { 450 | 'Content-Type': 'application/json' 451 | } 452 | }); 453 | 454 | if (response.status === 200) { 455 | console.log('Subscription uploaded successfully'); 456 | } else { 457 | return null; 458 | // console.log('Unknown response status'); 459 | } 460 | } catch (error) { 461 | if (error.response) { 462 | if (error.response.status === 400) { 463 | // console.error('Subscription already exists'); 464 | } 465 | } 466 | } 467 | } else if (UPLOAD_URL) { 468 | if (!fs.existsSync(listPath)) return; 469 | const content = fs.readFileSync(listPath, 'utf-8'); 470 | const nodes = content.split('\n').filter(line => /(vless|vmess|trojan|hysteria2|tuic):\/\//.test(line)); 471 | 472 | if (nodes.length === 0) return; 473 | 474 | const jsonData = JSON.stringify({ nodes }); 475 | 476 | try { 477 | await axios.post(`${UPLOAD_URL}/api/add-nodes`, jsonData, { 478 | headers: { 'Content-Type': 'application/json' } 479 | }); 480 | if (response.status === 200) { 481 | console.log('Subscription uploaded successfully'); 482 | } else { 483 | return null; 484 | } 485 | } catch (error) { 486 | return null; 487 | } 488 | } else { 489 | // console.log('Skipping upload nodes'); 490 | return; 491 | } 492 | } 493 | 494 | // 90s后删除相关文件 495 | function cleanFiles() { 496 | setTimeout(() => { 497 | const filesToDelete = [bootLogPath, configPath, webPath, botPath, phpPath, npmPath]; 498 | 499 | if (NEZHA_PORT) { 500 | filesToDelete.push(npmPath); 501 | } else if (NEZHA_SERVER && NEZHA_KEY) { 502 | filesToDelete.push(phpPath); 503 | } 504 | 505 | exec(`rm -rf ${filesToDelete.join(' ')} >/dev/null 2>&1`, (error) => { 506 | console.clear(); 507 | console.log('App is running'); 508 | console.log('Thank you for using this script, enjoy!'); 509 | }); 510 | }, 90000); // 90s 511 | } 512 | cleanFiles(); 513 | 514 | // 自动访问项目URL 515 | async function AddVisitTask() { 516 | if (!AUTO_ACCESS || !PROJECT_URL) { 517 | console.log("Skipping adding automatic access task"); 518 | return; 519 | } 520 | 521 | try { 522 | const response = await axios.post('https://oooo.serv00.net/add-url', { 523 | url: PROJECT_URL 524 | }, { 525 | headers: { 526 | 'Content-Type': 'application/json' 527 | } 528 | }); 529 | // console.log(`${JSON.stringify(response.data)}`); 530 | console.log(`automatic access task added successfully`); 531 | } catch (error) { 532 | console.error(`添加URL失败: ${error.message}`); 533 | } 534 | } 535 | 536 | // 回调运行 537 | async function startserver() { 538 | deleteNodes(); 539 | cleanupOldFiles(); 540 | await downloadFilesAndRun(); 541 | await extractDomains(); 542 | AddVisitTask(); 543 | } 544 | startserver(); 545 | 546 | app.listen(PORT, () => console.log(`http server is running on port:${PORT}!`)); 547 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-argo", 3 | "version": "8.8.8", 4 | "description": "Thsi is a node project", 5 | "main": "index.js", 6 | "author": "eooce", 7 | "repository": "https://github.com/eooce/nodejs-argo", 8 | "license": "MIT", 9 | "private": false, 10 | "scripts": { 11 | "start": "node index.js" 12 | }, 13 | "dependencies": { 14 | "axios": "latest", 15 | "express": "^4.18.2" 16 | }, 17 | "engines": { 18 | "node": ">=14" 19 | } 20 | } 21 | --------------------------------------------------------------------------------