root hace 6 meses
padre
commit
3019fdfd80
Se han modificado 3 ficheros con 51 adiciones y 11 borrados
  1. 16 8
      app.py
  2. 18 2
      core/cluster_manager.py
  3. 17 1
      core/dns_manager.py

+ 16 - 8
app.py

@@ -894,28 +894,36 @@ def api_cluster_adblock():
 @app.route('/api/v1/cluster/zone', methods=['POST'])
 @api_required
 def api_add_slave():
-    data = request.json
+    data = request.get_json(force=True, silent=True) or {}
     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)
+    # --- Η ΝΕΑ ΛΟΓΙΚΗ ---
+    # 1. Αν ο Master μας έστειλε λίστα, τη χρησιμοποιούμε αυτούσια
+    if data.get('masters'):
+        masters_list = data['masters']
+    else:
+        # 2. Fallback στην IP αυτού που μας κάλεσε (αν δεν στάλθηκαν IPs)
+        master_ip = request.headers.get('X-Forwarded-For', request.remote_addr).split(',')[0].strip()
+        masters_list = [master_ip]
     
-    # ΠΡΟΣΤΑΣΙΑ: Αν η ζώνη υπάρχει ήδη, ΔΕΝ την πειράζουμε καθόλου. Κρατάει τον Master που είχε!
+    # Μετατροπή της λίστας σε string για το BIND config (π.χ. "1.1.1.1; 2.2.2.2;")
+    masters_string = "".join([f" {ip};" for ip in masters_list])
+
+    conf = fm.read_file(config.LOCAL_CONF)
     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'
+    conf += f'\nzone "{domain}" {{\n    type slave;\n    file "{path}";\n    masters {{{masters_string} }};\n    masterfile-format text;\n}};\n'
     
     fm.write_file(config.LOCAL_CONF, conf)
     subprocess.run(["rndc", "reconfig"])
-    fm.log_audit("API_SYNC", domain, f"Auto-created as Slave (Master Auto-Detected: {master_ip})")
+    fm.log_audit("API_SYNC", domain, f"Auto-created Slave (Masters: {masters_string})")
     
-    # ΑΦΑΙΡΕΘΗΚΕ ΤΟ AUTO-LINKING! Ο κώδικας δεν πειράζει πια τα settings του Slave.
-    return jsonify({"status": "success", "message": f"Slave zone {domain} configured."})
+    return jsonify({"status": "success", "message": f"Slave zone {domain} configured with masters: {masters_string}"})
 
 @app.route('/api/v1/cluster/zone/<domain>', methods=['DELETE'])
 @api_required

+ 18 - 2
core/cluster_manager.py

@@ -6,19 +6,35 @@ from core.file_manager import load_panel_settings, load_adblock_lists, load_cust
 
 def broadcast_to_cluster(action, domain):
     def task():
+        from core import dns  # Import εδώ για αποφυγή circular import
         settings = load_panel_settings()
         nodes = settings.get('cluster_nodes', [])
+        
+        # Βρίσκουμε ποιους masters πρέπει να στείλουμε
+        masters = dns.get_zone_masters_list(domain)
+        
+        # Αν η λίστα είναι κενή, σημαίνει ότι η ζώνη είναι Master στον Master μας,
+        # οπότε στέλνουμε την Public IP αυτού του Server.
+        if not masters:
+            master_ip = settings.get('server_public_ip', '').strip()
+            if master_ip:
+                masters = [master_ip]
+
         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}
+                
+                # Στέλνουμε στο Payload και τις IPs των masters
+                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=5, context=config.ssl_ctx)
-                print(f"Cluster Sync OK: {node['name']} ({action} {domain})")
+                print(f"Cluster Sync OK: {node['name']} ({action} {domain} -> Masters: {masters})")
             except Exception as e:
                 print(f"Cluster Sync FAIL: {node['name']} -> {e}")
     threading.Thread(target=task, daemon=True).start()

+ 17 - 1
core/dns_manager.py

@@ -136,4 +136,20 @@ def construct_record_value(req_form, rtype):
         if len(parts) >= 2 and not parts[-1].endswith('.'):
             parts[-1] = f"{parts[-1]}."
             v = " ".join(parts)
-    return v
+    return v
+    
+def get_zone_masters_list(domain):
+    """
+    Επιστρέφει μια λίστα με IPs των Masters. 
+    Αν είναι Master ζώνη, επιστρέφει κενή λίστα (θα τη χειριστεί ο cluster_manager).
+    """
+    conf = read_file(config.LOCAL_CONF)
+    block = get_zone_block(conf, domain)
+    if not block: return []
+    
+    if "type slave" in block or "type forward" in block:
+        # Ψάχνουμε IPs μέσα στα άγκιστρα { ... }
+        m = re.search(r'(?:masters|forwarders)\s*\{\s*([^;\}]+);', block, re.S | re.I)
+        if m:
+            return [ip.strip() for ip in m.group(1).split(';') if ip.strip()]
+    return []