Add Fleet control plane and split manage.sh deploy menu.
Strategy nodes gain fleet token APIs; control/ app for local ops; manage.sh offers strategy vs control one-click deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
"""代理调用策略机。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from .config import get_control_settings
|
||||
from .crypto import unseal
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def node_token_plain(node: dict[str, Any]) -> str | None:
|
||||
sealed = (node.get("token_sealed") or "").strip()
|
||||
if not sealed:
|
||||
return None
|
||||
secret = get_control_settings().control_auth_secret
|
||||
try:
|
||||
return unseal(sealed, secret)
|
||||
except Exception:
|
||||
logger.exception("unseal fleet token failed node=%s", node.get("id"))
|
||||
return None
|
||||
|
||||
|
||||
async def call_node(
|
||||
node: dict[str, Any],
|
||||
method: str,
|
||||
path: str,
|
||||
*,
|
||||
require_token: bool = True,
|
||||
json_body: dict | None = None,
|
||||
) -> tuple[int, Any]:
|
||||
settings = get_control_settings()
|
||||
base = str(node["base_url"]).rstrip("/")
|
||||
headers: dict[str, str] = {}
|
||||
if require_token:
|
||||
tok = node_token_plain(node)
|
||||
if not tok:
|
||||
return 400, {"detail": "该机尚未生成/保存中控 Token"}
|
||||
headers["X-Fleet-Token"] = tok
|
||||
url = f"{base}{path}"
|
||||
timeout = settings.control_http_timeout_sec
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client:
|
||||
res = await client.request(method, url, headers=headers, json=json_body)
|
||||
try:
|
||||
data = res.json()
|
||||
except Exception:
|
||||
data = {"detail": res.text[:500]}
|
||||
return res.status_code, data
|
||||
except Exception as e:
|
||||
return 502, {"detail": f"连接失败: {e}"}
|
||||
|
||||
|
||||
async def probe_health(node: dict[str, Any]) -> dict[str, Any]:
|
||||
code, data = await call_node(node, "GET", "/health", require_token=False)
|
||||
if code == 200 and isinstance(data, dict):
|
||||
return {"online": True, "health": data, "error": None}
|
||||
# fallback fleet status if health blocked
|
||||
code2, data2 = await call_node(node, "GET", "/api/fleet/status", require_token=True)
|
||||
if code2 == 200 and isinstance(data2, dict):
|
||||
return {"online": True, "health": data2, "error": None}
|
||||
detail = ""
|
||||
if isinstance(data, dict):
|
||||
detail = str(data.get("detail") or "")
|
||||
return {
|
||||
"online": False,
|
||||
"health": None,
|
||||
"error": detail or f"HTTP {code}",
|
||||
}
|
||||
Reference in New Issue
Block a user