├── README.md ├── entrypoint.sh ├── index.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # Xray + Argo for Express.js PaaS 2 | 3 | 在没有公网的平台挖啊挖啊挖,Argo打通各式服务连接千万家。---为 JS 平台而生 4 | 5 | * * * 6 | 7 | # 目录 8 | 9 | - [项目特点](README.md#项目特点) 10 | - [部署](README.md#部署) 11 | - [在 Glitch 部署重点](README.md#在-glitch-部署重点) 12 | - [在 Daki 部署重点](README.md#在-daki-部署重点) 13 | - [ttyd webssh / filebrowser webftp 的部署](README.md#ttyd-webssh--filebrowser-webftp-的部署) 14 | - [鸣谢下列作者的文章和项目](README.md#鸣谢下列作者的文章和项目) 15 | - [免责声明](README.md#免责声明) 16 | 17 | * * * 18 | 19 | ## 项目特点: 20 | * 本项目用于在 Express.js PaaS 平台上部署 Xray,采用的方案为 Argo + Xray + WebSocket + TLS 21 | * 解锁 ChatGPT 22 | * 在浏览器查看系统各项信息,方便直观 23 | * 使用 CloudFlare 的 Argo 隧道,直接优选 + 隧道,CDN 不用再做 workers 24 | * 回流分流,同时支持 Xray 4 种主流协议: vless / vmess / trojan / shadowsocks 25 | * vmess 和 vless 的 uuid,trojan 和 shadowsocks 的 password,各协议的 ws 路径既可以自定义,又或者使用默认值 26 | * 集成哪吒探针,可以自由选择是否安装 27 | * 前端 js 定时保活,会玩的用户可以根据具体情况修改间隔时间 28 | * 节点信息以 V2rayN / Clash / 小火箭 链接方式输出 29 | * Xray 文件重新编译官方文件增加隐秘性,修改了运行时的显示信息,文件为: https://github.com/XTLS/Xray-core/blob/main/core/core.go 30 | * 可以使用浏览器使用 webssh 和 webftp,更方便管理系统 31 | 32 | image 33 | 34 | ## 部署: 35 | 36 | ### PaaS 平台用到的变量: 37 | 38 | * 在 `server.js` 文件的第1、2行修改查询网页的用户名和密码 39 | | 变量名 | 是否必须 | 默认值 | 备注 | 40 | | ------------ | ------ | ------ | ------ | 41 | | WEB_USERNAME | 是 | admin | 网页的用户名 | 42 | | WEB_PASSWORD | 是 | password | 网页的密码 | 43 | 44 | image 45 | 46 | * 在 `entrypoint.sh` 文件的前面 4-15 行修改;访问页面的认证在 `server.js` 文件的第1、2行修改必填 47 | | 变量名 | 是否必须 | 默认值 | 备注 | 48 | | ------------ | ------ | ------ | ------ | 49 | | UUID | 否 | de04add9-5c68-8bab-950c-08cd5320df18 | 可在线生成 https://www.zxgj.cn/g/uuid | 50 | | WSPATH | 否 | argo | 勿以 / 开头,各协议路径为 `/WSPATH-协议`,如 `/argo-vless`,`/argo-vmess`,`/argo-trojan`,`/argo-shadowsocks` | 51 | | NEZHA_SERVER | 否 | | 哪吒探针服务端的 IP 或域名 | 52 | | NEZHA_PORT | 否 | | 哪吒探针服务端的端口 | 53 | | NEZHA_KEY | 否 | | 哪吒探针客户端专用 Key | 54 | | NEZHA_TLS | 否 | | 哪吒探针是否启用 SSL/TLS 加密 ,如不启用请删除,如要启用填"1" | 55 | | ARGO_AUTH | 否 | | Argo 的 Token 或者 json 值,其中 json 可以通过以下网站,在不需绑卡的情况下轻松获取: https://fscarmen.cloudflare.now.cc/ | 56 | | ARGO_DOMAIN | 否 | | Argo 的域名,须与 ARGO_DOMAIN 必需一起填了才能生效 | 57 | | SSH_DOMAIN | 否 | | webssh 的域名,用户名和密码就是 | 58 | | FTP_DOMAIN | 否 | | webftp 的域名,用户名和密码就是 | 59 | 60 | 61 | image 62 | 63 | * 需要应用的 js 64 | | 命令 | 说明 | 65 | | ---- |------ | 66 | | /list | 查看节点数据 | 67 | | /status | 查看后台进程 | 68 | | /listen | 查看后台监听端口 | 69 | | /test | 测试是否为只读系统 | 70 | 71 | ## 在 Glitch 部署重点 72 | 73 | 这里只作重点的展示,更详细可以参考项目: https://github.com/fscarmen2/X-for-Glitch 74 | 75 | image 76 | 77 | image 78 | 79 | image 80 | 81 | ## 在 Daki 部署重点 82 | 83 | image 84 | 85 | image 86 | 87 | image 88 | 89 | image 90 | 91 | image 92 | 93 | image 94 | 95 | image 96 | 97 | ## ttyd webssh / filebrowser webftp 的部署 98 | 99 | * 原理 100 | ``` 101 | +---------+ argo +---------+ http +--------+ ssh +-----------+ 102 | | browser | <==========> | CF edge | <==========> | ttyd | <=======> | ssh server| 103 | +---------+ argo +---------+ websocket +--------+ ssh +-----------+ 104 | 105 | +---------+ argo +---------+ http +--------------+ ftp +-----------+ 106 | | browser | <==========> | CF edge | <==========> | filebrowser | <=======> | ftp server| 107 | +---------+ argo +---------+ websocket +--------------+ ftp +-----------+ 108 | 109 | ``` 110 | 111 | * 使用 Json 方式建的隧道 112 | 113 | image 114 | 115 | image 116 | 117 | image 118 | 119 | image 120 | 121 | ## 鸣谢下列作者的文章和项目: 122 | * 前端 JS 在大佬 Nike Jeff 的项目 基础上,为了通用性和扩展功能作修改,https://github.com/hrzyang/glitch-trojan 123 | * 后端全部原创,如转载须注明来源。 124 | 125 | ## 免责声明: 126 | * 本程序仅供学习了解, 非盈利目的,请于下载后 24 小时内删除, 不得用作任何商业用途, 文字、数据及图片均有所属版权, 如转载须注明来源。 127 | * 使用本程序必循遵守部署免责声明。使用本程序必循遵守部署服务器所在地、所在国家和用户所在国家的法律法规, 程序作者不对使用者任何不当行为负责。 -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 设置各变量,WS 路径前缀。(注意:伪装路径不需要 / 符号开始,为避免不必要的麻烦,请不要使用特殊符号.) 4 | WSPATH=${WSPATH:-'argo'} 5 | UUID=${UUID:-'de04add9-5c68-8bab-950c-08cd5320df18'} 6 | WEB_USERNAME=${WEB_USERNAME:-'admin'} 7 | WEB_PASSWORD=${WEB_PASSWORD:-'password'} 8 | 9 | # 哪吒4个参数,ssl/tls 看是否需要,不需要的话可以留空,删除或在这4行最前面加 # 以注释 10 | NEZHA_SERVER="$NEZHA_SERVER" 11 | NEZHA_PORT="$NEZHA_PORT" 12 | NEZHA_KEY="$NEZHA_KEY" 13 | NEZHA_TLS="$NEZHA_TLS" 14 | 15 | # Argo 固定域名隧道的两个参数,这个可以填 Json 内容或 Token 内容,不需要的话可以留空,删除或在这三行最前面加 # 以注释 16 | ARGO_AUTH='' 17 | ARGO_DOMAIN="$ARGO_DOMAIN" 18 | 19 | # ttyd / filebrowser argo 域名 20 | SSH_DOMAIN="$SSH_AUTH" 21 | FTP_DOMAIN="$FTP_AUTH" 22 | 23 | # 安装系统依赖 24 | check_dependencies() { 25 | DEPS_CHECK=("wget" "unzip" "ss" "tar") 26 | DEPS_INSTALL=(" wget" " unzip" " iproute2" "tar") 27 | for ((i=0;i<${#DEPS_CHECK[@]};i++)); do [[ ! $(type -p ${DEPS_CHECK[i]}) ]] && DEPS+=${DEPS_INSTALL[i]}; done 28 | [ -n "$DEPS" ] && { apt-get update >/dev/null 2>&1; apt-get install -y $DEPS >/dev/null 2>&1; } 29 | } 30 | 31 | # 生成 X 配置文件 32 | generate_config() { 33 | cat > config.json << EOF 34 | { 35 | "log":{ 36 | "access":"/dev/null", 37 | "error":"/dev/null", 38 | "loglevel":"none" 39 | }, 40 | "inbounds":[ 41 | { 42 | "port":8080, 43 | "protocol":"vless", 44 | "settings":{ 45 | "clients":[ 46 | { 47 | "id":"${UUID}", 48 | "flow":"xtls-rprx-vision" 49 | } 50 | ], 51 | "decryption":"none", 52 | "fallbacks":[ 53 | { 54 | "dest":3001 55 | }, 56 | { 57 | "path":"/${WSPATH}-vless", 58 | "dest":3002 59 | }, 60 | { 61 | "path":"/${WSPATH}-vmess", 62 | "dest":3003 63 | }, 64 | { 65 | "path":"/${WSPATH}-trojan", 66 | "dest":3004 67 | }, 68 | { 69 | "path":"/${WSPATH}-shadowsocks", 70 | "dest":3005 71 | } 72 | ] 73 | }, 74 | "streamSettings":{ 75 | "network":"tcp" 76 | } 77 | }, 78 | { 79 | "port":3001, 80 | "listen":"127.0.0.1", 81 | "protocol":"vless", 82 | "settings":{ 83 | "clients":[ 84 | { 85 | "id":"${UUID}" 86 | } 87 | ], 88 | "decryption":"none" 89 | }, 90 | "streamSettings":{ 91 | "network":"ws", 92 | "security":"none" 93 | } 94 | }, 95 | { 96 | "port":3002, 97 | "listen":"127.0.0.1", 98 | "protocol":"vless", 99 | "settings":{ 100 | "clients":[ 101 | { 102 | "id":"${UUID}", 103 | "level":0 104 | } 105 | ], 106 | "decryption":"none" 107 | }, 108 | "streamSettings":{ 109 | "network":"ws", 110 | "security":"none", 111 | "wsSettings":{ 112 | "path":"/${WSPATH}-vless" 113 | } 114 | }, 115 | "sniffing":{ 116 | "enabled":true, 117 | "destOverride":[ 118 | "http", 119 | "tls", 120 | "quic" 121 | ], 122 | "metadataOnly":false 123 | } 124 | }, 125 | { 126 | "port":3003, 127 | "listen":"127.0.0.1", 128 | "protocol":"vmess", 129 | "settings":{ 130 | "clients":[ 131 | { 132 | "id":"${UUID}", 133 | "alterId":0 134 | } 135 | ] 136 | }, 137 | "streamSettings":{ 138 | "network":"ws", 139 | "wsSettings":{ 140 | "path":"/${WSPATH}-vmess" 141 | } 142 | }, 143 | "sniffing":{ 144 | "enabled":true, 145 | "destOverride":[ 146 | "http", 147 | "tls", 148 | "quic" 149 | ], 150 | "metadataOnly":false 151 | } 152 | }, 153 | { 154 | "port":3004, 155 | "listen":"127.0.0.1", 156 | "protocol":"trojan", 157 | "settings":{ 158 | "clients":[ 159 | { 160 | "password":"${UUID}" 161 | } 162 | ] 163 | }, 164 | "streamSettings":{ 165 | "network":"ws", 166 | "security":"none", 167 | "wsSettings":{ 168 | "path":"/${WSPATH}-trojan" 169 | } 170 | }, 171 | "sniffing":{ 172 | "enabled":true, 173 | "destOverride":[ 174 | "http", 175 | "tls", 176 | "quic" 177 | ], 178 | "metadataOnly":false 179 | } 180 | }, 181 | { 182 | "port":3005, 183 | "listen":"127.0.0.1", 184 | "protocol":"shadowsocks", 185 | "settings":{ 186 | "clients":[ 187 | { 188 | "method":"chacha20-ietf-poly1305", 189 | "password":"${UUID}" 190 | } 191 | ], 192 | "decryption":"none" 193 | }, 194 | "streamSettings":{ 195 | "network":"ws", 196 | "wsSettings":{ 197 | "path":"/${WSPATH}-shadowsocks" 198 | } 199 | }, 200 | "sniffing":{ 201 | "enabled":true, 202 | "destOverride":[ 203 | "http", 204 | "tls", 205 | "quic" 206 | ], 207 | "metadataOnly":false 208 | } 209 | } 210 | ], 211 | "dns":{ 212 | "servers":[ 213 | "https+local://8.8.8.8/dns-query" 214 | ] 215 | }, 216 | "outbounds":[ 217 | { 218 | "protocol":"freedom" 219 | }, 220 | { 221 | "tag":"WARP", 222 | "protocol":"wireguard", 223 | "settings":{ 224 | "secretKey":"YFYOAdbw1bKTHlNNi+aEjBM3BO7unuFC5rOkMRAz9XY=", 225 | "address":[ 226 | "172.16.0.2/32", 227 | "2606:4700:110:8a36:df92:102a:9602:fa18/128" 228 | ], 229 | "peers":[ 230 | { 231 | "publicKey":"bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=", 232 | "allowedIPs":[ 233 | "0.0.0.0/0", 234 | "::/0" 235 | ], 236 | "endpoint":"162.159.193.10:2408" 237 | } 238 | ], 239 | "reserved":[78, 135, 76], 240 | "mtu":1280 241 | } 242 | } 243 | ], 244 | "routing":{ 245 | "domainStrategy":"AsIs", 246 | "rules":[ 247 | { 248 | "type":"field", 249 | "domain":[ 250 | "domain:openai.com", 251 | "domain:ai.com" 252 | ], 253 | "outboundTag":"WARP" 254 | } 255 | ] 256 | } 257 | } 258 | EOF 259 | } 260 | 261 | generate_argo() { 262 | cat > argo.sh << ABC 263 | #!/usr/bin/env bash 264 | 265 | ARGO_AUTH=${ARGO_AUTH} 266 | ARGO_DOMAIN=${ARGO_DOMAIN} 267 | SSH_DOMAIN=${SSH_DOMAIN} 268 | FTP_DOMAIN=${FTP_DOMAIN} 269 | 270 | # 下载并运行 Argo 271 | check_file() { 272 | [ ! -e cloudflared ] && wget -O cloudflared https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 && chmod +x cloudflared 273 | } 274 | 275 | run() { 276 | if [[ -n "\${ARGO_AUTH}" && -n "\${ARGO_DOMAIN}" ]]; then 277 | if [[ "\$ARGO_AUTH" =~ TunnelSecret ]]; then 278 | echo "\$ARGO_AUTH" | sed 's@{@{"@g;s@[,:]@"\0"@g;s@}@"}@g' > tunnel.json 279 | cat > tunnel.yml << EOF 280 | tunnel: \$(sed "s@.*TunnelID:\(.*\)}@\1@g" <<< "\$ARGO_AUTH") 281 | credentials-file: $(pwd)/tunnel.json 282 | protocol: http2 283 | 284 | ingress: 285 | - hostname: \$ARGO_DOMAIN 286 | service: http://localhost:8080 287 | EOF 288 | [ -n "\${SSH_DOMAIN}" ] && cat >> tunnel.yml << EOF 289 | - hostname: \$SSH_DOMAIN 290 | service: http://localhost:2222 291 | EOF 292 | [ -n "\${FTP_DOMAIN}" ] && cat >> tunnel.yml << EOF 293 | - hostname: \$FTP_DOMAIN 294 | service: http://localhost:3333 295 | EOF 296 | cat >> tunnel.yml << EOF 297 | - service: http_status:404 298 | EOF 299 | nohup ./cloudflared tunnel --edge-ip-version auto --config tunnel.yml run 2>/dev/null 2>&1 & 300 | elif [[ \$ARGO_AUTH =~ ^[A-Z0-9a-z=]{120,250}$ ]]; then 301 | nohup ./cloudflared tunnel --edge-ip-version auto --protocol http2 run --token ${ARGO_AUTH} 2>/dev/null 2>&1 & 302 | fi 303 | else 304 | nohup ./cloudflared tunnel --edge-ip-version auto --protocol http2 --no-autoupdate --url http://localhost:8080 2>/dev/null 2>&1 & 305 | sleep 5 306 | local LOCALHOST=\$(ss -nltp | grep '"cloudflared"' | awk '{print \$4}') 307 | ARGO_DOMAIN=\$(wget -qO- http://\$LOCALHOST/quicktunnel | cut -d\" -f4) 308 | fi 309 | } 310 | 311 | export_list() { 312 | VMESS="{ \"v\": \"2\", \"ps\": \"Argo-Vmess\", \"add\": \"icook.hk\", \"port\": \"443\", \"id\": \"${UUID}\", \"aid\": \"0\", \"scy\": \"none\", \"net\": \"ws\", \"type\": \"none\", \"host\": \"\${ARGO_DOMAIN}\", \"path\": \"/${WSPATH}-vmess?ed=2048\", \"tls\": \"tls\", \"sni\": \"\${ARGO_DOMAIN}\", \"alpn\": \"\" }" 313 | cat > list << EOF 314 | ******************************************* 315 | V2-rayN: 316 | ---------------------------- 317 | vless://${UUID}@icook.hk:443?encryption=none&security=tls&sni=\${ARGO_DOMAIN}&type=ws&host=\${ARGO_DOMAIN}&path=%2F${WSPATH}-vless?ed=2048#Argo-Vless 318 | ---------------------------- 319 | vmess://\$(echo \$VMESS | base64 -w0) 320 | ---------------------------- 321 | trojan://${UUID}@icook.hk:443?security=tls&sni=\${ARGO_DOMAIN}&type=ws&host=\${ARGO_DOMAIN}&path=%2F${WSPATH}-trojan?ed=2048#Argo-Trojan 322 | ---------------------------- 323 | ss://$(echo "chacha20-ietf-poly1305:${UUID}@icook.hk:443" | base64 -w0)@icook.hk:443#Argo-Shadowsocks 324 | 由于该软件导出的链接不全,请自行处理如下: 传输协议: WS , 伪装域名: \${ARGO_DOMAIN} ,路径: /${WSPATH}-shadowsocks?ed=2048 , 传输层安全: tls , sni: \${ARGO_DOMAIN} 325 | ******************************************* 326 | 小火箭: 327 | ---------------------------- 328 | vless://${UUID}@icook.hk:443?encryption=none&security=tls&type=ws&host=\${ARGO_DOMAIN}&path=/${WSPATH}-vless?ed=2048&sni=\${ARGO_DOMAIN}#Argo-Vless 329 | ---------------------------- 330 | vmess://$(echo "none:${UUID}@icook.hk:443" | base64 -w0)?remarks=Argo-Vmess&obfsParam=\${ARGO_DOMAIN}&path=/${WSPATH}-vmess?ed=2048&obfs=websocket&tls=1&peer=\${ARGO_DOMAIN}&alterId=0 331 | ---------------------------- 332 | trojan://${UUID}@icook.hk:443?peer=\${ARGO_DOMAIN}&plugin=obfs-local;obfs=websocket;obfs-host=\${ARGO_DOMAIN};obfs-uri=/${WSPATH}-trojan?ed=2048#Argo-Trojan 333 | ---------------------------- 334 | ss://$(echo "chacha20-ietf-poly1305:${UUID}@icook.hk:443" | base64 -w0)?obfs=wss&obfsParam=\${ARGO_DOMAIN}&path=/${WSPATH}-shadowsocks?ed=2048#Argo-Shadowsocks 335 | ******************************************* 336 | Clash: 337 | ---------------------------- 338 | - {name: Argo-Vless, type: vless, server: icook.hk, port: 443, uuid: ${UUID}, tls: true, servername: \${ARGO_DOMAIN}, skip-cert-verify: false, network: ws, ws-opts: {path: /${WSPATH}-vless?ed=2048, headers: { Host: \${ARGO_DOMAIN}}}, udp: true} 339 | ---------------------------- 340 | - {name: Argo-Vmess, type: vmess, server: icook.hk, port: 443, uuid: ${UUID}, alterId: 0, cipher: none, tls: true, skip-cert-verify: true, network: ws, ws-opts: {path: /${WSPATH}-vmess?ed=2048, headers: {Host: \${ARGO_DOMAIN}}}, udp: true} 341 | ---------------------------- 342 | - {name: Argo-Trojan, type: trojan, server: icook.hk, port: 443, password: ${UUID}, udp: true, tls: true, sni: \${ARGO_DOMAIN}, skip-cert-verify: false, network: ws, ws-opts: { path: /${WSPATH}-trojan?ed=2048, headers: { Host: \${ARGO_DOMAIN} } } } 343 | ---------------------------- 344 | - {name: Argo-Shadowsocks, type: ss, server: icook.hk, port: 443, cipher: chacha20-ietf-poly1305, password: ${UUID}, plugin: v2ray-plugin, plugin-opts: { mode: websocket, host: \${ARGO_DOMAIN}, path: /${WSPATH}-shadowsocks?ed=2048, tls: true, skip-cert-verify: false, mux: false } } 345 | ******************************************* 346 | EOF 347 | cat list 348 | } 349 | check_file 350 | run 351 | export_list 352 | ABC 353 | } 354 | 355 | generate_nezha() { 356 | cat > nezha.sh << EOF 357 | #!/usr/bin/env bash 358 | 359 | # 哪吒的4个参数 360 | NEZHA_SERVER="$NEZHA_SERVER" 361 | NEZHA_PORT="$NEZHA_PORT" 362 | NEZHA_KEY="$NEZHA_KEY" 363 | NEZHA_TLS="$NEZHA_TLS" 364 | 365 | # 检测是否已运行 366 | check_run() { 367 | [[ \$(pgrep -laf nezha-agent) ]] && echo "哪吒客户端正在运行中!" && exit 368 | } 369 | 370 | # 三个变量不全则不安装哪吒客户端 371 | check_variable() { 372 | [[ -z "\${NEZHA_SERVER}" || -z "\${NEZHA_PORT}" || -z "\${NEZHA_KEY}" ]] && exit 373 | } 374 | 375 | # 下载最新版本 Nezha Agent 376 | download_agent() { 377 | if [ ! -e nezha-agent ]; then 378 | URL=\$(wget -qO- -4 "https://api.github.com/repos/nezhahq/agent/releases/latest" | grep -o "https.*linux_amd64.zip") 379 | URL=\${URL:-https://github.com/nezhahq/agent/releases/download/v0.15.6/nezha-agent_linux_amd64.zip} 380 | wget -t 2 -T 10 -N \${URL} 381 | unzip -qod ./ nezha-agent_linux_amd64.zip && rm -f nezha-agent_linux_amd64.zip 382 | fi 383 | } 384 | 385 | # 运行客户端 386 | run() { 387 | TLS=\${NEZHA_TLS:+'--tls'} 388 | [[ ! \$PROCESS =~ nezha-agent && -e nezha-agent ]] && ./nezha-agent -s \${NEZHA_SERVER}:\${NEZHA_PORT} -p \${NEZHA_KEY} \${TLS} 2>&1 & 389 | } 390 | 391 | check_run 392 | check_variable 393 | download_agent 394 | run 395 | EOF 396 | } 397 | 398 | generate_ttyd() { 399 | cat > ttyd.sh << EOF 400 | #!/usr/bin/env bash 401 | 402 | # ttyd 三个参数 403 | WEB_USERNAME=${WEB_USERNAME} 404 | WEB_PASSWORD=${WEB_PASSWORD} 405 | SSH_DOMAIN=${SSH_DOMAIN} 406 | 407 | # 检测是否已运行 408 | check_run() { 409 | [[ \$(pgrep -lafx ttyd) ]] && echo "ttyd 正在运行中" && exit 410 | } 411 | 412 | # ssh argo 域名不设置,则不安装 ttyd 服务端 413 | check_variable() { 414 | [ -z "\${SSH_DOMAIN}" ] && exit 415 | } 416 | 417 | # 下载最新版本 ttyd 418 | download_ttyd() { 419 | if [ ! -e ttyd ]; then 420 | URL=\$(wget -qO- "https://api.github.com/repos/tsl0922/ttyd/releases/latest" | grep -o "https.*x86_64") 421 | URL=\${URL:-https://github.com/tsl0922/ttyd/releases/download/1.7.3/ttyd.x86_64} 422 | wget -O ttyd \${URL} 423 | chmod +x ttyd 424 | fi 425 | } 426 | 427 | # 运行 ttyd 服务端 428 | run() { 429 | [ -e ttyd ] && nohup ./ttyd -c \${WEB_USERNAME}:\${WEB_PASSWORD} -p 2222 bash >/dev/null 2>&1 & 430 | } 431 | 432 | check_run 433 | check_variable 434 | download_ttyd 435 | run 436 | EOF 437 | } 438 | 439 | generate_filebrowser () { 440 | cat > filebrowser.sh << EOF 441 | #!/usr/bin/env bash 442 | 443 | # filebrowser 三个参数 444 | WEB_USERNAME=${WEB_USERNAME} 445 | WEB_PASSWORD=${WEB_PASSWORD} 446 | FTP_DOMAIN=${FTP_DOMAIN} 447 | 448 | # 检测是否已运行 449 | check_run() { 450 | [[ \$(pgrep -lafx filebrowser) ]] && echo "filebrowser 正在运行中" && exit 451 | } 452 | 453 | # 若 ftp argo 域名不设置,则不安装 filebrowser 454 | check_variable() { 455 | [ -z "\${FTP_DOMAIN}" ] && exit 456 | } 457 | 458 | # 下载最新版本 filebrowser 459 | download_filebrowser() { 460 | if [ ! -e filebrowser ]; then 461 | URL=\$(wget -qO- "https://api.github.com/repos/filebrowser/filebrowser/releases/latest" | grep -o "https.*linux-amd64.*gz") 462 | URL=\${URL:-https://github.com/filebrowser/filebrowser/releases/download/v2.23.0/linux-amd64-filebrowser.tar.gz} 463 | wget -O filebrowser.tar.gz \${URL} 464 | tar xzvf filebrowser.tar.gz filebrowser 465 | rm -f filebrowser.tar.gz 466 | chmod +x filebrowser 467 | fi 468 | } 469 | 470 | # 运行 filebrowser 服务端 471 | run() { 472 | PASSWORD_HASH=\$(./filebrowser hash \$WEB_PASSWORD) 473 | [ -e filebrowser ] && nohup ./filebrowser --port 3333 --username \${WEB_USERNAME} --password "\${PASSWORD_HASH}" >/dev/null 2>&1 & 474 | } 475 | 476 | check_run 477 | check_variable 478 | download_filebrowser 479 | run 480 | EOF 481 | } 482 | 483 | generate_config 484 | generate_argo 485 | generate_nezha 486 | generate_ttyd 487 | generate_filebrowser 488 | 489 | [ -e nezha.sh ] && bash nezha.sh 490 | [ -e argo.sh ] && bash argo.sh 491 | [ -e ttyd.sh ] && bash ttyd.sh 492 | [ -e filebrowser.sh ] && bash filebrowser.sh 493 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const username = process.env.WEB_USERNAME || "admin"; 2 | const password = process.env.WEB_PASSWORD || "password"; 3 | const url = "http://127.0.0.1"; 4 | const port = process.env.PORT || 3000; /* 当容器平台分配不规则端口时,此处需修改为分配端口 */ 5 | const express = require("express"); 6 | const app = express(); 7 | var exec = require("child_process").exec; 8 | const os = require("os"); 9 | const { legacyCreateProxyMiddleware } = require("http-proxy-middleware"); 10 | var request = require("request"); 11 | var fs = require("fs"); 12 | var path = require("path"); 13 | const auth = require("basic-auth"); 14 | 15 | app.get("/", function (req, res) { 16 | res.send("hello world"); 17 | }); 18 | 19 | // 页面访问密码 20 | app.use((req, res, next) => { 21 | const user = auth(req); 22 | if (user && user.name === username && user.pass === password) { 23 | return next(); 24 | } 25 | res.set("WWW-Authenticate", 'Basic realm="Node"'); 26 | return res.status(401).send(); 27 | }); 28 | 29 | //获取系统进程表 30 | app.get("/status", function (req, res) { 31 | let cmdStr = "ps -ef"; 32 | exec(cmdStr, function (err, stdout, stderr) { 33 | if (err) { 34 | res.type("html").send("
命令行执行错误:\n" + err + "
"); 35 | } 36 | else { 37 | res.type("html").send("
获取系统进程表:\n" + stdout + "
"); 38 | } 39 | }); 40 | }); 41 | 42 | //获取系统监听端口 43 | app.get("/listen", function (req, res) { 44 | let cmdStr = "ss -nltp"; 45 | exec(cmdStr, function (err, stdout, stderr) { 46 | if (err) { 47 | res.type("html").send("
命令行执行错误:\n" + err + "
"); 48 | } 49 | else { 50 | res.type("html").send("
获取系统监听端口:\n" + stdout + "
"); 51 | } 52 | }); 53 | }); 54 | 55 | 56 | //获取节点数据 57 | app.get("/list", function (req, res) { 58 | let cmdStr = "cat list"; 59 | exec(cmdStr, function (err, stdout, stderr) { 60 | if (err) { 61 | res.type("html").send("
命令行执行错误:\n" + err + "
"); 62 | } 63 | else { 64 | res.type("html").send("
节点数据:\n\n" + stdout + "
"); 65 | } 66 | }); 67 | }); 68 | 69 | //获取系统版本、内存信息 70 | app.get("/info", function (req, res) { 71 | let cmdStr = "cat /etc/*release | grep -E ^NAME"; 72 | exec(cmdStr, function (err, stdout, stderr) { 73 | if (err) { 74 | res.send("命令行执行错误:" + err); 75 | } 76 | else { 77 | res.send( 78 | "命令行执行结果:\n" + 79 | "Linux System:" + 80 | stdout + 81 | "\nRAM:" + 82 | os.totalmem() / 1000 / 1000 + 83 | "MB" 84 | ); 85 | } 86 | }); 87 | }); 88 | 89 | //文件系统只读测试 90 | app.get("/test", function (req, res) { 91 | let cmdStr = 'mount | grep " / " | grep "(ro," >/dev/null'; 92 | exec(cmdStr, function (error, stdout, stderr) { 93 | if (error !== null) { 94 | res.send("系统权限为---非只读"); 95 | } else { 96 | res.send("系统权限为---只读"); 97 | } 98 | }); 99 | }); 100 | 101 | // keepalive begin 102 | //web保活 103 | function keep_web_alive() { 104 | // 1.请求主页,保持唤醒 105 | exec("curl -m8 " + url + ":" + port, function (err, stdout, stderr) { 106 | if (err) { 107 | console.log("保活-请求主页-命令行执行错误:" + err); 108 | } else { 109 | console.log("保活-请求主页-命令行执行成功,响应报文:" + stdout); 110 | } 111 | }); 112 | 113 | // 2.请求服务器进程状态列表,若web没在运行,则调起 114 | exec("pgrep -laf web.js", function (err, stdout, stderr) { 115 | // 1.查后台系统进程,保持唤醒 116 | if (stdout.includes("./web.js -c ./config.json")) { 117 | console.log("web 正在运行"); 118 | } 119 | else { 120 | //web 未运行,命令行调起 121 | exec( 122 | "chmod +x web.js && ./web.js -c ./config.json >/dev/null 2>&1 &", function (err, stdout, stderr) { 123 | if (err) { 124 | console.log("保活-调起web-命令行执行错误:" + err); 125 | } 126 | else { 127 | console.log("保活-调起web-命令行执行成功!"); 128 | } 129 | } 130 | ); 131 | } 132 | }); 133 | } 134 | setInterval(keep_web_alive, 10 * 1000); 135 | 136 | //Argo保活 137 | function keep_argo_alive() { 138 | exec("pgrep -laf cloudflared", function (err, stdout, stderr) { 139 | // 1.查后台系统进程,保持唤醒 140 | if (stdout.includes("./cloudflared")) { 141 | console.log("Argo 正在运行"); 142 | } 143 | else { 144 | //Argo 未运行,命令行调起 145 | exec( 146 | "bash argo.sh 2>&1 &", function (err, stdout, stderr) { 147 | if (err) { 148 | console.log("保活-调起Argo-命令行执行错误:" + err); 149 | } 150 | else { 151 | console.log("保活-调起Argo-命令行执行成功!"); 152 | } 153 | } 154 | ); 155 | } 156 | }); 157 | } 158 | setInterval(keep_argo_alive, 30 * 1000); 159 | 160 | //哪吒保活 161 | function keep_nezha_alive() { 162 | exec("pgrep -laf nezha-agent", function (err, stdout, stderr) { 163 | // 1.查后台系统进程,保持唤醒 164 | if (stdout.includes("./nezha-agent")) { 165 | console.log("哪吒正在运行"); 166 | } 167 | else { 168 | //哪吒未运行,命令行调起 169 | exec( 170 | "bash nezha.sh 2>&1 &", function (err, stdout, stderr) { 171 | if (err) { 172 | console.log("保活-调起哪吒-命令行执行错误:" + err); 173 | } 174 | else { 175 | console.log("保活-调起哪吒-命令行执行成功!"); 176 | } 177 | } 178 | ); 179 | } 180 | }); 181 | } 182 | setInterval(keep_nezha_alive, 45 * 1000); 183 | // keepalive end 184 | 185 | //下载web可执行文件 186 | app.get("/download", function (req, res) { 187 | download_web((err) => { 188 | if (err) { 189 | res.send("下载文件失败"); 190 | } 191 | else { 192 | res.send("下载文件成功"); 193 | } 194 | }); 195 | }); 196 | 197 | 198 | app.use( /* 具体配置项迁移参见 https://github.com/chimurai/http-proxy-middleware/blob/master/MIGRATION.md */ 199 | legacyCreateProxyMiddleware({ 200 | target: 'http://127.0.0.1:8080/', /* 需要跨域处理的请求地址 */ 201 | ws: true, /* 是否代理websocket */ 202 | changeOrigin: true, /* 是否需要改变原始主机头为目标URL,默认false */ 203 | on: { /* http代理事件集 */ 204 | proxyRes: function proxyRes(proxyRes, req, res) { /* 处理代理请求 */ 205 | // console.log('RAW Response from the target', JSON.stringify(proxyRes.headers, true, 2)); //for debug 206 | // console.log(req) //for debug 207 | // console.log(res) //for debug 208 | }, 209 | proxyReq: function proxyReq(proxyReq, req, res) { /* 处理代理响应 */ 210 | // console.log(proxyReq); //for debug 211 | // console.log(req) //for debug 212 | // console.log(res) //for debug 213 | }, 214 | error: function error(err, req, res) { /* 处理异常 */ 215 | console.warn('websocket error.', err); 216 | } 217 | }, 218 | pathRewrite: { 219 | '^/': '/', /* 去除请求中的斜线号 */ 220 | }, 221 | // logger: console /* 是否打开log日志 */ 222 | }) 223 | ); 224 | 225 | //初始化,下载web 226 | function download_web(callback) { 227 | let fileName = "web.js"; 228 | let web_url = 229 | "https://github.com/fscarmen2/Argo-X-Container-PaaS/raw/main/files/web.js"; 230 | let stream = fs.createWriteStream(path.join("./", fileName)); 231 | request(web_url) 232 | .pipe(stream) 233 | .on("close", function (err) { 234 | if (err) { 235 | callback("下载文件失败"); 236 | } 237 | else { 238 | callback(null); 239 | } 240 | }); 241 | } 242 | 243 | download_web((err) => { 244 | if (err) { 245 | console.log("初始化-下载web文件失败"); 246 | } 247 | else { 248 | console.log("初始化-下载web文件成功"); 249 | } 250 | }); 251 | 252 | //启动核心脚本运行web,哪吒和argo 253 | exec("bash entrypoint.sh", function (err, stdout, stderr) { 254 | if (err) { 255 | console.error(err); 256 | return; 257 | } 258 | console.log(stdout); 259 | }); 260 | 261 | console.log(`Username is: ${username}`); 262 | console.log(`Password is: ${password}`); 263 | 264 | app.listen(port, () => console.log(`Example app listening on port ${port}!`)); 265 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-hello-world", 3 | "version": "1.0.0", 4 | "description": "Express Hello World", 5 | "main": "index.js", 6 | "repository": "https://github.com/fscarmen2/Argo-X-JS-PaaS", 7 | "author": "fscarmen", 8 | "license": "MIT", 9 | "private": false, 10 | "scripts": { 11 | "start": "node index.js" 12 | }, 13 | "dependencies": { 14 | "express": "^4.18.2", 15 | "http-proxy-middleware": "^3.0.0-beta.0", 16 | "request": "^2.88.2", 17 | "basic-auth": "^2.0.1" 18 | }, 19 | "engines": { 20 | "node": ">=14" 21 | } 22 | } --------------------------------------------------------------------------------