Files
Nexus/server/infrastructure/ssh/remote_probe.py
T
Your Name cdd0be328a fix: 六轮深度扫描 — 47项Bug修复、安全加固、死代码清理
Critical runtime bugs:
- terminal.html WebSSH完全不可用(URL前缀/JSON解析/Content-Type三处错误)
- servers.py路由遮蔽:/logs被/{id}拦截,3个前端页面同步日志查询失败
- scripts.html startExecPoll()→startExecPolling(),长任务快速执行崩溃
- agent.py {value!r!s:.50}格式串非法,agent发非数值时ValueError
- alerts.html d.daily.reduce()无null检查,API返回空数据时TypeError

Resource leak / stability:
- websocket.py僵尸连接未关闭TCP,文件描述符泄漏
- websocket.py _last_alert_time字典无限增长(加1小时过期清理)
- asyncssh_pool.py全忙时超过MAX_CONNECTIONS无限增长
- self_monitor.py Telegram告警无冷却,宕机时每30秒刷屏
- schedule_runner.py一次性调度执行超60秒会重复触发
- 限速脚本EXPIRE每次重置窗口可绕过(改用Lua原子脚本)

Security:
- JWT access token加token_version声明,改密码后旧token立失效(零宽限)
- INSTALL_MODE导入时常量→动态函数,安装后JWT认证不再残留禁用
- install.py /lock端点加管理员存在性验证,防止阻断安装
- ServerUpdate schema移除connectivity只读字段,防止伪造连接状态

Frontend fixes:
- doExec()缺r.ok检查、commands.html null检查
- _server_to_dict()补last_checked_at+ssh_key_public
- _field_match()逗号cron表达式修复
- alerts类型显示、SSH会话名称、搜索高亮定位
- 一次性/循环定时任务(run_mode+fire_at+自动禁用)

Dead code removed (400+ lines):
- SyncService batch_push/_push_single等5个方法(零调用者)
- 5个未使用schema(SyncCommands/SyncConfig/SyncSftp/FileDeploy/PaginatedResponse)
- 6个零调用service方法、3个无前端API端点
- 4个未使用import

Schema migrations:
- push_schedules: run_mode + fire_at列,cron_expr改NULL
- servers: 7个新列 + ssh_key_private/public VARCHAR(500)→TEXT

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-24 16:26:40 +08:00

86 lines
2.9 KiB
Python

"""SSH-based remote health / metrics probe (no Agent HTTP port required)."""
import json
import logging
from typing import Any
from server.domain.models import Server
from server.infrastructure.ssh.asyncssh_pool import exec_ssh_command
logger = logging.getLogger("nexus.remote_probe")
# Emits one JSON line: {"status":"healthy","system_info":{...}}
SSH_HEALTH_METRICS_CMD = r"""python3 -c "
import json, platform
from datetime import datetime, timezone
info = {
'hostname': platform.node(),
'platform': platform.platform(),
'agent_time': datetime.now(timezone.utc).isoformat(),
'probe': 'ssh',
}
try:
import psutil
info['cpu_usage'] = psutil.cpu_percent(interval=0.3)
info['mem_usage'] = psutil.virtual_memory().percent
info['disk_usage'] = round(psutil.disk_usage('/').percent, 1)
except Exception as exc:
info['metrics_error'] = str(exc)[:200]
print(json.dumps({'status': 'healthy', 'system_info': info}))
" 2>/dev/null || python3.12 -c "
import json, platform
from datetime import datetime, timezone
info = {
'hostname': platform.node(),
'platform': platform.platform(),
'agent_time': datetime.now(timezone.utc).isoformat(),
'probe': 'ssh',
}
try:
import psutil
info['cpu_usage'] = psutil.cpu_percent(interval=0.3)
info['mem_usage'] = psutil.virtual_memory().percent
info['disk_usage'] = round(psutil.disk_usage('/').percent, 1)
except Exception as exc:
info['metrics_error'] = str(exc)[:200]
print(json.dumps({'status': 'healthy', 'system_info': info}))
" 2>/dev/null || echo '{"status":"healthy","system_info":{"hostname":"'"$(hostname)"'","probe":"ssh","ssh_only":true}}'
"""
def _parse_health_stdout(stdout: str) -> dict[str, Any] | None:
for line in reversed(stdout.strip().splitlines()):
line = line.strip()
if not line.startswith("{"):
continue
try:
data = json.loads(line)
if isinstance(data, dict) and data.get("status"):
return data
except json.JSONDecodeError:
continue
return None
async def ssh_health_probe(server: Server, timeout: int = 15) -> dict[str, Any]:
"""Run metrics probe over SSH; returns {status, system_info?, error?, channel}."""
result = await exec_ssh_command(server, SSH_HEALTH_METRICS_CMD, timeout=timeout)
if result.get("exit_code", -1) != 0:
return {
"status": "offline",
"error": (result.get("stderr") or result.get("stdout") or "SSH probe failed")[:300],
"channel": "ssh",
}
parsed = _parse_health_stdout(result.get("stdout", ""))
if not parsed:
return {
"status": "online",
"system_info": {"probe": "ssh", "raw": result.get("stdout", "")[:500]},
"channel": "ssh",
}
return {
"status": "online" if parsed.get("status") == "healthy" else "offline",
"system_info": parsed,
"channel": "ssh",
}