import json import threading import urllib.request import ssl import config from core.file_manager import load_panel_settings, load_adblock_lists, load_custom_rules def _get_ssl_context(): ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE return ctx def broadcast_to_cluster(action, domain): def task(): from core import dns_manager as dns settings = load_panel_settings() nodes = settings.get('cluster_nodes', []) masters = dns.get_zone_masters_list(domain) if not masters: master_ip = settings.get('server_public_ip', '').strip() if master_ip: masters = [master_ip] ctx = _get_ssl_context() for node in nodes: try: url = f"{node['url'].rstrip('/')}/api/v1/cluster/zone" headers = {"X-API-Key": node['api_key'], "Content-Type": "application/json"} payload = {"domain": domain, "masters": masters} if action == 'add_slave': req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers=headers, method='POST') elif action == 'delete_zone': req = urllib.request.Request(f"{url}/{domain}", headers=headers, method='DELETE') urllib.request.urlopen(req, timeout=10, context=ctx) print(f"Cluster Sync OK: {node['name']} ({action} {domain})") except Exception as e: print(f"Cluster Sync FAIL: {node['name']} -> {e}") threading.Thread(target=task, daemon=True).start() def broadcast_adblock_to_cluster(action): def task(): s = load_panel_settings() lists = load_adblock_lists() rules = load_custom_rules() count = s.get('adblock_domains_count', 0) update_time = s.get('adblock_last_update', 'Never') ctx = _get_ssl_context() for node in s.get('cluster_nodes', []): try: url = f"{node['url'].rstrip('/')}/api/v1/cluster/adblock" headers = {"X-API-Key": node['api_key'], "Content-Type": "application/json"} payload = { "action": action, "lists": lists, "rules": rules, "count": count, "time": update_time } req = urllib.request.Request(url, data=json.dumps(payload).encode(), headers=headers, method='POST') urllib.request.urlopen(req, timeout=10, context=ctx) print(f"Cluster AdBlock Sync OK: {node['name']} ({action})") except Exception as e: print(f"Cluster AdBlock Sync FAIL: {node['name']} -> {e}") threading.Thread(target=task, daemon=True).start() def broadcast_user_to_cluster(action, username, user_data=None, old_username=None): def task(): settings = load_panel_settings() nodes = settings.get('cluster_nodes', []) ctx = _get_ssl_context() for node in nodes: try: url = f"{node['url'].rstrip('/')}/api/v1/cluster/user" headers = {"X-API-Key": node['api_key'], "Content-Type": "application/json"} if action == 'save': payload = {"username": username, "user_data": user_data, "old_username": old_username} req = urllib.request.Request(url, data=json.dumps(payload).encode(), headers=headers, method='POST') elif action == 'delete': req = urllib.request.Request(f"{url}/{username}", headers=headers, method='DELETE') urllib.request.urlopen(req, timeout=5, context=ctx) print(f"Cluster Sync OK: {node['name']} (User {action} -> {username})") except Exception as e: print(f"Cluster Sync FAIL: {node['name']} -> User {username} ({e})") threading.Thread(target=task, daemon=True).start() def broadcast_templates_to_cluster(templates_dict): def task(): settings = load_panel_settings() nodes = settings.get('cluster_nodes', []) ctx = _get_ssl_context() for node in nodes: try: url = f"{node['url'].rstrip('/')}/api/v1/cluster/templates" headers = {"X-API-Key": node['api_key'], "Content-Type": "application/json"} req = urllib.request.Request(url, data=json.dumps(templates_dict).encode(), headers=headers, method='POST') urllib.request.urlopen(req, timeout=5, context=ctx) print(f"Cluster Sync OK: {node['name']} (Templates updated)") except Exception as e: print(f"Cluster Sync FAIL: {node['name']} -> Templates ({e})") threading.Thread(target=task, daemon=True).start()