From e430d1a70cd32a11d158205ac085c6aa6e990691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=91=D0=BE=D0=B1?= =?UTF-8?q?=D1=8B=D1=80=D0=B5=D0=B2?= Date: Mon, 1 Jun 2026 00:54:26 +0300 Subject: [PATCH] fix: recover secondary API key handling --- app.py | 116 ++++++++++++++++++++++---------------- server/secondary-setup.sh | 7 ++- 2 files changed, 73 insertions(+), 50 deletions(-) diff --git a/app.py b/app.py index f3dd01c..5960442 100644 --- a/app.py +++ b/app.py @@ -591,24 +591,15 @@ def add_peer_to_server(server_ip, public_key, allowed_ips): return {"status": "error", "msg": str(e)} # Secondary — call API servers = load_servers() - api_key = "" + target_server = {"ip": server_ip, "api_key": ""} for srv in servers: if srv["ip"] == server_ip: - api_key = srv.get("api_key", "") + target_server = srv break - if not api_key: - return {"status": "error", "msg": f"No API key for {server_ip}"} try: - import urllib.request - data = json.dumps({"public_key": public_key, "allowed_ips": allowed_ips}).encode() - req = urllib.request.Request( - f"http://{server_ip}:8444/api/peers/add", - data=data, - headers={"X-API-Key": api_key, "Content-Type": "application/json"}, - method="POST" - ) - resp = urllib.request.urlopen(req, timeout=10) - return json.loads(resp.read()) + return secondary_api_request(target_server, "/api/peers/add", { + "public_key": public_key, "allowed_ips": allowed_ips + }, timeout=10) except Exception as e: return {"status": "error", "msg": str(e)[:200]} @@ -785,23 +776,15 @@ def fanout_router_config(client_id): conf = generate_failover_conf_for_client(client_id) except Exception: return - import urllib.request - payload = json.dumps({"client_id": client_id, "conf": conf}).encode() for srv in load_servers(): ip = srv.get("ip", "") - key = srv.get("api_key", "") if not ip: continue # skip servers the monitor already knows are down (avoid blocking) if server_stats_cache.get(ip, {}).get("status") == "down": continue try: - req = urllib.request.Request( - f"http://{ip}:8444/api/router-config-set", - data=payload, - headers={"X-API-Key": key, "Content-Type": "application/json"}, - method="POST") - urllib.request.urlopen(req, timeout=3) + secondary_api_request(srv, "/api/router-config-set", {"client_id": client_id, "conf": conf}, timeout=3) except Exception: pass @@ -1606,6 +1589,56 @@ def save_servers(servers): json.dump(servers, f, indent=2) +def _server_api_key_candidates(server): + keys = [] + for key in (server.get("api_key", ""), load_settings().get("server_api_key", "")): + if key and key not in keys: + keys.append(key) + return keys + + +def _remember_server_api_key(ip, api_key): + if not ip or not api_key: + return + servers = load_servers() + changed = False + for srv in servers: + if srv.get("ip") == ip and srv.get("api_key") != api_key: + srv["api_key"] = api_key + changed = True + if changed: + save_servers(servers) + + +def secondary_api_request(server, path, data=None, timeout=10): + import urllib.error + import urllib.request + headers = {"Content-Type": "application/json"} + body = json.dumps(data).encode() if data is not None else None + last_error = None + for key in _server_api_key_candidates(server): + headers["X-API-Key"] = key + try: + req = urllib.request.Request( + f"http://{server['ip']}:8444{path}", + data=body, + headers=headers, + method="POST" if body is not None else "GET", + ) + resp = urllib.request.urlopen(req, timeout=timeout) + _remember_server_api_key(server.get("ip", ""), key) + server["api_key"] = key + return json.loads(resp.read()) + except urllib.error.HTTPError as e: + last_error = e + if e.code in (401, 403): + continue + raise + if last_error: + raise last_error + raise RuntimeError("no API key for secondary server") + + def panel_port(): try: return open(os.path.join(PANEL_DIR, ".port")).read().strip() or "8443" @@ -1666,11 +1699,7 @@ def get_all_servers_ordered(): def check_server_health(server): """Check secondary server health via API.""" try: - import urllib.request - url = f"http://{server['ip']}:8444/api/health" - req = urllib.request.Request(url, headers={"X-API-Key": server.get("api_key", "")}) - resp = urllib.request.urlopen(req, timeout=5) - return json.loads(resp.read()) + return secondary_api_request(server, "/api/health", timeout=5) except Exception as e: return {"status": "error", "error": str(e)} @@ -1678,17 +1707,11 @@ def check_server_health(server): def sync_peer_to_server(server, public_key, allowed_ips, action="add"): """Add or remove peer on secondary server.""" try: - import urllib.request - url = f"http://{server['ip']}:8444/api/peers/{action}" - data = json.dumps({"public_key": public_key, "allowed_ips": allowed_ips}).encode() - req = urllib.request.Request(url, data=data, headers={ - "Content-Type": "application/json", - "X-API-Key": server.get("api_key", "") - }) - resp = urllib.request.urlopen(req, timeout=10) - return json.loads(resp.read()) - except Exception: - return {"status": "error"} + return secondary_api_request(server, f"/api/peers/{action}", { + "public_key": public_key, "allowed_ips": allowed_ips + }, timeout=10) + except Exception as e: + return {"status": "error", "msg": str(e)[:200]} def sync_peer_to_all_servers(public_key, allowed_ips, action="add"): @@ -1793,6 +1816,7 @@ def api_register_server(): for srv in servers: if srv["ip"] == ip: srv.update(data) + srv["api_key"] = api_key srv["last_seen"] = datetime.now().isoformat() save_servers(servers) return json.dumps({"status": "updated"}), 200, {"Content-Type": "application/json"} @@ -1970,7 +1994,7 @@ def servers_page(): action = request.form.get("action") if action == "add": ip = request.form.get("ip", "").strip() - api_key = request.form.get("api_key", "").strip() + api_key = request.form.get("api_key", "").strip() or s.get("server_api_key", "") ssh_user = request.form.get("ssh_user", "root").strip() or "root" ssh_pass = request.form.get("ssh_pass", "").strip() ssh_port = request.form.get("ssh_port", "22").strip() or "22" @@ -2020,11 +2044,7 @@ def servers_page(): for srv in servers: if srv["ip"] == ip: try: - import urllib.request as ul - url = f"http://{ip}:8444/api/info" - rq = ul.Request(url, headers={"X-API-Key": srv.get("api_key", "")}) - resp = ul.urlopen(rq, timeout=5) - info = json.loads(resp.read()) + info = secondary_api_request(srv, "/api/info", timeout=5) srv["wg_public_key"] = info.get("wg_public_key", "") srv["obfuscator_key"] = info.get("obfuscator_key", "") srv["ports"] = ",".join(info.get("ports", [])) @@ -2327,12 +2347,12 @@ def servers_page():
- + - {hlp("Добавить новый VPN-сервер в пул резервирования. Нужны: IP, API-ключ агента (порт 8444) и SSH-пароль для первичной настройки.", "Add a new VPN server to the failover pool. Needs: IP, agent API key (port 8444) and SSH password for initial setup.")} + {hlp("Добавить новый VPN-сервер в пул резервирования. Если secondary ставился командой ниже, API key можно оставить пустым — будет использован главный ключ панели. 8444 — это порт mini-API, не ключ.", "Add a new VPN server to the failover pool. If the secondary was installed with the command below, API key may be left blank — the panel main key will be used. 8444 is the mini-API port, not the key.")}
@@ -2395,7 +2415,7 @@ def servers_page():

Deploy Secondary Server

One command deploys WG + obfuscator + mini-API on a new VPS. Auto-registers in panel. Run via SSH on new VPS:

- MAIN_SERVER={SERVER_IP} MAIN_API_KEY={api_key} bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main/server/secondary-setup.sh) + MAIN_SERVER={SERVER_IP} MAIN_PORT={panel_port()} MAIN_API_KEY={api_key} bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main/server/secondary-setup.sh)
""" return render(html) diff --git a/server/secondary-setup.sh b/server/secondary-setup.sh index c4f17f4..20d8397 100755 --- a/server/secondary-setup.sh +++ b/server/secondary-setup.sh @@ -4,7 +4,7 @@ # Deploys WG + obfuscator + mini-API (no web panel) # # Usage: -# MAIN_SERVER=212.118.52.193 MAIN_API_KEY=secret123 \ +# MAIN_SERVER=212.118.52.193 MAIN_PORT=10514 MAIN_API_KEY=secret123 \ # bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main/server/secondary-setup.sh) # # Uninstall: @@ -15,6 +15,7 @@ set -e MAIN_SERVER="${MAIN_SERVER:?Set MAIN_SERVER=ip_of_main_server}" MAIN_API_KEY="${MAIN_API_KEY:?Set MAIN_API_KEY=your_api_key}" +MAIN_PORT="${MAIN_PORT:-8443}" OBF_PORTS="${OBF_PORTS:-2083,5443,993}" PHOBOS_DIR="/opt/Phobos" @@ -30,6 +31,7 @@ echo "╠═══════════════════════ echo "║ Server IP : $SERVER_IP" echo "║ Interface : $IFACE" echo "║ Main server : $MAIN_SERVER" +echo "║ Main port : $MAIN_PORT" echo "║ OBF ports : $OBF_PORTS" echo "╚══════════════════════════════════════════════════════╝" echo "" @@ -233,6 +235,7 @@ OBFUSCATOR_KEY=$OBF_KEY OBFUSCATOR_PORTS=$OBF_PORTS MAIN_SERVER=$MAIN_SERVER MAIN_API_KEY=$MAIN_API_KEY +MAIN_PORT=$MAIN_PORT ROLE=secondary EOF @@ -367,7 +370,7 @@ REG_DATA=$(cat << EOF } EOF ) -curl -s -X POST "http://${MAIN_SERVER}:$(cat /opt/phobos-panel/.port 2>/dev/null || echo 8443)/api/servers/register" \ +curl -s -X POST "http://${MAIN_SERVER}:${MAIN_PORT}/api/servers/register" \ -H "Content-Type: application/json" \ -H "X-API-Key: ${MAIN_API_KEY}" \ -d "$REG_DATA" || echo " (Registration will be done manually via panel)"