[
  {
    "path": ".github/workflows/build-docker-image.yml",
    "content": "name: Build and Push Docker Image\n\non:\n  push:\n    branches: [ main ]\n    paths:\n      - 'Dockerfile'\n      - 'index.js'\n      - 'package.json'\n  pull_request:\n    branches: [ main ]\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      packages: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Log in to GitHub Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Build and push Docker image\n        uses: docker/build-push-action@v5\n        with:\n          context: .\n          push: true\n          platforms: linux/amd64,linux/arm64\n          tags: |\n            ghcr.io/${{ github.repository_owner }}/nodejs:latest\n          labels: |\n            org.opencontainers.image.source=https://github.com/${{ github.repository }}\n            org.opencontainers.image.description=HTTP Server\n            org.opencontainers.image.licenses=MIT\n          cache-from: type=gha\n          cache-to: type=gha,mode=max\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:alpine3.20\n\nWORKDIR /tmp\n\nCOPY . .\n\nEXPOSE 3000/tcp\n\nRUN apk update && apk upgrade &&\\\n    apk add --no-cache openssl curl gcompat iproute2 coreutils &&\\\n    apk add --no-cache bash &&\\\n    chmod +x index.js &&\\\n    npm install\n\nCMD [\"node\", \"index.js\"]\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <h2>\n    <img src=\"https://cdn.nodeimage.com/i/NXz3ah3zTwikq3AdQOU0dYw3uyaBiGVj.webp\" width=\"40\" height=\"40\" style=\"vertical-align: middle;\"/> \n    nodejs-argo隧道代理\n  </h2>\n  nodejs-argo是一个强大的Argo隧道部署工具，专为PaaS平台和游戏玩具平台设计。它支持多种代理协议（VLESS、VMess、Trojan等），并集成了哪吒探针功能。\n\n---\n\nTelegram交流反馈群组：https://t.me/eooceu\n</div>\n\n## 郑重声明\n* 本项目自2025年10月29日15时45分起,已更改开源协议,并包含以下特定要求\n* 此项目仅限个人使用，禁止用于商业行为(包括但不限于：youtube,bilibili,tiktok,facebook..等等)\n* 禁止新建项目将代码复制到自己仓库中用做商业行为\n* 请遵守当地法律法规,禁止滥用做公共代理行为\n* 如有违反以上条款者将追究法律责任\n\n## 说明 （部署前请仔细阅读）\n\n* 本项目是针对node环境的paas平台和游戏玩具而生，采用Argo隧道部署节点，集成哪吒探针v0或v1可选。\n* node玩具平台只需上传index.js和package.json即可，paas平台需要docker部署的才上传Dockerfile。\n* 不填写ARGO_DOMAIN和ARGO_AUTH两个变量即启用临时隧道，反之则使用固定隧道。\n* 哪吒v0/v1可选,当哪吒端口为{443,8443,2096,2087,2083,2053}其中之一时，自动开启tls。\n\n## 📋 环境变量\n\n| 变量名 | 是否必须 | 默认值 | 说明 |\n|--------|----------|--------|------|\n| UPLOAD_URL | 否 | - | 订阅上传地址 |\n| PROJECT_URL | 否 | https://www.google.com | 项目分配的域名 |\n| AUTO_ACCESS | 否 | false | 是否开启自动访问保活 |\n| PORT | 否 | 3000 | HTTP服务监听端口 |\n| ARGO_PORT | 否 | 8001 | Argo隧道端口 |\n| UUID | 否 | 89c13786-25aa-4520-b2e7-12cd60fb5202 | 用户UUID |\n| NEZHA_SERVER | 否 | - | 哪吒面板域名 |\n| NEZHA_PORT | 否 | - | 哪吒端口 |\n| NEZHA_KEY | 否 | - | 哪吒密钥 |\n| ARGO_DOMAIN | 否 | - | Argo固定隧道域名 |\n| ARGO_AUTH | 否 | - | Argo固定隧道密钥 |\n| CFIP | 否 | www.visa.com.tw | 节点优选域名或IP |\n| CFPORT | 否 | 443 | 节点端口 |\n| NAME | 否 | Vls | 节点名称前缀 |\n| FILE_PATH | 否 | ./tmp | 运行目录 |\n| SUB_PATH | 否 | sub | 订阅路径 |\n\n## 🌐 订阅地址\n\n- 标准端口：`https://your-domain.com/sub`\n- 非标端口：`http://your-domain.com:port/sub`\n\n---\n\n## 🚀 进阶使用\n\n### 安装\n\n```bash\n# 全局安装（推荐）\nnpm install -g nodejs-argo\n\n# 或者使用yarn\nyarn global add nodejs-argo\n\n# 或者使用pnpm\npnpm add -g nodejs-argo\n```\n\n### 基本使用\n\n```bash\n# 直接运行（使用默认配置）\nnodejs-argo\n\n# 使用npx运行\nnpx nodejs-argo\n\n# 设置环境变量运行\n PORT=3000 npx nodejs-argo\n```\n\n### 环境变量配置\n\n可使用 `.env` 文件来配置环境变量运行\n\n\n或者直接在命令行中设置：\n\n```bash\nexport UPLOAD_URL=\"https://your-merge-sub-domain.com\"\nexport PROJECT_URL=\"https://your-project-domain.com\"\nexport PORT=3000\nexport UUID=\"your-uuid-here\"\nexport NEZHA_SERVER=\"nz.your-domain.com:8008\"\nexport NEZHA_KEY=\"your-nezha-key\"\n```\n\n## 📦 作为npm模块使用\n\n```javascript\n// CommonJS\nconst nodejsArgo = require('nodejs-argo');\n\n// ES6 Modules\nimport nodejsArgo from 'nodejs-argo';\n\n// 启动服务\nnodejsArgo.start();\n```\n\n## 🔧 后台运行\n\n### 使用screen（推荐）\n```bash\n# 创建screen会话\nscreen -S argo\n\n# 运行应用\nnodejs-argo\n\n# 按 Ctrl+A 然后按 D 分离会话\n# 重新连接：screen -r argo\n```\n\n### 使用tmux\n```bash\n# 创建tmux会话\ntmux new-session -d -s argo\n\n# 运行应用\ntmux send-keys -t argo \"nodejs-argo\" Enter\n\n# 分离会话：tmux detach -s argo\n# 重新连接：tmux attach -t argo\n```\n\n### 使用PM2\n```bash\n# 安装PM2\nnpm install -g pm2\n\n# 启动应用\npm2 start nodejs-argo --name \"argo-service\"\n\n# 管理应用\npm2 status\npm2 logs argo-service\npm2 restart argo-service\n```\n\n### 使用systemd（Linux系统服务）\n```bash\n# 创建服务文件\nsudo nano /etc/systemd/system/nodejs-argo.service\n\n```\n[Unit]\nDescription=Node.js Argo Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/root/test\nEnvironment=ARGO_PORT=8080\nEnvironment=PORT=3000\nExecStart=/usr/bin/npx nodejs-argo\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\n```\n\n# 启动服务\nsudo systemctl start nodejs-argo\nsudo systemctl enable nodejs-argo\n```\n\n## 🔄 更新\n\n```bash\n# 更新全局安装的包\nnpm update -g nodejs-argo\n\n# 或者重新安装\nnpm uninstall -g nodejs-argo\nnpm install -g nodejs-argo\n```\n\n## 📚 更多信息\n\n- [GitHub仓库](https://github.com/eooce/nodejs-argo)\n- [npm包页面](https://www.npmjs.com/package/nodejs-argo)\n- [问题反馈](https://github.com/eooce/nodejs-argo/issues)\n\n---\n\n## 赞助\n* 感谢[VPS.Town](https://vps.town)提供赞助 <a href=\"https://vps.town\" target=\"_blank\"><img src=\"https://vps.town/static/images/sponsor.png\" width=\"30%\" alt=\"https://vps.town\"></a>\n\n* 感谢[ZMTO](https://zmto.com/?affid=1548)提供赞助优质双isp vps。\n"
  },
  {
    "path": "index.js",
    "content": "const express = require(\"express\");\nconst app = express();\nconst axios = require(\"axios\");\nconst os = require('os');\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst { promisify } = require('util');\nconst exec = promisify(require('child_process').exec);\nconst UPLOAD_URL = process.env.UPLOAD_URL || '';      // 节点或订阅自动上传地址,需填写部署Merge-sub项目后的首页地址,例如：https://merge.xxx.com\nconst PROJECT_URL = process.env.PROJECT_URL || '';    // 需要上传订阅或保活时需填写项目分配的url,例如：https://google.com\nconst AUTO_ACCESS = process.env.AUTO_ACCESS || false; // false关闭自动保活，true开启,需同时填写PROJECT_URL变量\nconst FILE_PATH = process.env.FILE_PATH || '.tmp';   // 运行目录,sub节点文件保存目录\nconst SUB_PATH = process.env.SUB_PATH || 'sub';       // 订阅路径\nconst PORT = process.env.SERVER_PORT || process.env.PORT || 3000;        // http服务订阅端口\nconst UUID = process.env.UUID || '9afd1229-b893-40c1-84dd-51e7ce204913'; // 使用哪吒v1,在不同的平台运行需修改UUID,否则会覆盖\nconst NEZHA_SERVER = process.env.NEZHA_SERVER || '';        // 哪吒v1填写形式: nz.abc.com:8008  哪吒v0填写形式：nz.abc.com\nconst NEZHA_PORT = process.env.NEZHA_PORT || '';            // 使用哪吒v1请留空，哪吒v0需填写\nconst NEZHA_KEY = process.env.NEZHA_KEY || '';              // 哪吒v1的NZ_CLIENT_SECRET或哪吒v0的agent密钥\nconst ARGO_DOMAIN = process.env.ARGO_DOMAIN || '';          // 固定隧道域名,留空即启用临时隧道\nconst ARGO_AUTH = process.env.ARGO_AUTH || '';              // 固定隧道密钥json或token,留空即启用临时隧道,json获取地址：https://json.zone.id\nconst ARGO_PORT = process.env.ARGO_PORT || 8001;            // 固定隧道端口,使用token需在cloudflare后台设置和这里一致\nconst CFIP = process.env.CFIP || 'saas.sin.fan';            // 节点优选域名或优选ip  \nconst CFPORT = process.env.CFPORT || 443;                   // 节点优选域名或优选ip对应的端口\nconst NAME = process.env.NAME || '';                        // 节点名称\n\n// 创建运行文件夹\nif (!fs.existsSync(FILE_PATH)) {\n  fs.mkdirSync(FILE_PATH);\n  console.log(`${FILE_PATH} is created`);\n} else {\n  console.log(`${FILE_PATH} already exists`);\n}\n\n// 生成随机6位字符文件名\nfunction generateRandomName() {\n  const characters = 'abcdefghijklmnopqrstuvwxyz';\n  let result = '';\n  for (let i = 0; i < 6; i++) {\n    result += characters.charAt(Math.floor(Math.random() * characters.length));\n  }\n  return result;\n}\n\n// 全局常量\nconst npmName = generateRandomName();\nconst webName = generateRandomName();\nconst botName = generateRandomName();\nconst phpName = generateRandomName();\nlet npmPath = path.join(FILE_PATH, npmName);\nlet phpPath = path.join(FILE_PATH, phpName);\nlet webPath = path.join(FILE_PATH, webName);\nlet botPath = path.join(FILE_PATH, botName);\nlet subPath = path.join(FILE_PATH, 'sub.txt');\nlet listPath = path.join(FILE_PATH, 'list.txt');\nlet bootLogPath = path.join(FILE_PATH, 'boot.log');\nlet configPath = path.join(FILE_PATH, 'config.json');\n\n// 如果订阅器上存在历史运行节点则先删除\nfunction deleteNodes() {\n  try {\n    if (!UPLOAD_URL) return;\n    if (!fs.existsSync(subPath)) return;\n\n    let fileContent;\n    try {\n      fileContent = fs.readFileSync(subPath, 'utf-8');\n    } catch {\n      return null;\n    }\n\n    const decoded = Buffer.from(fileContent, 'base64').toString('utf-8');\n    const nodes = decoded.split('\\n').filter(line => \n      /(vless|vmess|trojan|hysteria2|tuic):\\/\\//.test(line)\n    );\n\n    if (nodes.length === 0) return;\n\n    axios.post(`${UPLOAD_URL}/api/delete-nodes`, \n      JSON.stringify({ nodes }),\n      { headers: { 'Content-Type': 'application/json' } }\n    ).catch((error) => { \n      return null; \n    });\n    return null;\n  } catch (err) {\n    return null;\n  }\n}\n\n// 清理历史文件\nfunction cleanupOldFiles() {\n  try {\n    const files = fs.readdirSync(FILE_PATH);\n    files.forEach(file => {\n      const filePath = path.join(FILE_PATH, file);\n      try {\n        const stat = fs.statSync(filePath);\n        if (stat.isFile()) {\n          fs.unlinkSync(filePath);\n        }\n      } catch (err) {\n        // 忽略所有错误，不记录日志\n      }\n    });\n  } catch (err) {\n    // 忽略所有错误，不记录日志\n  }\n}\n\n// 生成xr-ay配置文件\nasync function generateConfig() {\n  const config = {\n    log: { access: '/dev/null', error: '/dev/null', loglevel: 'none' },\n    inbounds: [\n      { 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' } },\n      { port: 3001, listen: \"127.0.0.1\", protocol: \"vless\", settings: { clients: [{ id: UUID }], decryption: \"none\" }, streamSettings: { network: \"tcp\", security: \"none\" } },\n      { 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 } },\n      { 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 } },\n      { 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 } },\n    ],\n    dns: { servers: [\"https+local://8.8.8.8/dns-query\"] },\n    outbounds: [ { protocol: \"freedom\", tag: \"direct\" }, {protocol: \"blackhole\", tag: \"block\"} ]\n  };\n  fs.writeFileSync(path.join(FILE_PATH, 'config.json'), JSON.stringify(config, null, 2));\n}\n\n// 判断系统架构\nfunction getSystemArchitecture() {\n  const arch = os.arch();\n  if (arch === 'arm' || arch === 'arm64' || arch === 'aarch64') {\n    return 'arm';\n  } else {\n    return 'amd';\n  }\n}\n\n// 下载对应系统架构的依赖文件\nfunction downloadFile(fileName, fileUrl, callback) {\n  const filePath = fileName; \n  \n  // 确保目录存在\n  if (!fs.existsSync(FILE_PATH)) {\n    fs.mkdirSync(FILE_PATH, { recursive: true });\n  }\n  \n  const writer = fs.createWriteStream(filePath);\n\n  axios({\n    method: 'get',\n    url: fileUrl,\n    responseType: 'stream',\n  })\n    .then(response => {\n      response.data.pipe(writer);\n\n      writer.on('finish', () => {\n        writer.close();\n        console.log(`Download ${path.basename(filePath)} successfully`);\n        callback(null, filePath);\n      });\n\n      writer.on('error', err => {\n        fs.unlink(filePath, () => { });\n        const errorMessage = `Download ${path.basename(filePath)} failed: ${err.message}`;\n        console.error(errorMessage); // 下载失败时输出错误消息\n        callback(errorMessage);\n      });\n    })\n    .catch(err => {\n      const errorMessage = `Download ${path.basename(filePath)} failed: ${err.message}`;\n      console.error(errorMessage); // 下载失败时输出错误消息\n      callback(errorMessage);\n    });\n}\n\n// 下载并运行依赖文件\nasync function downloadFilesAndRun() {  \n  \n  const architecture = getSystemArchitecture();\n  const filesToDownload = getFilesForArchitecture(architecture);\n\n  if (filesToDownload.length === 0) {\n    console.log(`Can't find a file for the current architecture`);\n    return;\n  }\n\n  const downloadPromises = filesToDownload.map(fileInfo => {\n    return new Promise((resolve, reject) => {\n      downloadFile(fileInfo.fileName, fileInfo.fileUrl, (err, filePath) => {\n        if (err) {\n          reject(err);\n        } else {\n          resolve(filePath);\n        }\n      });\n    });\n  });\n\n  try {\n    await Promise.all(downloadPromises);\n  } catch (err) {\n    console.error('Error downloading files:', err);\n    return;\n  }\n  // 授权和运行\n  function authorizeFiles(filePaths) {\n    const newPermissions = 0o775;\n    filePaths.forEach(absoluteFilePath => {\n      if (fs.existsSync(absoluteFilePath)) {\n        fs.chmod(absoluteFilePath, newPermissions, (err) => {\n          if (err) {\n            console.error(`Empowerment failed for ${absoluteFilePath}: ${err}`);\n          } else {\n            console.log(`Empowerment success for ${absoluteFilePath}: ${newPermissions.toString(8)}`);\n          }\n        });\n      }\n    });\n  }\n  const filesToAuthorize = NEZHA_PORT ? [npmPath, webPath, botPath] : [phpPath, webPath, botPath];\n  authorizeFiles(filesToAuthorize);\n\n  //运行ne-zha\n  if (NEZHA_SERVER && NEZHA_KEY) {\n    if (!NEZHA_PORT) {\n      // 检测哪吒是否开启TLS\n      const port = NEZHA_SERVER.includes(':') ? NEZHA_SERVER.split(':').pop() : '';\n      const tlsPorts = new Set(['443', '8443', '2096', '2087', '2083', '2053']);\n      const nezhatls = tlsPorts.has(port) ? 'true' : 'false';\n      // 生成 config.yaml\n      const configYaml = `\nclient_secret: ${NEZHA_KEY}\ndebug: false\ndisable_auto_update: true\ndisable_command_execute: false\ndisable_force_update: true\ndisable_nat: false\ndisable_send_query: false\ngpu: false\ninsecure_tls: true\nip_report_period: 1800\nreport_delay: 4\nserver: ${NEZHA_SERVER}\nskip_connection_count: true\nskip_procs_count: true\ntemperature: false\ntls: ${nezhatls}\nuse_gitee_to_upgrade: false\nuse_ipv6_country_code: false\nuuid: ${UUID}`;\n      \n      fs.writeFileSync(path.join(FILE_PATH, 'config.yaml'), configYaml);\n      \n      // 运行 v1\n      const command = `nohup ${phpPath} -c \"${FILE_PATH}/config.yaml\" >/dev/null 2>&1 &`;\n      try {\n        await exec(command);\n        console.log(`${phpName} is running`);\n        await new Promise((resolve) => setTimeout(resolve, 1000));\n      } catch (error) {\n        console.error(`php running error: ${error}`);\n      }\n    } else {\n      let NEZHA_TLS = '';\n      const tlsPorts = ['443', '8443', '2096', '2087', '2083', '2053'];\n      if (tlsPorts.includes(NEZHA_PORT)) {\n        NEZHA_TLS = '--tls';\n      }\n      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 &`;\n      try {\n        await exec(command);\n        console.log(`${npmName} is running`);\n        await new Promise((resolve) => setTimeout(resolve, 1000));\n      } catch (error) {\n        console.error(`npm running error: ${error}`);\n      }\n    }\n  } else {\n    console.log('NEZHA variable is empty,skip running');\n  }\n  //运行xr-ay\n  const command1 = `nohup ${webPath} -c ${FILE_PATH}/config.json >/dev/null 2>&1 &`;\n  try {\n    await exec(command1);\n    console.log(`${webName} is running`);\n    await new Promise((resolve) => setTimeout(resolve, 1000));\n  } catch (error) {\n    console.error(`web running error: ${error}`);\n  }\n\n  // 运行cloud-fared\n  if (fs.existsSync(botPath)) {\n    let args;\n\n    if (ARGO_AUTH.match(/^[A-Z0-9a-z=]{120,250}$/)) {\n      args = `tunnel --edge-ip-version auto --no-autoupdate --protocol http2 run --token ${ARGO_AUTH}`;\n    } else if (ARGO_AUTH.match(/TunnelSecret/)) {\n      args = `tunnel --edge-ip-version auto --config ${FILE_PATH}/tunnel.yml run`;\n    } else {\n      args = `tunnel --edge-ip-version auto --no-autoupdate --protocol http2 --logfile ${FILE_PATH}/boot.log --loglevel info --url http://localhost:${ARGO_PORT}`;\n    }\n\n    try {\n      await exec(`nohup ${botPath} ${args} >/dev/null 2>&1 &`);\n      console.log(`${botName} is running`);\n      await new Promise((resolve) => setTimeout(resolve, 2000));\n    } catch (error) {\n      console.error(`Error executing command: ${error}`);\n    }\n  }\n  await new Promise((resolve) => setTimeout(resolve, 5000));\n\n}\n\n//根据系统架构返回对应的url\nfunction getFilesForArchitecture(architecture) {\n  let baseFiles;\n  if (architecture === 'arm') {\n    baseFiles = [\n      { fileName: webPath, fileUrl: \"https://arm64.ssss.nyc.mn/web\" },\n      { fileName: botPath, fileUrl: \"https://arm64.ssss.nyc.mn/bot\" }\n    ];\n  } else {\n    baseFiles = [\n      { fileName: webPath, fileUrl: \"https://amd64.ssss.nyc.mn/web\" },\n      { fileName: botPath, fileUrl: \"https://amd64.ssss.nyc.mn/bot\" }\n    ];\n  }\n\n  if (NEZHA_SERVER && NEZHA_KEY) {\n    if (NEZHA_PORT) {\n      const npmUrl = architecture === 'arm' \n        ? \"https://arm64.ssss.nyc.mn/agent\"\n        : \"https://amd64.ssss.nyc.mn/agent\";\n        baseFiles.unshift({ \n          fileName: npmPath, \n          fileUrl: npmUrl \n        });\n    } else {\n      const phpUrl = architecture === 'arm' \n        ? \"https://arm64.ssss.nyc.mn/v1\" \n        : \"https://amd64.ssss.nyc.mn/v1\";\n      baseFiles.unshift({ \n        fileName: phpPath, \n        fileUrl: phpUrl\n      });\n    }\n  }\n\n  return baseFiles;\n}\n\n// 获取固定隧道json\nfunction argoType() {\n  if (!ARGO_AUTH || !ARGO_DOMAIN) {\n    console.log(\"ARGO_DOMAIN or ARGO_AUTH variable is empty, use quick tunnels\");\n    return;\n  }\n\n  if (ARGO_AUTH.includes('TunnelSecret')) {\n    fs.writeFileSync(path.join(FILE_PATH, 'tunnel.json'), ARGO_AUTH);\n    const tunnelYaml = `\n  tunnel: ${ARGO_AUTH.split('\"')[11]}\n  credentials-file: ${path.join(FILE_PATH, 'tunnel.json')}\n  protocol: http2\n  \n  ingress:\n    - hostname: ${ARGO_DOMAIN}\n      service: http://localhost:${ARGO_PORT}\n      originRequest:\n        noTLSVerify: true\n    - service: http_status:404\n  `;\n    fs.writeFileSync(path.join(FILE_PATH, 'tunnel.yml'), tunnelYaml);\n  } else {\n    console.log(\"ARGO_AUTH mismatch TunnelSecret,use token connect to tunnel\");\n  }\n}\n\n// 获取临时隧道domain\nasync function extractDomains() {\n  let argoDomain;\n\n  if (ARGO_AUTH && ARGO_DOMAIN) {\n    argoDomain = ARGO_DOMAIN;\n    console.log('ARGO_DOMAIN:', argoDomain);\n    await generateLinks(argoDomain);\n  } else {\n    try {\n      const fileContent = fs.readFileSync(path.join(FILE_PATH, 'boot.log'), 'utf-8');\n      const lines = fileContent.split('\\n');\n      const argoDomains = [];\n      lines.forEach((line) => {\n        const domainMatch = line.match(/https?:\\/\\/([^ ]*trycloudflare\\.com)\\/?/);\n        if (domainMatch) {\n          const domain = domainMatch[1];\n          argoDomains.push(domain);\n        }\n      });\n\n      if (argoDomains.length > 0) {\n        argoDomain = argoDomains[0];\n        console.log('ArgoDomain:', argoDomain);\n        await generateLinks(argoDomain);\n      } else {\n        console.log('ArgoDomain not found, re-running bot to obtain ArgoDomain');\n        // 删除 boot.log 文件，等待 2s 重新运行 server 以获取 ArgoDomain\n        fs.unlinkSync(path.join(FILE_PATH, 'boot.log'));\n        async function killBotProcess() {\n          try {\n            if (process.platform === 'win32') {\n              await exec(`taskkill /f /im ${botName}.exe > nul 2>&1`);\n            } else {\n              await exec(`pkill -f \"[${botName.charAt(0)}]${botName.substring(1)}\" > /dev/null 2>&1`);\n            }\n          } catch (error) {\n            // 忽略输出\n          }\n        }\n        killBotProcess();\n        await new Promise((resolve) => setTimeout(resolve, 3000));\n        const args = `tunnel --edge-ip-version auto --no-autoupdate --protocol http2 --logfile ${FILE_PATH}/boot.log --loglevel info --url http://localhost:${ARGO_PORT}`;\n        try {\n          await exec(`nohup ${botPath} ${args} >/dev/null 2>&1 &`);\n          console.log(`${botName} is running`);\n          await new Promise((resolve) => setTimeout(resolve, 3000));\n          await extractDomains(); // 重新提取域名\n        } catch (error) {\n          console.error(`Error executing command: ${error}`);\n        }\n      }\n    } catch (error) {\n      console.error('Error reading boot.log:', error);\n  }\n}\n\n// 获取isp信息\nasync function getMetaInfo() {\n  try {\n    const response1 = await axios.get('https://api.ip.sb/geoip', { headers: { 'User-Agent': 'Mozilla/5.0', timeout: 3000 }});\n    if (response1.data && response1.data.country_code && response1.data.isp) {\n      return `${response1.data.country_code}-${response1.data.isp}`.replace(/\\s+/g, '_');\n    }\n  } catch (error) {\n      try {\n        // 备用 ip-api.com 获取isp\n        const response2 = await axios.get('http://ip-api.com/json', { headers: { 'User-Agent': 'Mozilla/5.0', timeout: 3000 }});\n        if (response2.data && response2.data.status === 'success' && response2.data.countryCode && response2.data.org) {\n          return `${response2.data.countryCode}-${response2.data.org}`.replace(/\\s+/g, '_');\n        }\n      } catch (error) {\n        // console.error('Backup API also failed');\n      }\n  }\n  return 'Unknown';\n}\n// 生成 list 和 sub 信息\nasync function generateLinks(argoDomain) {\n  const ISP = await getMetaInfo();\n  const nodeName = NAME ? `${NAME}-${ISP}` : ISP;\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      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'};\n      const subTxt = `\nvless://${UUID}@${CFIP}:${CFPORT}?encryption=none&security=tls&sni=${argoDomain}&fp=firefox&type=ws&host=${argoDomain}&path=%2Fvless-argo%3Fed%3D2560#${nodeName}\n\nvmess://${Buffer.from(JSON.stringify(VMESS)).toString('base64')}\n\ntrojan://${UUID}@${CFIP}:${CFPORT}?security=tls&sni=${argoDomain}&fp=firefox&type=ws&host=${argoDomain}&path=%2Ftrojan-argo%3Fed%3D2560#${nodeName}\n    `;\n      // 打印 sub.txt 内容到控制台\n      console.log(Buffer.from(subTxt).toString('base64'));\n      fs.writeFileSync(subPath, Buffer.from(subTxt).toString('base64'));\n      console.log(`${FILE_PATH}/sub.txt saved successfully`);\n      uploadNodes();\n      // 将内容进行 base64 编码并写入 SUB_PATH 路由\n      app.get(`/${SUB_PATH}`, (req, res) => {\n        const encodedContent = Buffer.from(subTxt).toString('base64');\n        res.set('Content-Type', 'text/plain; charset=utf-8');\n        res.send(encodedContent);\n      });\n      resolve(subTxt);\n      }, 2000);\n    });\n  }\n}\n\n// 自动上传节点或订阅\nasync function uploadNodes() {\n  if (UPLOAD_URL && PROJECT_URL) {\n    const subscriptionUrl = `${PROJECT_URL}/${SUB_PATH}`;\n    const jsonData = {\n      subscription: [subscriptionUrl]\n    };\n    try {\n        const response = await axios.post(`${UPLOAD_URL}/api/add-subscriptions`, jsonData, {\n            headers: {\n                'Content-Type': 'application/json'\n            }\n        });\n        \n        if (response && response.status === 200) {\n            console.log('Subscription uploaded successfully');\n            return response;\n        } else {\n          return null;\n          //  console.log('Unknown response status');\n        }\n    } catch (error) {\n        if (error.response) {\n            if (error.response.status === 400) {\n              //  console.error('Subscription already exists');\n            }\n        }\n    }\n  } else if (UPLOAD_URL) {\n      if (!fs.existsSync(listPath)) return;\n      const content = fs.readFileSync(listPath, 'utf-8');\n      const nodes = content.split('\\n').filter(line => /(vless|vmess|trojan|hysteria2|tuic):\\/\\//.test(line));\n\n      if (nodes.length === 0) return;\n\n      const jsonData = JSON.stringify({ nodes });\n\n      try {\n          const response = await axios.post(`${UPLOAD_URL}/api/add-nodes`, jsonData, {\n              headers: { 'Content-Type': 'application/json' }\n          });\n          if (response && response.status === 200) {\n            console.log('Nodes uploaded successfully');\n            return response;\n        } else {\n            return null;\n        }\n      } catch (error) {\n          return null;\n      }\n  } else {\n      // console.log('Skipping upload nodes');\n      return;\n  }\n}\n\n// 90s后删除相关文件\nfunction cleanFiles() {\n  setTimeout(() => {\n    const filesToDelete = [bootLogPath, configPath, webPath, botPath];  \n    \n    if (NEZHA_PORT) {\n      filesToDelete.push(npmPath);\n    } else if (NEZHA_SERVER && NEZHA_KEY) {\n      filesToDelete.push(phpPath);\n    }\n\n    // Windows系统使用不同的删除命令\n    if (process.platform === 'win32') {\n      exec(`del /f /q ${filesToDelete.join(' ')} > nul 2>&1`, (error) => {\n        console.clear();\n        console.log('App is running');\n        console.log('Thank you for using this script, enjoy!');\n      });\n    } else {\n      exec(`rm -rf ${filesToDelete.join(' ')} >/dev/null 2>&1`, (error) => {\n        console.clear();\n        console.log('App is running');\n        console.log('Thank you for using this script, enjoy!');\n      });\n    }\n  }, 90000); // 90s\n}\ncleanFiles();\n\n// 自动访问项目URL\nasync function AddVisitTask() {\n  if (!AUTO_ACCESS || !PROJECT_URL) {\n    console.log(\"Skipping adding automatic access task\");\n    return;\n  }\n\n  try {\n    const response = await axios.post('https://oooo.serv00.net/add-url', {\n      url: PROJECT_URL\n    }, {\n      headers: {\n        'Content-Type': 'application/json'\n      }\n    });\n    // console.log(`${JSON.stringify(response.data)}`);\n    console.log(`automatic access task added successfully`);\n    return response;\n  } catch (error) {\n    console.error(`Add automatic access task faild: ${error.message}`);\n    return null;\n  }\n}\n\n// 主运行逻辑\nasync function startserver() {\n  try {\n    argoType();\n    deleteNodes();\n    cleanupOldFiles();\n    await generateConfig();\n    await downloadFilesAndRun();\n    await extractDomains();\n    await AddVisitTask();\n  } catch (error) {\n    console.error('Error in startserver:', error);\n  }\n}\nstartserver().catch(error => {\n  console.error('Unhandled error in startserver:', error);\n});\n\n// 根路由\napp.get(\"/\", async function(req, res) {\n  try {\n    const filePath = path.join(__dirname, 'index.html');\n    const data = await fs.promises.readFile(filePath, 'utf8');\n    res.send(data);\n  } catch (err) {\n    res.send(\"Hello world!<br><br>You can access /{SUB_PATH}(Default: /sub) to get your nodes!\");\n  }\n});\n\napp.listen(PORT, () => console.log(`http server is running on port:${PORT}!`));\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"nodejs-argo\",\n  \"version\": \"1,0,0\",\n  \"description\": \"Thsi is a node project\",\n  \"main\": \"index.js\",\n  \"author\": \"eooce\",\n  \"repository\": \"\",\n  \"license\": \"MIT\",\n  \"private\": false,\n  \"scripts\": {\n    \"dev\": \"node index.js\",\n    \"start\": \"node index.js\"\n  },\n  \"dependencies\": {\n    \"axios\": \"latest\",\n    \"express\": \"^4.18.2\"\n  },\n  \"engines\": {\n    \"node\": \">=14\"\n  }\n}\n"
  }
]