From 5cfe50488eb83ca2e7967abccd70a41dd90b964f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E7=8E=8B?= Date: Fri, 20 Mar 2026 16:11:57 +0800 Subject: [PATCH] remove shell file --- python/app.py | 291 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 253 insertions(+), 38 deletions(-) diff --git a/python/app.py b/python/app.py index 49d2661..c9755ad 100644 --- a/python/app.py +++ b/python/app.py @@ -1,56 +1,271 @@ -import sys +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + import os +import sys +import signal +import time +import stat import subprocess +import urllib.request +import urllib.error import http.server import socketserver import threading +import platform +from pathlib import Path -PORT = int(os.environ.get('PORT') or 3000) # http port +# 环境变量配置 +PORT = int(os.environ.get('PORT', 3000)) # http服务端口 +SUB_PATH = os.environ.get('SUB_PATH', 'sub') # 订阅token +config = { + 'UUID': os.environ.get('UUID', 'b5c445d1-8e59-465f-af0c-1f4193d15693'), # 节点UUID,使用哪吒v1时在不不同的平台部署需要修改,否则agent会覆盖 + 'NEZHA_SERVER': os.environ.get('NEZHA_SERVER', ''), # 哪吒面板地址,v1格式: nezha.xxx.com:8008 v0格式: nezha.xxx.com + 'NEZHA_PORT': os.environ.get('NEZHA_PORT', ''), # 哪吒v1请留空,哪吒v0 agent端口 + 'NEZHA_KEY': os.environ.get('NEZHA_KEY', ''), # 哪吒v1的NZ_CLIENT_SECRET或哪吒v0-agent密钥 + 'ARGO_DOMAIN': os.environ.get('ARGO_DOMAIN', ''), # 固定隧道域名,留空即启用临时隧道 + 'ARGO_AUTH': os.environ.get('ARGO_AUTH', ''), # 固定隧道token或json,留空即启用临时隧道,json获取:https://json.zone.id + 'ARGO_PORT': os.environ.get('ARGO_PORT', '8001'), # argo端口 使用固定隧道token,cloudflare后台设置的端口需和这里对应 + 'CFIP': os.environ.get('CFIP', 'saas.sin.fan'), # 优选域名或优选ip + 'CFPORT': os.environ.get('CFPORT', '443'), # 优选域名或优选ip对应端口 + 'NAME': os.environ.get('NAME', ''), # 节点备注 + 'S5_PORT': os.environ.get('S5_PORT', ''), # socks5端口,支持多端口玩具可填写,否则不动 + 'HY2_PORT': os.environ.get('HY2_PORT', ''), # Hy2 端口,支持多端口玩具可填写,否则不动 + 'TUIC_PORT': os.environ.get('TUIC_PORT', ''), # Tuic 端口,支持多端口玩具可填写,否则不动 + 'ANYTLS_PORT': os.environ.get('ANYTLS_PORT', ''), # AnyTLS 端口,支持多端口玩具可填写,否则不动 + 'REALITY_PORT': os.environ.get('REALITY_PORT', ''), # Reality 端口,支持多端口玩具可填写,否则不动 + 'ANYREALITY_PORT': os.environ.get('ANYREALITY_PORT', ''), # AnyReality 端口,支持多端口玩具可填写,否则不动 + 'CHAT_ID': os.environ.get('CHAT_ID', ''), # TG chat_id,可在https://t.me/laowang_serv00_bot 获取 + 'BOT_TOKEN': os.environ.get('BOT_TOKEN', ''), # TG bot_token, 使用自己的bot需要填写,使用上方的bot不用填写,不会给别人发送 + 'UPLOAD_URL': os.environ.get('UPLOAD_URL', ''), # 节点上传地址,需部署merge-sub订阅器项目,例如:https://merge.xxx.com + 'FILE_PATH': os.environ.get('FILE_PATH', '.cache'), # sub,.txt节点存放目录 + 'DISABLE_ARGO': os.environ.get('DISABLE_ARGO', 'false'), # 是否禁用argo, true为禁用,false为不禁用,默认开启 +} -class MyHandler(http.server.SimpleHTTPRequestHandler): +def sleep(ms): + time.sleep(ms / 1000) - def log_message(self, format, *args): - pass +def get_architecture(): + """获取系统架构""" + arch = platform.machine().lower() + system = platform.system().lower() + + if system in ['linux', 'darwin']: + if arch in ['x86_64', 'amd64']: + return 'amd64' + elif arch in ['aarch64', 'arm64']: + return 'arm64' + + raise Exception(f"Unsupported architecture: {system} {arch}") +def download_file(url, dest_path): + print(f"Downloading from: {url}") + opener = urllib.request.build_opener() + opener.addheaders = [ + ('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'), + ('Accept', '*/*'), + ('Connection', 'keep-alive') + ] + + urllib.request.install_opener(opener) + + try: + # 下载文件 + urllib.request.urlretrieve(url, dest_path) + # print("Download completed!") + except urllib.error.HTTPError as e: + if os.path.exists(dest_path): + os.unlink(dest_path) + raise Exception(f"Download failed (HTTP {e.code}): {e.reason}") + except urllib.error.URLError as e: + if os.path.exists(dest_path): + os.unlink(dest_path) + raise Exception(f"Download failed (URL error): {str(e)}") + except Exception as e: + if os.path.exists(dest_path): + os.unlink(dest_path) + raise Exception(f"Download failed: {str(e)}") + +def set_executable(file_path): + """设置文件可执行权限""" + try: + current_permissions = os.stat(file_path).st_mode + os.chmod(file_path, current_permissions | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + except Exception as e: + raise Exception(f"Failed to set executable permission: {str(e)}") + +def delete_file(file_path): + """删除文件""" + try: + if os.path.exists(file_path): + os.unlink(file_path) + except Exception as e: + print(f"Failed to delete file {file_path}: {str(e)}") + +class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): + """自定义HTTP请求处理器""" + def do_GET(self): if self.path == '/': - self.send_response(200) - self.end_headers() - self.wfile.write(b'Hello, world') - elif self.path == '/sub': - try: - with open("./sub.txt", 'rb') as file: - content = file.read() - self.send_response(200) - self.send_header('Content-Type', 'text/plain; charset=utf-8') - self.end_headers() - self.wfile.write(content) - except FileNotFoundError: - self.send_response(500) - self.end_headers() - self.wfile.write(b'Error reading file') + self.handle_root() + elif self.path == f'/{SUB_PATH}': + self.handle_sub() + elif self.path == '/ps': + self.handle_ps() else: - self.send_response(404) + self.send_error(404, '404 Not Found') + + def handle_root(self): + """处理根路径请求""" + try: + html_path = os.path.join(os.path.dirname(__file__), 'index.html') + if os.path.exists(html_path): + with open(html_path, 'r', encoding='utf-8') as f: + data = f.read() + self.send_response(200) + self.send_header('Content-Type', 'text/html') + self.end_headers() + self.wfile.write(data.encode('utf-8')) + else: + self.send_response(200) + self.send_header('Content-Type', 'text/html; charset=utf-8') + self.end_headers() + self.wfile.write("Hello world!

You can access /{SUB_PATH}(Default: /sub) to get your nodes!".encode('utf-8')) + except Exception as e: + self.send_error(500, str(e)) + + def handle_sub(self): + """处理订阅路径请求""" + sub_file_path = os.path.join(config['FILE_PATH'], 'sub.txt') + try: + if os.path.exists(sub_file_path): + with open(sub_file_path, 'r', encoding='utf-8') as f: + data = f.read() + self.send_response(200) + self.send_header('Content-Type', 'text/plain') + self.end_headers() + self.wfile.write(data.encode('utf-8')) + else: + self.send_error(404, f'Sub file not found at: {sub_file_path}') + except Exception as e: + self.send_error(500, str(e)) + + def handle_ps(self): + """处理进程列表请求""" + try: + result = subprocess.run(['ps', 'aux'], capture_output=True, text=True, check=True) + self.send_response(200) + self.send_header('Content-Type', 'text/plain') self.end_headers() - self.wfile.write(b'Not found') -httpd = socketserver.TCPServer(('', PORT), MyHandler) -server_thread = threading.Thread(target=httpd.serve_forever) -server_thread.daemon = True -server_thread.start() + self.wfile.write(result.stdout.encode('utf-8')) + except subprocess.CalledProcessError as e: + self.send_error(500, f'Error executing ps command: {str(e)}') + + def log_message(self, format, *args): + """覆盖日志方法,不输出访问日志""" + pass -shell_command = "chmod +x start.sh && ./start.sh" +def start_http_server(): + """启动HTTP服务器""" + handler = CustomHTTPRequestHandler + + # 创建服务器,允许地址重用 + socketserver.TCPServer.allow_reuse_address = True + httpd = socketserver.TCPServer(('0.0.0.0', PORT), handler) + + server_thread = threading.Thread(target=httpd.serve_forever) + server_thread.daemon = True + server_thread.start() + + return httpd -try: - completed_process = subprocess.run(['bash', '-c', shell_command], stdout=sys.stdout, stderr=subprocess.PIPE, text=True, check=True) +def cleanup(binary_path): + """清理二进制文件""" + delete_file(binary_path) - print("App is running") +def main(): + """主函数""" + binary_path = None + httpd = None + process = None + + try: + # 启动HTTP服务器 + httpd = start_http_server() + + # 获取架构并下载 + arch = get_architecture() + download_url = 'https://amd64.eooce.com/sbsh' if arch == 'amd64' else 'https://arm64.eooce.com/sbsh' + + # print(f"Using download link: {download_url}") + binary_path = os.path.join(os.getcwd(), 'sbsh') + + download_file(download_url, binary_path) + if not os.path.exists(binary_path): + raise Exception('Download failed, file does not exist at the specified path') + + set_executable(binary_path) + + # 准备环境变量 + env = os.environ.copy() + env.update({k: str(v) for k, v in config.items()}) + + # 启动子进程 + process = subprocess.Popen( + [binary_path], + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + bufsize=1 + ) + + # 实时输出子进程日志 + def log_output(): + for line in process.stdout: + print(line, end='') + + log_thread = threading.Thread(target=log_output) + log_thread.daemon = True + log_thread.start() + + sleep(18000) + print('\nLogs will be deleted in 90 seconds, you can copy the above nodes!') + + sleep(90000) + + cleanup(binary_path) + + # 清除控制台 + os.system('clear' if os.name == 'posix' else 'cls') + + print('✅ App is running') + print(f'🌐 HTTP server is running on {PORT}') + + # 保持主线程运行 + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + print("\nShutting down...") + + except Exception as e: + print(f"\n❌ An error occurred: {str(e)}") + if binary_path: + cleanup(binary_path) + sys.exit(1) + finally: + if process and process.poll() is None: + process.terminate() + if httpd: + httpd.shutdown() -except subprocess.CalledProcessError as e: - print(f"Error: {e.returncode}") - print("Standard Output:") - print(e.stdout) - print("Standard Error:") - print(e.stderr) - sys.exit(1) +def signal_handler(signum, frame): + print("\nReceived signal to terminate") + sys.exit(0) -server_thread.join() +if __name__ == "__main__": + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + main()