├── .vscode └── settings.json ├── Dockerfile ├── README.md ├── cron.sh ├── index.js ├── m_setup.sh ├── package.json └── setup.sh /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "apc.stylesheet": { ".anysphere-markdown-container-root": "font-size: 16px; line-height: 1.4;" } 3 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY . . 6 | 7 | EXPOSE 3000 8 | 9 | RUN apk update && apk add --no-cache openssl curl &&\ 10 | chmod +x index.js &&\ 11 | npm install 12 | 13 | CMD ["node", "index.js"] 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | 3 | > **注意:** 记得把命令中的 `yourdomain` 改为你真实的域名 4 | 5 | ```bash 6 | curl -Ls https://raw.githubusercontent.com/frankiejun/node-ws/refs/heads/main/setup.sh > setup.sh && chmod +x setup.sh && ./setup.sh yourdomain 7 | ``` 8 | 9 | > webhostmost目前只能手动上传 index.js, package.json, cron.sh 然后面板启动! 10 | 11 | ### 查看节点 12 | https://你的域名/你的uuid 13 | 14 | ### 关于保活 15 | 默认自动保活哪吒和节点(webhostmost保活哪吒和一定程度保活节点),无需你特殊处理。 16 | 17 | 18 | 19 | # Node-ws说明 20 | 用于node环境的玩具和容器,基于node三方ws库,集成哪吒探针服务,可自行添加环境变量 21 | * PaaS 平台设置的环境变量 22 | | 变量名 | 是否必须 | 默认值 | 备注 | 23 | | ------------ | ------ | ------ | ------ | 24 | | UUID | 否 |de04add9-5c68-6bab-950c-08cd5320df33| 开启了哪吒v1,请修改UUID| 25 | | PORT | 否 | 3000 | 监听端口 | 26 | | NEZHA_SERVER | 否 | |哪吒v1填写形式:nz.abc.com:8008 哪吒v0填写形式:nz.abc.com| 27 | | NEZHA_PORT | 否 | | 哪吒v1没有此变量,v0的agent端口| 28 | | NEZHA_KEY | 否 | | 哪吒v1的NZ_CLIENT_SECRET或v0的agent端口 | 29 | | NAME | 否 | | 节点名称前缀,例如:Glitch | 30 | | DOMAIN | 是 | | 项目分配的域名或已反代的域名,不包括https://前缀 | 31 | | AUTO_ACCESS | 否 | true | 是否开启自动访问保活,false为关闭,true为开启,需同时填写DOMAIN变量 | 32 | 33 | 34 | * js混肴地址:https://obfuscator.io 35 | 36 | -------------------------------------------------------------------------------- /cron.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | checkProcAlive() { 4 | local procname=$1 5 | if ps aux | grep "$procname" | grep -v "grep" >/dev/null; then 6 | return 0 7 | else 8 | return 1 9 | fi 10 | } 11 | username=$(whoami) 12 | 13 | nezha_check=false 14 | 15 | if ! checkProcAlive index.js ; then 16 | # echo "check" >> /home/$username/a.log 17 | cd /home/$username/public_html 18 | if [ -e "node_modules" ]; then 19 | nohup /opt/alt/alt-nodejs20/root/usr/bin/node index.js > out.log 2>&1 & 20 | fi 21 | fi 22 | 23 | if [ "$nezha_check" != "false" ]; then 24 | if ! checkProcAlive npm ; then 25 | cd /home/$username/public_html 26 | if [ -e "npm" ] && [ -e "config.yaml" ]; then 27 | nohup ./npm -c config.yaml > /dev/null 2>&1 & 28 | fi 29 | fi 30 | fi -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const http = require('http'); 3 | const fs = require('fs'); 4 | const axios = require('axios'); 5 | const net = require('net'); 6 | const { Buffer } = require('buffer'); 7 | const { exec, execSync } = require('child_process'); 8 | const { WebSocket, createWebSocketStream } = require('ws'); 9 | const UUID = process.env.UUID || '#UUID#'; // 运行哪吒v1,在不同的平台需要改UUID,否则会被覆盖 10 | const NEZHA_SERVER = process.env.NEZHA_SERVER || ''; // 哪吒v1填写形式:nz.abc.com:8008 哪吒v0填写形式:nz.abc.com 11 | const NEZHA_PORT = process.env.NEZHA_PORT || ''; // 哪吒v1没有此变量,v0的agent端口为{443,8443,2096,2087,2083,2053}其中之一时开启tls 12 | const NEZHA_KEY = process.env.NEZHA_KEY || ''; // v1的NZ_CLIENT_SECRET或v0的agent端口 13 | const DOMAIN = process.env.DOMAIN || '#DOMAIN#'; // 填写项目域名或已反代的域名,不带前缀,建议填已反代的域名 14 | const AUTO_ACCESS = process.env.AUTO_ACCESS || true; // 是否开启自动访问保活,false为关闭,true为开启,需同时填写DOMAIN变量 15 | const NAME = process.env.NAME || 'Vls'; // 节点名称 16 | const PORT = process.env.PORT || #PORT#; // http和ws服务端口 17 | 18 | let ISP = ''; 19 | const fetchMetaInfo = async () => { 20 | try { 21 | const response = await axios.get('https://speed.cloudflare.com/meta'); 22 | if (response.data) { 23 | const data = response.data; 24 | ISP = `${data.country}-${data.asOrganization}`.replace(/ /g, '_'); 25 | } 26 | } catch (error) { 27 | console.error('Failed to fetch Cloudflare metadata:', error.message); 28 | ISP = 'Unknown'; 29 | } 30 | }; 31 | 32 | // Execute the fetch at startup 33 | fetchMetaInfo(); 34 | 35 | const httpServer = http.createServer((req, res) => { 36 | if (req.url === '/') { 37 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 38 | res.end('Hello, World\n'); 39 | } else if (req.url === `/${UUID}`) { 40 | const vlessURL = `vless://${UUID}@www.visa.com.hk:443?encryption=none&security=tls&sni=${DOMAIN}&type=ws&host=${DOMAIN}&path=%2F#${NAME}-${ISP}`; 41 | 42 | const base64Content = Buffer.from(vlessURL).toString('base64'); 43 | exec('bash /HOME/cron.sh', (error, stdout, stderr) => { 44 | if (error) { 45 | console.error(`Error executing cron.sh: ${error}`); 46 | return; 47 | } 48 | if (stderr) { 49 | console.error(`cron.sh stderr: ${stderr}`); 50 | return; 51 | } 52 | console.log(`cron.sh output: ${stdout}`); 53 | }); 54 | 55 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 56 | res.end(base64Content + '\n'); 57 | } else { 58 | res.writeHead(404, { 'Content-Type': 'text/plain' }); 59 | res.end('Not Found\n'); 60 | } 61 | }); 62 | 63 | const wss = new WebSocket.Server({ server: httpServer }); 64 | const uuid = UUID.replace(/-/g, ""); 65 | wss.on('connection', ws => { 66 | // console.log("Connected successfully"); 67 | ws.once('message', msg => { 68 | const [VERSION] = msg; 69 | const id = msg.slice(1, 17); 70 | if (!id.every((v, i) => v == parseInt(uuid.substr(i * 2, 2), 16))) return; 71 | let i = msg.slice(17, 18).readUInt8() + 19; 72 | const port = msg.slice(i, i += 2).readUInt16BE(0); 73 | const ATYP = msg.slice(i, i += 1).readUInt8(); 74 | const host = ATYP == 1 ? msg.slice(i, i += 4).join('.') : 75 | (ATYP == 2 ? new TextDecoder().decode(msg.slice(i + 1, i += 1 + msg.slice(i, i + 1).readUInt8())) : 76 | (ATYP == 3 ? msg.slice(i, i += 16).reduce((s, b, i, a) => (i % 2 ? s.concat(a.slice(i - 1, i + 1)) : s), []).map(b => b.readUInt16BE(0).toString(16)).join(':') : '')); 77 | // console.log(`Connection from ${host}:${port}`); 78 | ws.send(new Uint8Array([VERSION, 0])); 79 | const duplex = createWebSocketStream(ws); 80 | net.connect({ host, port }, function () { 81 | this.write(msg.slice(i)); 82 | duplex.on('error', () => { }).pipe(this).on('error', () => { }).pipe(duplex); 83 | }).on('error', () => { }); 84 | }).on('error', () => { }); 85 | }); 86 | 87 | const getDownloadUrl = () => { 88 | const arch = os.arch(); 89 | if (arch === 'arm' || arch === 'arm64' || arch === 'aarch64') { 90 | if (!NEZHA_PORT) { 91 | return 'https://arm64.ssss.nyc.mn/v1'; 92 | } else { 93 | return 'https://arm64.ssss.nyc.mn/agent'; 94 | } 95 | } else { 96 | if (!NEZHA_PORT) { 97 | return 'https://amd64.ssss.nyc.mn/v1'; 98 | } else { 99 | return 'https://amd64.ssss.nyc.mn/agent'; 100 | } 101 | } 102 | }; 103 | 104 | const downloadFile = async () => { 105 | try { 106 | const url = getDownloadUrl(); 107 | // console.log(`Start downloading file from ${url}`); 108 | const response = await axios({ 109 | method: 'get', 110 | url: url, 111 | responseType: 'stream' 112 | }); 113 | 114 | const writer = fs.createWriteStream('npm'); 115 | response.data.pipe(writer); 116 | 117 | return new Promise((resolve, reject) => { 118 | writer.on('finish', () => { 119 | console.log('npm download successfully'); 120 | exec('chmod +x ./npm', (err) => { 121 | if (err) reject(err); 122 | resolve(); 123 | }); 124 | }); 125 | writer.on('error', reject); 126 | }); 127 | } catch (err) { 128 | throw err; 129 | } 130 | }; 131 | 132 | const runnz = async () => { 133 | await downloadFile(); 134 | let NEZHA_TLS = ''; 135 | let command = ''; 136 | 137 | console.log(`NEZHA_SERVER: ${NEZHA_SERVER}`); 138 | 139 | 140 | const checkNpmRunning = () => { 141 | try { 142 | const result = execSync('ps aux | grep "npm" | grep -v "grep"').toString(); 143 | return result.length > 0; 144 | } catch (error) { 145 | return false; 146 | } 147 | }; 148 | 149 | if (checkNpmRunning()) { 150 | console.log('npm is already running'); 151 | return; 152 | } 153 | 154 | if (NEZHA_SERVER && NEZHA_PORT && NEZHA_KEY) { 155 | const tlsPorts = ['443', '8443', '2096', '2087', '2083', '2053']; 156 | NEZHA_TLS = tlsPorts.includes(NEZHA_PORT) ? '--tls' : ''; 157 | command = `./npm -s ${NEZHA_SERVER}:${NEZHA_PORT} -p ${NEZHA_KEY} ${NEZHA_TLS} >/dev/null 2>&1 &`; 158 | } else if (NEZHA_SERVER && NEZHA_KEY) { 159 | if (!NEZHA_PORT) { 160 | // 检测哪吒是否开启TLS 161 | const port = NEZHA_SERVER.includes(':') ? NEZHA_SERVER.split(':').pop() : ''; 162 | const tlsPorts = new Set(['443', '8443', '2096', '2087', '2083', '2053']); 163 | const nezhatls = tlsPorts.has(port) ? 'true' : 'false'; 164 | const configYaml = ` 165 | client_secret: ${NEZHA_KEY} 166 | debug: false 167 | disable_auto_update: true 168 | disable_command_execute: false 169 | disable_force_update: true 170 | disable_nat: false 171 | disable_send_query: false 172 | gpu: false 173 | insecure_tls: false 174 | ip_report_period: 1800 175 | report_delay: 1 176 | server: ${NEZHA_SERVER} 177 | skip_connection_count: false 178 | skip_procs_count: false 179 | temperature: false 180 | tls: ${nezhatls} 181 | use_gitee_to_upgrade: false 182 | use_ipv6_country_code: false 183 | uuid: ${UUID}`; 184 | 185 | if (!fs.existsSync('config.yaml')) { 186 | fs.writeFileSync('config.yaml', configYaml); 187 | } 188 | } 189 | command = ` ./npm -c config.yaml >/dev/null 2>&1 &`; 190 | } else { 191 | console.log('NEZHA variable is empty, skip running'); 192 | return; 193 | } 194 | 195 | try { 196 | exec(command, { 197 | shell: '/bin/bash' 198 | }); 199 | console.log('npm is running'); 200 | } catch (error) { 201 | console.error(`npm running error: ${error}`); 202 | } 203 | }; 204 | 205 | async function addAccessTask() { 206 | if (!AUTO_ACCESS) return; 207 | try { 208 | if (!DOMAIN) { 209 | console.log('URL is empty. Skip Adding Automatic Access Task'); 210 | return; 211 | } else { 212 | const fullURL = `https://${DOMAIN}/${UUID}`; 213 | axios.post('https://urlcheck.fk.ddns-ip.net/add-url', { 214 | url: fullURL 215 | }, { 216 | headers: { 217 | 'Content-Type': 'application/json' 218 | } 219 | }) 220 | .then(response => { 221 | console.log('Automatic Access Task added successfully:', response.data); 222 | }) 223 | .catch(error => { 224 | console.error('Error sending request:', error.message); 225 | }); 226 | } 227 | } catch (error) { 228 | console.error('Error added Task:', error.message); 229 | } 230 | } 231 | 232 | const delFiles = () => { 233 | fs.unlink('npm', () => { }); 234 | fs.unlink('config.yaml', () => { }); 235 | }; 236 | 237 | httpServer.listen(PORT, () => { 238 | runnz(); 239 | // setTimeout(() => { 240 | // delFiles(); 241 | // }, 30000); 242 | addAccessTask(); 243 | console.log(`Server is running on port ${PORT}`); 244 | }); 245 | -------------------------------------------------------------------------------- /m_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | echo "Error: 参数为你的域名!" 5 | echo "Usage: $0 domain" 6 | exit 1 7 | fi 8 | 9 | domain=$1 10 | username=$(whoami) 11 | random_port=$((RANDOM % 40001 + 20000)) 12 | 13 | 14 | read -p "输入UUID:" uuid 15 | if [ -z "$uuid" ]; then 16 | echo "Error: UUID不能为空!" 17 | exit 1 18 | fi 19 | echo "你输入的UUID: $uuid" 20 | read -p "是否安装探针? [y/n] [n]:" input 21 | input=${input:-n} 22 | if [ "$input" != "n" ]; then 23 | read -p "输入NEZHA_SERVER(哪吒v1填写形式:nz.abc.com:8008,哪吒v0填写形式:nz.abc.com):" nezha_server 24 | if [ -z "$nezha_server" ]; then 25 | echo "Error: nezha_server不能为空!" 26 | exit 1 27 | fi 28 | read -p "输入NEZHA_PORT( v1面板此处按回车, v0的agent端口为{443,8443,2096,2087,2083,2053}其中之一时开启tls):" nezha_port 29 | nezha_port=${nezha_port:-""} 30 | read -p "输入NEZHA_KEY(v1的NZ_CLIENT_SECRET或v0的agent端口):" nezha_key 31 | if [ -z "$nezha_key" ]; then 32 | echo "Error: nezha_key不能为空!" 33 | exit 1 34 | fi 35 | fi 36 | echo "你输入的nezha_server: $nezha_server, nezha_port:$nezha_port, nezha_key:$nezha_key" 37 | 38 | sed -i "s/NEZHA_SERVER || ''/NEZHA_SERVER || '$nezha_server'/g" "/home/$username/domains/$domain/public_html/index.js" 39 | sed -i "s/NEZHA_PORT || ''/NEZHA_PORT || '$nezha_port'/g" "/home/$username/domains/$domain/public_html/index.js" 40 | sed -i "s/NEZHA_KEY || ''/NEZHA_KEY || '$nezha_key'/g" "/home/$username/domains/$domain/public_html/index.js" 41 | sed -i "s/#DOMAIN#/$domain/g" "/home/$username/domains/$domain/public_html/index.js" 42 | sed -i "s/#PORT#;/$random_port;/g" "/home/$username/domains/$domain/public_html/index.js" 43 | sed -i "s/#UUID#/$uuid/g" "/home/$username/domains/$domain/public_html/index.js" 44 | sed -i "s|/HOME|/home/$username|g" "/home/$username/domains/$domain/public_html/index.js" 45 | 46 | 47 | echo "安装完毕" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-ws", 3 | "version": "1.0.0", 4 | "description": "Node.js Server", 5 | "main": "index.js", 6 | "author": "eoovve", 7 | "repository": "https://github.com/eoovve/node-ws", 8 | "license": "MIT", 9 | "private": false, 10 | "scripts": { 11 | "start": "node index.js" 12 | }, 13 | "dependencies": { 14 | "ws": "^8.14.2", 15 | "axios": "^1.6.2", 16 | "mime-types": "^2.1.35" 17 | }, 18 | "engines": { 19 | "node": ">=14" 20 | } 21 | } -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | echo "Error: 参数为你的域名!" 5 | echo "Usage: $0 domain" 6 | exit 1 7 | fi 8 | 9 | domain=$1 10 | username=$(whoami) 11 | random_port=$((RANDOM % 40001 + 20000)) 12 | 13 | 14 | echo "to /home/$username/domains/$domain/public_html/index.js" 15 | curl -s -o "/home/$username/domains/$domain/public_html/index.js" "https://raw.githubusercontent.com/frankiejun/node-ws/main/index.js" 16 | if [ $? -ne 0 ]; then 17 | echo "Error: 下载脚本 index.js 失败!" 18 | exit 1 19 | fi 20 | curl -s -o "/home/$username/cron.sh" "https://raw.githubusercontent.com/frankiejun/node-ws/main/cron.sh" 21 | if [ $? -ne 0 ]; then 22 | echo "Error: 下载脚本 cron.sh 失败!" 23 | exit 1 24 | fi 25 | chmod +x /home/$username/cron.sh 26 | 27 | 28 | read -p "输入UUID:" uuid 29 | if [ -z "$uuid" ]; then 30 | echo "Error: UUID不能为空!" 31 | exit 1 32 | fi 33 | echo "你输入的UUID: $uuid" 34 | read -p "是否安装探针? [y/n] [n]:" input 35 | input=${input:-n} 36 | if [ "$input" != "n" ]; then 37 | read -p "输入NEZHA_SERVER(哪吒v1填写形式:nz.abc.com:8008,哪吒v0填写形式:nz.abc.com):" nezha_server 38 | if [ -z "$nezha_server" ]; then 39 | echo "Error: nezha_server不能为空!" 40 | exit 1 41 | fi 42 | read -p "输入NEZHA_PORT( v1面板此处按回车, v0的agent端口为{443,8443,2096,2087,2083,2053}其中之一时开启tls):" nezha_port 43 | nezha_port=${nezha_port:-""} 44 | read -p "输入NEZHA_KEY(v1的NZ_CLIENT_SECRET或v0的agent端口):" nezha_key 45 | if [ -z "$nezha_key" ]; then 46 | echo "Error: nezha_key不能为空!" 47 | exit 1 48 | fi 49 | fi 50 | echo "你输入的nezha_server: $nezha_server, nezha_port:$nezha_port, nezha_key:$nezha_key" 51 | 52 | 53 | 54 | sed -i "s/NEZHA_SERVER || ''/NEZHA_SERVER || '$nezha_server'/g" "/home/$username/domains/$domain/public_html/index.js" 55 | sed -i "s/NEZHA_PORT || ''/NEZHA_PORT || '$nezha_port'/g" "/home/$username/domains/$domain/public_html/index.js" 56 | sed -i "s/NEZHA_KEY || ''/NEZHA_KEY || '$nezha_key'/g" "/home/$username/domains/$domain/public_html/index.js" 57 | sed -i "s/#DOMAIN#/$domain/g" "/home/$username/domains/$domain/public_html/index.js" 58 | sed -i "s/#PORT#;/$random_port;/g" "/home/$username/domains/$domain/public_html/index.js" 59 | sed -i "s/#UUID#/$uuid/g" "/home/$username/domains/$domain/public_html/index.js" 60 | sed -i "s|/HOME|/home/$username|g" "/home/$username/domains/$domain/public_html/index.js" 61 | 62 | if [ "$input" = "y" ]; then 63 | sed -i "s/nezha_check=false/nezha_check=true/g" "/home/$username/cron.sh" 64 | fi 65 | 66 | 67 | cat > "/home/$username/domains/$domain/public_html/package.json" << EOF 68 | { 69 | "name": "node-ws", 70 | "version": "1.0.0", 71 | "description": "Node.js Server", 72 | "main": "index.js", 73 | "author": "fkj", 74 | "repository": "https://github.com/frankiejun/node-ws", 75 | "license": "MIT", 76 | "private": false, 77 | "scripts": { 78 | "start": "node index.js" 79 | }, 80 | "dependencies": { 81 | "ws": "^8.14.2", 82 | "axios": "^1.6.2", 83 | "mime-types": "^2.1.35" 84 | }, 85 | "engines": { 86 | "node": ">=14" 87 | } 88 | } 89 | EOF 90 | 91 | echo "*/1 * * * * cd /home/$username/public_html && /home/$username/cron.sh" > ./mycron 92 | crontab ./mycron >/dev/null 2>&1 93 | rm ./mycron 94 | 95 | echo "安装完毕" --------------------------------------------------------------------------------