Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| Simplified Multi-Client Ultroid Launcher v2 | |
| Uses working directories but with minimal setup | |
| """ | |
| import os | |
| import subprocess | |
| import sys | |
| import shutil | |
| # Load .env file | |
| try: | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| except ImportError: | |
| pass | |
| REQUIRED_VARS = ["API_ID", "API_HASH", "SESSION", "MONGO_URI"] | |
| def has_client_config(client_num): | |
| """Check if client has all required config""" | |
| suffix = "" if client_num == 1 else str(client_num - 1) | |
| for var in REQUIRED_VARS: | |
| if not os.environ.get(var + suffix): | |
| return False | |
| return True | |
| def stop_client(client_num): | |
| """Stop a running client if it exists""" | |
| base_dir = os.getcwd() | |
| pid_file = os.path.join(base_dir, f"client_{client_num}.pid") | |
| if os.path.exists(pid_file): | |
| try: | |
| with open(pid_file, 'r') as f: | |
| pid = int(f.read().strip()) | |
| # Try to kill the process | |
| try: | |
| if sys.platform == "win32": | |
| subprocess.run(["taskkill", "/F", "/PID", str(pid)], | |
| capture_output=True, check=False) | |
| else: | |
| os.kill(pid, 15) # SIGTERM | |
| print(f" β Stopped Client {client_num} (PID: {pid})") | |
| except (ProcessLookupError, OSError): | |
| # Process already dead | |
| pass | |
| # Remove PID file | |
| try: | |
| os.remove(pid_file) | |
| except: | |
| pass | |
| except Exception: | |
| pass | |
| # Also try to kill by process detection (psutil if available) | |
| try: | |
| import psutil | |
| for proc in psutil.process_iter(['pid', 'cmdline', 'cwd']): | |
| try: | |
| cmdline = proc.info.get('cmdline', []) | |
| proc_cwd = proc.info.get('cwd', '') | |
| # Check if this is a pyUltroid process in client_N directory | |
| if any('pyUltroid' in str(arg) for arg in cmdline): | |
| if f'client_{client_num}' in proc_cwd: | |
| proc.kill() | |
| print(f" β Stopped Client {client_num} (PID: {proc.info['pid']})") | |
| except (psutil.NoSuchProcess, psutil.AccessDenied): | |
| continue | |
| except ImportError: | |
| # psutil not available, skip process detection | |
| pass | |
| def setup_client_dir(client_num, base_dir): | |
| """Minimal setup for client directory""" | |
| client_dir = os.path.join(base_dir, f"client_{client_num}") | |
| os.makedirs(client_dir, exist_ok=True) | |
| # Only create symlinks for essential directories | |
| for dir_name in ["plugins", "resources", "assistant", "strings", "addons"]: | |
| src = os.path.join(base_dir, dir_name) | |
| dst = os.path.join(client_dir, dir_name) | |
| if os.path.exists(src) and not os.path.exists(dst): | |
| try: | |
| if sys.platform != "win32": | |
| os.symlink(os.path.abspath(src), dst) | |
| else: | |
| # Windows: copy instead of symlink | |
| if os.path.isdir(src): | |
| shutil.copytree(src, dst) | |
| except Exception: | |
| pass | |
| # Copy .env | |
| if os.path.exists(".env"): | |
| shutil.copy(".env", os.path.join(client_dir, ".env")) | |
| return client_dir | |
| def start_client(client_num): | |
| """Start a client""" | |
| if not has_client_config(client_num): | |
| suffix = "" if client_num == 1 else str(client_num - 1) | |
| missing = [v for v in REQUIRED_VARS if not os.environ.get(v + suffix)] | |
| print(f"β Client {client_num}: Missing {', '.join(missing)}") | |
| return None | |
| print(f"β Client {client_num}: Starting...") | |
| base_dir = os.getcwd() | |
| client_dir = setup_client_dir(client_num, base_dir) | |
| # Build environment | |
| env = os.environ.copy() | |
| suffix = "" if client_num == 1 else str(client_num - 1) | |
| # Get values | |
| api_id = os.environ.get(f"API_ID{suffix}") or os.environ.get("API_ID") | |
| api_hash = os.environ.get(f"API_HASH{suffix}") or os.environ.get("API_HASH") | |
| session = os.environ.get(f"SESSION{suffix}") or os.environ.get("SESSION") | |
| mongo_uri = os.environ.get(f"MONGO_URI{suffix}") or os.environ.get("MONGO_URI") | |
| # Unique database | |
| db_name = f"UltroidDB{client_num}" | |
| if mongo_uri.endswith('/'): | |
| env["MONGO_URI"] = f"{mongo_uri}{db_name}" | |
| else: | |
| env["MONGO_URI"] = f"{mongo_uri}/{db_name}" | |
| # Optional vars | |
| if os.environ.get(f"LOG_CHANNEL{suffix}"): | |
| env["LOG_CHANNEL"] = os.environ.get(f"LOG_CHANNEL{suffix}") | |
| elif os.environ.get("LOG_CHANNEL"): | |
| env["LOG_CHANNEL"] = os.environ.get("LOG_CHANNEL") | |
| if os.environ.get(f"BOT_TOKEN{suffix}"): | |
| env["BOT_TOKEN"] = os.environ.get(f"BOT_TOKEN{suffix}") | |
| elif os.environ.get("BOT_TOKEN"): | |
| env["BOT_TOKEN"] = os.environ.get("BOT_TOKEN") | |
| env["PYTHONPATH"] = base_dir | |
| # Note: The .restart command will work correctly because: | |
| # - Working directory (client_dir) is preserved by os.execl() | |
| # - Environment variables (PYTHONPATH, MONGO_URI, etc.) are preserved | |
| # - sys.argv arguments (API_ID, API_HASH, SESSION) are preserved | |
| # The restart function in pyUltroid/fns/helper.py handles this automatically | |
| try: | |
| proc = subprocess.Popen( | |
| [sys.executable, "-m", "pyUltroid", api_id, api_hash, session, "", ""], | |
| cwd=client_dir, | |
| env=env, | |
| ) | |
| # Save PID file | |
| pid_file = os.path.join(base_dir, f"client_{client_num}.pid") | |
| try: | |
| with open(pid_file, 'w') as f: | |
| f.write(str(proc.pid)) | |
| except: | |
| pass | |
| print(f" β Started (PID: {proc.pid})") | |
| return proc | |
| except Exception as e: | |
| print(f" β Error: {e}") | |
| return None | |
| def main(): | |
| print("=" * 60) | |
| print("Multi-Client Ultroid Launcher v2 (Simplified)") | |
| print("=" * 60) | |
| print() | |
| # First, stop clients that don't have config | |
| print("Checking for clients without configuration...") | |
| stopped_count = 0 | |
| for i in range(1, 6): | |
| if not has_client_config(i): | |
| # Check if this client is running and stop it | |
| base_dir = os.getcwd() | |
| pid_file = os.path.join(base_dir, f"client_{i}.pid") | |
| if os.path.exists(pid_file): | |
| stop_client(i) | |
| stopped_count += 1 | |
| if stopped_count > 0: | |
| print(f"β Stopped {stopped_count} client(s) without configuration") | |
| print() | |
| # Now start clients with proper config | |
| processes = [] | |
| for i in range(1, 6): | |
| proc = start_client(i) | |
| if proc: | |
| processes.append((i, proc)) | |
| print() | |
| if not processes: | |
| print("β No clients started. Check your .env file.") | |
| sys.exit(1) | |
| print("=" * 60) | |
| print(f"β {len(processes)} client(s) running") | |
| for num, proc in processes: | |
| print(f" Client {num}: PID {proc.pid}") | |
| print("=" * 60) | |
| print("\nPress Ctrl+C to exit (clients keep running)") | |
| print("Monitoring clients - will stop any that lose configuration...") | |
| try: | |
| import time | |
| while True: | |
| time.sleep(30) # Check every 30 seconds | |
| # Check all clients and stop those without config | |
| for i in range(1, 6): | |
| if not has_client_config(i): | |
| # Check if this client is still running | |
| base_dir = os.getcwd() | |
| pid_file = os.path.join(base_dir, f"client_{i}.pid") | |
| if os.path.exists(pid_file): | |
| try: | |
| with open(pid_file, 'r') as f: | |
| pid = int(f.read().strip()) | |
| # Check if process is still alive | |
| try: | |
| if sys.platform == "win32": | |
| result = subprocess.run(["tasklist", "/FI", f"PID eq {pid}"], | |
| capture_output=True, check=False) | |
| if str(pid) in result.stdout.decode(): | |
| # Process exists, kill it | |
| subprocess.run(["taskkill", "/F", "/PID", str(pid)], | |
| capture_output=True, check=False) | |
| print(f"\nβ Client {i} stopped (configuration removed)") | |
| else: | |
| os.kill(pid, 0) # Check if process exists | |
| # Process exists, kill it | |
| os.kill(pid, 15) # SIGTERM | |
| print(f"\nβ Client {i} stopped (configuration removed)") | |
| except (ProcessLookupError, OSError): | |
| # Process already dead | |
| pass | |
| # Remove PID file | |
| try: | |
| os.remove(pid_file) | |
| except: | |
| pass | |
| except Exception: | |
| pass | |
| except KeyboardInterrupt: | |
| print("\n\nExiting. Clients still running.") | |
| print("Stop with: pkill -f pyUltroid") | |
| if __name__ == "__main__": | |
| main() |