root 6 месяцев назад
Родитель
Сommit
835ee62e2e
1 измененных файлов с 64 добавлено и 42 удалено
  1. 64 42
      app.py

+ 64 - 42
app.py

@@ -862,7 +862,8 @@ def import_backup():
 @api_required
 def api_cluster_adblock():
     data = request.json
-    action, master_ip = data.get('action'), request.headers.get('X-Forwarded-For', request.remote_addr).split(',')[0].strip()
+    action = data.get('action')
+    master_ip = request.headers.get('X-Forwarded-For', request.remote_addr).split(',')[0].strip()
 
     if 'lists' in data: fm.save_adblock_lists(data['lists'])
     if 'rules' in data: fm.save_custom_rules(data['rules'])
@@ -894,13 +895,17 @@ def api_cluster_adblock():
 @api_required
 def api_add_slave():
     data = request.json
-    if not data or 'domain' not in data: return jsonify({"status": "error", "message": "Missing parameters"}), 400
+    if not data or 'domain' not in data: 
+        return jsonify({"status": "error", "message": "Missing parameters"}), 400
         
     domain = re.sub(r'[^a-zA-Z0-9\.\-\/]', '', data['domain']).replace('../', '')
     master_ip = request.headers.get('X-Forwarded-For', request.remote_addr).split(',')[0].strip()
     
     conf = fm.read_file(config.LOCAL_CONF)
-    if f'zone "{domain}"' in conf: return jsonify({"status": "error", "message": "Zone already exists"}), 409
+    
+    # ΠΡΟΣΤΑΣΙΑ: Αν η ζώνη υπάρχει ήδη, ΔΕΝ την πειράζουμε καθόλου. Κρατάει τον Master που είχε!
+    if f'zone "{domain}"' in conf: 
+        return jsonify({"status": "error", "message": "Zone already exists"}), 409
         
     path = os.path.join(config.CACHE_PATH, f"db.{domain}")
     conf += f'\nzone "{domain}" {{\n    type slave;\n    file "{path}";\n    masters {{ {master_ip}; }};\n    masterfile-format text;\n}};\n'
@@ -909,9 +914,7 @@ def api_add_slave():
     subprocess.run(["rndc", "reconfig"])
     fm.log_audit("API_SYNC", domain, f"Auto-created as Slave (Master Auto-Detected: {master_ip})")
     
-    settings = fm.load_panel_settings()
-    settings['master_node'] = {"name": f"Auto-Linked Master ({master_ip})", "url": f"http://{master_ip}:5000"}
-    fm.save_panel_settings(settings)
+    # ΑΦΑΙΡΕΘΗΚΕ ΤΟ AUTO-LINKING! Ο κώδικας δεν πειράζει πια τα settings του Slave.
     return jsonify({"status": "success", "message": f"Slave zone {domain} configured."})
 
 @app.route('/api/v1/cluster/zone/<domain>', methods=['DELETE'])
@@ -934,11 +937,7 @@ def api_delete_zone(domain):
 @app.route('/api/v1/cluster/ping', methods=['GET'])
 @api_required
 def api_ping():
-    master_ip = request.headers.get('X-Forwarded-For', request.remote_addr).split(',')[0].strip()
-    settings = fm.load_panel_settings()
-    if settings.get('master_node', {}).get('url') != f"http://{master_ip}:5000":
-        settings['master_node'] = {"name": f"Master Node ({master_ip})", "url": f"http://{master_ip}:5000"}
-        fm.save_panel_settings(settings)
+    # ΑΦΑΙΡΕΘΗΚΕ ΤΟ AUTO-LINKING! Το Ping πλέον απλά επιβεβαιώνει ότι το API Key είναι σωστό.
     return jsonify({"status": "success", "message": "Node is synchronized"})
 
 @app.route('/cluster/check_node', methods=['POST'])
@@ -967,13 +966,21 @@ def check_cluster_node():
 @app.route('/api/v1/cluster/service', methods=['POST'])
 @api_required
 def api_cluster_service():
-    action = request.json.get('action')
+    # Fix: Ασφαλής ανάγνωση του JSON, χωρίς να σκάει
+    data = request.get_json(force=True, silent=True) or {}
+    action = data.get('action')
+    
     if action in ['start', 'stop', 'restart', 'reload']:
         try:
-            subprocess.run(["systemctl", action, config.BIND_SERVICE], check=True)
-            return jsonify({"status": "success", "message": f"Service {action}ed successfully"})
+            # Fix: Πιάνουμε το πραγματικό error του systemctl αν αποτύχει
+            res = subprocess.run(["systemctl", action, config.BIND_SERVICE], capture_output=True, text=True)
+            if res.returncode == 0:
+                return jsonify({"status": "success", "message": f"Service {action}ed successfully"})
+            else:
+                return jsonify({"status": "error", "message": f"Systemctl failed: {res.stderr.strip()}"})
         except Exception as e:
             return jsonify({"status": "error", "message": str(e)})
+            
     elif action == 'status':
         try:
             res = subprocess.run(["systemctl", "is-active", config.BIND_SERVICE], capture_output=True, text=True)
@@ -981,49 +988,64 @@ def api_cluster_service():
             return jsonify({"status": "success", "service_status": "active" if is_active else "inactive"})
         except:
             return jsonify({"status": "error", "service_status": "unknown"})
-    return jsonify({"status": "error", "message": "Invalid action"})
+            
+    return jsonify({"status": "error", "message": "Invalid action provided"})
 
 # --- REMOTE SERVICE PROXY (MASTER TO SLAVE) ---
 @app.route('/cluster/node_service', methods=['POST'])
 @login_required
 def cluster_node_service():
-    node_name = request.json.get('node_name')
-    action = request.json.get('action')
-    node = next((n for n in fm.load_panel_settings().get('cluster_nodes', []) if n['name'] == node_name), None)
-    if not node: return jsonify({"status": "error", "message": "Node not found"})
+    # Fix: Απαραίτητο import για να μην σκάει η urllib αν πάρει Error από τον Slave
+    import urllib.request, urllib.error
     
     try:
+        data = request.get_json(force=True, silent=True) or {}
+        node_name = data.get('node_name')
+        action = data.get('action')
+        
+        node = next((n for n in fm.load_panel_settings().get('cluster_nodes', []) if n['name'] == node_name), None)
+        if not node: 
+            return jsonify({"status": "error", "message": "Node not found."})
+        
         url = f"{node['url'].rstrip('/')}/api/v1/cluster/service"
-        proxy_handler = urllib.request.ProxyHandler({})
-        opener = urllib.request.build_opener(proxy_handler)
         payload = {"action": action}
+        
+        proxy_handler = urllib.request.ProxyHandler({})
+        https_handler = urllib.request.HTTPSHandler(context=config.ssl_ctx)
+        opener = urllib.request.build_opener(proxy_handler, https_handler)
+        
         req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers={"X-API-Key": node['api_key'], "Content-Type": "application/json"}, method='POST')
         
-        with opener.open(req, timeout=10) as response:
+        # Fix: Timeout στα 15 δευτερόλεπτα! Το BIND μπορεί να κάνει ώρα να κάνει restart
+        with opener.open(req, timeout=15) as response:
             return jsonify(json.loads(response.read().decode('utf-8')))
+            
+    except urllib.error.HTTPError as e:
+        return jsonify({"status": "error", "message": f"Slave returned HTTP Error {e.code}. Check API keys."})
     except Exception as e:
-        return jsonify({"status": "error", "message": str(e)})
-
-    # --- GLOBAL CLUSTER SYNC (MASTER ACTION) ---
-    @app.route('/api/cluster/sync_all', methods=['POST'])
-    @admin_required
-    def sync_all_cluster():
-        conf = fm.read_file(config.LOCAL_CONF)
-        # Βρίσκουμε όλα τα domains που είναι "type master"
-        zones = re.findall(r'zone\s+"([^"]+)"\s*\{.*?type\s+master;', conf, re.DOTALL)
-        # Εξαιρούμε το RPZ, θα συγχρονιστεί από το AdBlock μενού
-        zones = [z for z in zones if z != "rpz.blacklist"]
+        return jsonify({"status": "error", "message": f"Connection failed: {str(e)}"})
+
+# --- GLOBAL CLUSTER SYNC (MASTER ACTION) ---
+@app.route('/api/cluster/sync_all', methods=['POST'])
+@admin_required
+def sync_all_cluster():
+    conf = fm.read_file(config.LOCAL_CONF)
+    # Βρίσκουμε όλα τα domains που είναι "type master"
+    zones = re.findall(r'zone\s+"([^"]+)"\s*\{.*?type\s+master;', conf, re.DOTALL)
+    # Εξαιρούμε το RPZ, θα συγχρονιστεί από το AdBlock μενού
+    zones = [z for z in zones if z != "rpz.blacklist"]
+    
+    if not zones:
+        return jsonify({"status": "error", "message": "No Master zones found to sync."})
         
-        if not zones:
-            return jsonify({"status": "error", "message": "No Master zones found to sync."})
+    def bulk_sync():
+        for z in zones:
+            cm.broadcast_to_cluster('add_slave', z)
+            time.sleep(0.5) # Μικρή καθυστέρηση για να μην "μπουκώσει" ο Slave
             
-        def bulk_sync():
-            for z in zones:
-                cm.broadcast_to_cluster('add_slave', z)
-                time.sleep(0.5) # Μικρή καθυστέρηση για να μην "μπουκώσει" ο Slave
-                
-        threading.Thread(target=bulk_sync, daemon=True).start()
-        return jsonify({"status": "success", "message": f"Started pushing {len(zones)} zones to all Nodes!"})
+    import threading
+    threading.Thread(target=bulk_sync, daemon=True).start()
+    return jsonify({"status": "success", "message": f"Started pushing {len(zones)} zones to all Nodes!"})
 if __name__ == '__main__':
     # Είναι σημαντικό το threaded=True για να μην κάνει "block" το UI στο self-ping
     app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)