import os import re import json import time import base64 import shutil import asyncio import requests import subprocess import threading from threading import Thread # WEB站点 WEB_ARGO_DOMAIN=os.environ.get('WEB_DOMAIN','') WEB_ARGO_AUTH = os.environ.get('WEB_ARGO_AUTH', '') # Environment variables FILE_PATH = os.environ.get('FILE_PATH', './.cache') # 运行路径,sub.txt保存路径 SUB_PATH = os.environ.get('SUB_PATH', 'getmysubs') # 订阅token,默认sub,例如:https://www.google.com/sub UUID = os.environ.get('UUID', 'b0cc09a7-06f0-42fb-a161-17434162ebdf') # UUID,如使用哪吒v1,在不同的平台部署需要修改,否则会覆盖 NEZHA_SERVER = os.environ.get('NEZHA_SERVER', '') # 哪吒面板域名或ip, v1格式: nezha.xxx.com:8008, v0格式: nezha.xxx.com NEZHA_PORT = os.environ.get('NEZHA_PORT', '') # v1哪吒请留空, v0哪吒的agent通信端口,自动匹配tls NEZHA_KEY = os.environ.get('NEZHA_KEY', '') # v1哪吒的NZ_CLIENT_SECRET或v0哪吒agent密钥 ARGO_DOMAIN = os.environ.get('ARGO_DOMAIN', '') # Argo固定隧道域名,留空即使用临时隧道 ARGO_AUTH = os.environ.get('ARGO_AUTH', '') # Argo固定隧道密钥,留空即使用临时隧道 ARGO_PORT = int(os.environ.get('ARGO_PORT', '8001')) # Argo端口,使用固定隧道token需在cloudflare后台设置端口和这里一致 CFIP = os.environ.get('CFIP', 'www.visa.com.tw') # 优选ip或优选域名 CFPORT = int(os.environ.get('CFPORT', '443')) # 优选ip或优选域名对应端口 NAME = os.environ.get('NAME', 'dbs-rtian1') # 节点名称 PORT = int(os.environ.get('SERVER_PORT') or os.environ.get('PORT') or 3000) # 订阅端口,如无法订阅,请手动修改为分配的端口 # Execute shell command and return output def exec_cmd(command): try: process = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) stdout, stderr = process.communicate() return stdout.strip() except Exception as e: print(f"Error executing command: {e}") return null # Create running folder def create_directory(): if not os.path.exists(FILE_PATH): os.makedirs(FILE_PATH) # print(f"{FILE_PATH} is created") # else: # print(f"{FILE_PATH} already exists") # Global variables npm_path = os.path.join(FILE_PATH, 'npm') php_path = os.path.join(FILE_PATH, 'php') crawl_path = os.path.join(FILE_PATH, 'crawl') bot_path = os.path.join(FILE_PATH, 'bot') sub_path = os.path.join(FILE_PATH, 'sub.txt') list_path = os.path.join(FILE_PATH, 'list.txt') boot_log_path = os.path.join(FILE_PATH, 'boot.log') config_path = os.path.join(FILE_PATH, 'config.json') # Delete nodes def delete_nodes(): try: if not os.path.exists(sub_path): return try: with open(sub_path, 'r') as file: file_content = file.read() except: return None decoded = base64.b64decode(file_content).decode('utf-8') nodes = [line for line in decoded.split('\n') if any(protocol in line for protocol in ['vless://', 'vmess://', 'trojan://', 'hysteria2://', 'tuic://'])] if not nodes: return except Exception as e: print(f"Error in delete_nodes: {e}") return None # Clean up old files def cleanup_old_files(): paths_to_delete = ['crawl', 'bot', 'npm', 'php', 'boot.log', 'list.txt'] for file in paths_to_delete: file_path = os.path.join(FILE_PATH, file) try: if os.path.exists(file_path): if os.path.isdir(file_path): shutil.rmtree(file_path) else: os.remove(file_path) except Exception as e: print(f"Error removing {file_path}: {e}") # Determine system architecture def get_system_architecture(): # architecture = platform.machine().lower() architecture = exec_cmd("uname -m").lower() if 'arm' in architecture or 'aarch64' in architecture: return 'arm' else: return 'amd' # Download file based on architecture def download_file(file_name, file_url): file_path = os.path.join(FILE_PATH, file_name) try: response = requests.get(file_url, stream=True) response.raise_for_status() with open(file_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"Download {file_name} successfully") return True except Exception as e: if os.path.exists(file_path): os.remove(file_path) print(f"Download {file_name} failed: {e}") return False # Get files for architecture def get_files_for_architecture(architecture): if architecture == 'arm': base_files = [ {"fileName": "crawl", "fileUrl": "https://arm64.ssss.nyc.mn/web"}, {"fileName": "bot", "fileUrl": "https://arm64.ssss.nyc.mn/2go"} ] else: base_files = [ {"fileName": "crawl", "fileUrl": "https://amd64.ssss.nyc.mn/web"}, {"fileName": "bot", "fileUrl": "https://amd64.ssss.nyc.mn/2go"} ] if NEZHA_SERVER and NEZHA_KEY: if NEZHA_PORT: npm_url = "https://arm64.ssss.nyc.mn/agent" if architecture == 'arm' else "https://amd64.ssss.nyc.mn/agent" base_files.insert(0, {"fileName": "npm", "fileUrl": npm_url}) else: php_url = "https://arm64.ssss.nyc.mn/v1" if architecture == 'arm' else "https://amd64.ssss.nyc.mn/v1" base_files.insert(0, {"fileName": "php", "fileUrl": php_url}) return base_files # Authorize files with execute permission def authorize_files(file_paths): for relative_file_path in file_paths: absolute_file_path = os.path.join(FILE_PATH, relative_file_path) if os.path.exists(absolute_file_path): try: os.chmod(absolute_file_path, 0o775) print(f"Empowerment success for {absolute_file_path}: 775") except Exception as e: print(f"Empowerment failed for {absolute_file_path}: {e}") # Configure Argo tunnel def argo_type(): if not ARGO_AUTH or not ARGO_DOMAIN: # print("ARGO_DOMAIN or ARGO_AUTH variable is empty, use quick tunnels") return if "TunnelSecret" in ARGO_AUTH: with open(os.path.join(FILE_PATH, 'tunnel.json'), 'w') as f: f.write(ARGO_AUTH) tunnel_id = ARGO_AUTH.split('"')[11] tunnel_yml = f""" tunnel: {tunnel_id} credentials-file: {os.path.join(FILE_PATH, 'tunnel.json')} protocol: http2 ingress: - hostname: {ARGO_DOMAIN} service: http://localhost:{ARGO_PORT} originRequest: noTLSVerify: true - service: http_status:404 """ with open(os.path.join(FILE_PATH, 'tunnel.yml'), 'w') as f: f.write(tunnel_yml) else: # print("Use token connect to tunnel,please set the {ARGO_PORT} in cloudflare") return # Download and run necessary files async def download_files_and_run(): global private_key, public_key architecture = get_system_architecture() files_to_download = get_files_for_architecture(architecture) if not files_to_download: print("Can't find a file for the current architecture") return # Download all files download_success = True for file_info in files_to_download: if not download_file(file_info["fileName"], file_info["fileUrl"]): download_success = False if not download_success: print("Error downloading files") return # Authorize files files_to_authorize = ['npm', 'crawl', 'bot'] if NEZHA_PORT else ['php', 'crawl', 'bot'] authorize_files(files_to_authorize) # Check TLS port = NEZHA_SERVER.split(":")[-1] if ":" in NEZHA_SERVER else "" if port in ["443", "8443", "2096", "2087", "2083", "2053"]: nezha_tls = "true" else: nezha_tls = "false" # Configure nezha if NEZHA_SERVER and NEZHA_KEY: if not NEZHA_PORT: # Generate config.yaml for v1 config_yaml = f""" client_secret: {NEZHA_KEY} debug: false disable_auto_update: true disable_command_execute: false disable_force_update: true disable_nat: false disable_send_query: false gpu: false insecure_tls: false ip_report_period: 1800 report_delay: 4 server: {NEZHA_SERVER} skip_connection_count: false skip_procs_count: false temperature: false tls: {nezha_tls} use_gitee_to_upgrade: false use_ipv6_country_code: false uuid: {UUID}""" with open(os.path.join(FILE_PATH, 'config.yaml'), 'w') as f: f.write(config_yaml) # Generate configuration file config ={"log":{"access":"/dev/null","error":"/dev/null","loglevel":"none",},"inbounds":[{"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",},},{"port":3001 ,"listen":"127.0.0.1","protocol":"vless","settings":{"clients":[{"id":UUID },],"decryption":"none"},"streamSettings":{"network":"ws","security":"none"}},{"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 }},{"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 }},{"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 }},],"outbounds":[{"protocol":"freedom","tag": "direct" },{"protocol":"blackhole","tag":"block"}]} with open(os.path.join(FILE_PATH, 'config.json'), 'w', encoding='utf-8') as config_file: json.dump(config, config_file, ensure_ascii=False, indent=2) # Run nezha if NEZHA_SERVER and NEZHA_PORT and NEZHA_KEY: tls_ports = ['443', '8443', '2096', '2087', '2083', '2053'] nezha_tls = '--tls' if NEZHA_PORT in tls_ports else '' command = f"nohup {os.path.join(FILE_PATH, 'npm')} -s {NEZHA_SERVER}:{NEZHA_PORT} -p {NEZHA_KEY} {nezha_tls} >/dev/null 2>&1 &" try: exec_cmd(command) print('npm is running') time.sleep(1) except Exception as e: print(f"npm running error: {e}") elif NEZHA_SERVER and NEZHA_KEY: # Run V1 command = f"nohup {FILE_PATH}/php -c \"{FILE_PATH}/config.yaml\" >/dev/null 2>&1 &" try: exec_cmd(command) print('php is running') time.sleep(1) except Exception as e: print(f"php running error: {e}") else: print('NEZHA variable is empty, skipping running') # Run sbX command = f"nohup {os.path.join(FILE_PATH, 'crawl')} -c {os.path.join(FILE_PATH, 'config.json')} >/dev/null 2>&1 &" try: # 首次启动不开启sbx # exec_cmd(command) print('crawl is running') time.sleep(1) except Exception as e: print(f"crawl running error: {e}") # Run cloudflared if os.path.exists(os.path.join(FILE_PATH, 'bot')): if re.match(r'^[A-Z0-9a-z=]{120,250}$', ARGO_AUTH): args = f"tunnel --edge-ip-version auto --no-autoupdate --protocol http2 run --token {ARGO_AUTH}" elif "TunnelSecret" in ARGO_AUTH: args = f"tunnel --edge-ip-version auto --config {os.path.join(FILE_PATH, 'tunnel.yml')} run" else: args = f"tunnel --edge-ip-version auto --no-autoupdate --protocol http2 --logfile {os.path.join(FILE_PATH, 'boot.log')} --loglevel info --url http://localhost:{ARGO_PORT}" if WEB_ARGO_DOMAIN and WEB_ARGO_AUTH: web_args=f"tunnel --edge-ip-version auto --no-autoupdate --protocol http2 run --token {WEB_ARGO_AUTH}" exec_cmd(f"nohup {os.path.join(FILE_PATH, 'bot')} {web_args} >/dev/null 2>&1 &") print('web_bot is running') time.sleep(3) try: exec_cmd(f"nohup {os.path.join(FILE_PATH, 'bot')} {args} >/dev/null 2>&1 &") print('bot is running') time.sleep(2) except Exception as e: print(f"Error executing command: {e}") time.sleep(5) # Extract domains and generate sub.txt await extract_domains() # Extract domains from cloudflared logs async def extract_domains(): argo_domain = None if ARGO_AUTH and ARGO_DOMAIN: argo_domain = ARGO_DOMAIN print(f'ARGO_DOMAIN: {argo_domain}') await generate_links(argo_domain) else: try: with open(boot_log_path, 'r') as f: file_content = f.read() lines = file_content.split('\n') argo_domains = [] for line in lines: domain_match = re.search(r'https?://([^ ]*trycloudflare\.com)/?', line) if domain_match: domain = domain_match.group(1) argo_domains.append(domain) if argo_domains: argo_domain = argo_domains[0] print(f'ArgoDomain: {argo_domain}') await generate_links(argo_domain) else: print('ArgoDomain not found, re-running bot to obtain ArgoDomain') # Remove boot.log and restart bot if os.path.exists(boot_log_path): os.remove(boot_log_path) try: exec_cmd('pkill -f "[b]ot" > /dev/null 2>&1') except: pass time.sleep(1) args = f'tunnel --edge-ip-version auto --no-autoupdate --protocol http2 --logfile {FILE_PATH}/boot.log --loglevel info --url http://localhost:{ARGO_PORT}' exec_cmd(f'nohup {os.path.join(FILE_PATH, "bot")} {args} >/dev/null 2>&1 &') print('bot is running.') time.sleep(6) # Wait 6 seconds await extract_domains() # Try again except Exception as e: print(f'Error reading boot.log: {e}') # Generate links and subscription content async def generate_links(argo_domain): meta_info = exec_cmd('curl -s https://speed.cloudflare.com/meta') meta_info = meta_info.split('"') ISP = f"【{meta_info[25]}】" time.sleep(2) VMESS = {"v": "2", "ps": f"{NAME}-{ISP}-1", "add": CFIP, "port": CFPORT, "id": UUID, "aid": "0", "scy": "none", "net": "ws", "type": "none", "host": argo_domain, "path": "/vmess-argo?ed=2560", "tls": "tls", "sni": argo_domain, "alpn": "", "fp": "chrome"} list_txt = f""" vmess://{ base64.b64encode(json.dumps(VMESS).encode('utf-8')).decode('utf-8')} vless://{UUID}@{CFIP}:{CFPORT}?encryption=none&security=tls&sni={argo_domain}&fp=chrome&type=ws&host={argo_domain}&path=%2Fvless-argo%3Fed%3D2560#{NAME}-{ISP}-2 trojan://{UUID}@{CFIP}:{CFPORT}?security=tls&sni={argo_domain}&fp=chrome&type=ws&host={argo_domain}&path=%2Ftrojan-argo%3Fed%3D2560#{NAME}-{ISP}-3 """ with open(os.path.join(FILE_PATH, 'list.txt'), 'w', encoding='utf-8') as list_file: list_file.write(list_txt) sub_txt = base64.b64encode(list_txt.encode('utf-8')).decode('utf-8') with open(os.path.join(FILE_PATH, 'sub.txt'), 'w', encoding='utf-8') as sub_file: sub_file.write(sub_txt) print(f"{FILE_PATH}/sub.txt saved successfully") return sub_txt # Clean up files after 90 seconds def clean_files(): def _cleanup(): time.sleep(90) # Wait 90 seconds files_to_delete = [boot_log_path, config_path, list_path, crawl_path, bot_path, php_path, npm_path] for file in files_to_delete: try: if os.path.exists(file): if os.path.isdir(file): shutil.rmtree(file) else: os.remove(file) except: pass print('App is running') print('Thank you for using this script, enjoy!') threading.Thread(target=_cleanup, daemon=True).start() # Main function to start the server async def start_sbx(): if os.path.exists(crawl_path): command = f"nohup {os.path.join(FILE_PATH, 'crawl')} -c {os.path.join(FILE_PATH, 'config.json')} >/dev/null 2>&1 &" exec_cmd(command) else: # delete_nodes() # cleanup_old_files() create_directory() argo_type() await download_files_and_run() # clean_files() def run_start(): crawl_loop = asyncio.new_event_loop() asyncio.set_event_loop(crawl_loop) crawl_loop.run_until_complete(start_sbx()) crawl_loop.close() def run_stop(): exec_cmd('pkill -f crawl > /dev/null 2>&1') def get_links(): running=exec_cmd('ps -ef|grep cache/crawl |grep -v grep') if running: status='running' else: status='stopped' if os.path.exists(sub_path): with open(sub_path, 'r', encoding='utf-8') as sub_file: sub_txt = sub_file.read() return json.dumps({"status":status,"data":sub_txt,"running":running}).encode('utf-8') else: return json.dumps({"status":status,"data":None,"running":running}).encode('utf-8') if __name__ == "__main__": run_start()