158 lines
6.4 KiB
Python
158 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
||
"""fix_gateway_port.py — 自愈系统调用的网关/XMPP Bot修复脚本
|
||
v3: 2026-08-10 修复自愈反噬死循环(8/3起gateway每5分钟被重启→所有LLM cron停摆)
|
||
1. 重启冷却:30分钟内不重复 restart(防死循环)
|
||
2. 等待120s:覆盖LLM冷启动20-100s(原5s必误判)
|
||
3. 区分故障类型:LLM端点/网络故障不重启(重启gateway无用),只告警
|
||
"""
|
||
import subprocess, sys, time, socket, json, urllib.request, os
|
||
from datetime import datetime
|
||
|
||
GATEWAY_PORT = 8643
|
||
BOT_PORT = 5805
|
||
BOT_SCRIPT = "/home/hmo/xmpp_zhiwei_bot.py"
|
||
API_KEY = "hermes123"
|
||
SESSION_ID = "xmpp-zhiwei"
|
||
GATEWAY_URL = f"http://127.0.0.1:{GATEWAY_PORT}/v1/chat/completions"
|
||
|
||
# v3 新增:重启冷却与等待
|
||
RESTART_COOLDOWN_SEC = 1800 # 30分钟内最多重启1次
|
||
RESTART_WAIT_SEC = 120 # 重启后等待120s(冷启动20-100s)
|
||
LOCK_FILE = "/tmp/fix_gateway_port.last_restart"
|
||
|
||
# LLM端点/网络类故障关键词(这些不是gateway自身问题,重启无用)
|
||
LLM_ENDPOINT_ERR = [
|
||
"RemoteProtocolError", "empty stream", "Stream stale", "peer closed",
|
||
"Connection", "connection", "timeout", "Timeout", "HTTP 429", "429",
|
||
"RemoteDisconnected", "ConnectionError",
|
||
]
|
||
|
||
def port_open(port, host="127.0.0.1"):
|
||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
s.settimeout(2)
|
||
try:
|
||
r = s.connect_ex((host, port))
|
||
return r == 0
|
||
finally:
|
||
s.close()
|
||
|
||
def check_session_health():
|
||
"""检测 gateway LLM 是否可用——扫 agent.log 最近一次真实调用结果。
|
||
返回 (healthy: bool, error_type: str|None)
|
||
error_type: "endpoint"(LLM端点故障,不重启) / "other"(其他,可重启) / None(健康)
|
||
"""
|
||
try:
|
||
sys.path.insert(0, '/home/hmo/MoFin')
|
||
from xmpp_logger import _scan_agent_log
|
||
r = _scan_agent_log(time.time(), "zhiwei")
|
||
if r["status"] == "ok":
|
||
print(f"Session {SESSION_ID} 健康 ✓ (agent.log: latency={r.get('latency')}, {r.get('age_sec')}s前)")
|
||
return True, None
|
||
if r["status"] == "error":
|
||
err = str(r.get("error", ""))
|
||
# v3: 区分LLM端点故障 vs session卡死
|
||
is_endpoint = any(k in err for k in LLM_ENDPOINT_ERR)
|
||
print(f"Session {SESSION_ID} 不健康: {err[:100]} → "
|
||
f"{'LLM端点故障(不重启,只告警)' if is_endpoint else '其他故障(可重启)'}", file=sys.stderr)
|
||
return False, ("endpoint" if is_endpoint else "other")
|
||
print(f"Session {SESSION_ID} 无近期调用记录(空闲正常)")
|
||
return True, None
|
||
except Exception as e:
|
||
print(f"Session {SESSION_ID} 健康检查异常: {e}(按健康处理)", file=sys.stderr)
|
||
return True, None
|
||
|
||
def check_cooldown():
|
||
"""v3: 重启冷却检查。距上次重启<30分钟则拒绝再次重启。"""
|
||
try:
|
||
if os.path.exists(LOCK_FILE):
|
||
last = float(open(LOCK_FILE).read().strip())
|
||
elapsed = time.time() - last
|
||
if elapsed < RESTART_COOLDOWN_SEC:
|
||
print(f"⚠️ 距上次重启仅{int(elapsed)}s (<{RESTART_COOLDOWN_SEC}s冷却),跳过重启防止循环", file=sys.stderr)
|
||
return False
|
||
except Exception:
|
||
pass
|
||
return True
|
||
|
||
def mark_restarted():
|
||
try:
|
||
with open(LOCK_FILE, "w") as f:
|
||
f.write(str(time.time()))
|
||
except Exception:
|
||
pass
|
||
|
||
def restart_gateway():
|
||
"""通过systemd重启gateway(v3: 冷却检查 + 等待120s)"""
|
||
if not check_cooldown():
|
||
return False
|
||
print(f"Gateway 端口{GATEWAY_PORT} 异常 → 重启中...")
|
||
subprocess.run(["sudo", "systemctl", "restart", "hermes-gateway-zhiwei.service"],
|
||
timeout=30, capture_output=True)
|
||
mark_restarted()
|
||
print(f" 等待 {RESTART_WAIT_SEC}s 让gateway冷启动...")
|
||
time.sleep(RESTART_WAIT_SEC)
|
||
if port_open(GATEWAY_PORT):
|
||
print(f"Gateway 已恢复 ✓")
|
||
return True
|
||
else:
|
||
print(f"Gateway 重启后{int(RESTART_WAIT_SEC)}s仍不可达(可能冷启动更慢或真故障)", file=sys.stderr)
|
||
return False
|
||
|
||
def notify_llm_endpoint():
|
||
"""v3: LLM端点故障告警(不重启)"""
|
||
try:
|
||
sys.path.insert(0, '/home/hmo/.hermes/profiles/position-analyst/scripts')
|
||
from alert_helper import notify, INFO
|
||
notify("LLM端点故障",
|
||
"fix_gateway_port检测到LLM端点异常(非gateway自身问题),不重启gateway。"
|
||
"请检查opencode.ai/deepseek-v4-flash可用性。",
|
||
INFO)
|
||
print("LLM端点故障已告警(未重启gateway)")
|
||
except Exception as e:
|
||
print(f"告警失败: {e}", file=sys.stderr)
|
||
|
||
target = sys.argv[1] if len(sys.argv) > 1 else "all"
|
||
|
||
if target in ("all", "bot", "xmpp_bot"):
|
||
if not port_open(BOT_PORT):
|
||
print(f"XMPP bot port {BOT_PORT} CLOSED → 启动")
|
||
subprocess.run(["pkill", "-f", "xmpp_zhiwei_bot.py"], timeout=5, capture_output=True)
|
||
time.sleep(1)
|
||
subprocess.Popen(["python3", BOT_SCRIPT], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||
time.sleep(5)
|
||
if port_open(BOT_PORT):
|
||
print(f"XMPP bot 端口{BOT_PORT} 已打开 ✓")
|
||
else:
|
||
print(f"XMPP bot 修复后仍不可达", file=sys.stderr)
|
||
sys.exit(1)
|
||
else:
|
||
print(f"XMPP bot 端口{BOT_PORT} 正常 ✓")
|
||
|
||
if target in ("all", "gateway", "session"):
|
||
if not port_open(GATEWAY_PORT):
|
||
restart_gateway()
|
||
elif target in ("all", "gateway") or target == "session":
|
||
# 端口通了 → 进一步检查session健康
|
||
healthy, err_type = check_session_health()
|
||
if not healthy:
|
||
if err_type == "endpoint":
|
||
# v3: LLM端点故障不重启(重启无用且导致死循环),只告警
|
||
notify_llm_endpoint()
|
||
else:
|
||
print(f"Session {SESSION_ID} 不健康({err_type}) → 重启gateway")
|
||
restart_gateway()
|
||
else:
|
||
print(f"Gateway 端口{GATEWAY_PORT} 正常 ✓")
|
||
|
||
if target in ("all", "session"):
|
||
if port_open(GATEWAY_PORT):
|
||
healthy, err_type = check_session_health()
|
||
if not healthy:
|
||
if err_type == "endpoint":
|
||
notify_llm_endpoint()
|
||
else:
|
||
print(f"Session {SESSION_ID} 不健康({err_type}) → 重启gateway")
|
||
restart_gateway()
|
||
|
||
sys.exit(0)
|