fix(ssh): 快速添加跳过主机密钥校验并中文化错误

凭据轮询在 SSH_STRICT 默认 true 时因 HostKeyNotVerifiable 在认证前失败;
探测始终 known_hosts=None,默认 strict 改为 false,错误信息统一中文。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Nexus Agent
2026-06-07 21:31:38 +08:00
parent c50f65e8ea
commit a14fbb3df3
10 changed files with 133 additions and 14 deletions
+15 -8
View File
@@ -21,10 +21,16 @@ from server.domain.models import Server
from server.utils.files_elevation import FilesElevation
from server.utils.posix_paths import posix_dirname
from server.infrastructure.database.crypto import decrypt_value
from server.utils.sync_error_message import translate_ssh_error_message
logger = logging.getLogger("nexus.asyncssh_pool")
def _localized_ssh_error(exc: Exception, *, max_len: int = 200) -> str:
raw = str(exc)[:max_len]
return translate_ssh_error_message(raw) or raw
async def try_ssh_login(
host: str,
port: int,
@@ -35,8 +41,6 @@ async def try_ssh_login(
timeout: float = 8.0,
) -> tuple[bool, str]:
"""One-shot SSH login probe (not pooled). Returns (ok, error_message)."""
from server.config import settings
if not password and not private_key:
return False, "未提供密码或私钥"
@@ -45,9 +49,9 @@ async def try_ssh_login(
"port": port or 22,
"username": username or "root",
"connect_timeout": timeout,
# 凭据轮询探测未知主机:跳过主机密钥校验(运维平台标准;持久连接见 SSH_STRICT_HOST_CHECKING
"known_hosts": None,
}
if settings.SSH_STRICT_HOST_CHECKING.lower() in ("false", "0", "no"):
connect_kwargs["known_hosts"] = None
if password:
connect_kwargs["password"] = password
@@ -59,9 +63,9 @@ async def try_ssh_login(
conn = await asyncssh.connect(**connect_kwargs)
return True, ""
except asyncssh.Error as e:
return False, str(e)[:200]
return False, _localized_ssh_error(e)
except Exception as e:
return False, str(e)[:200]
return False, _localized_ssh_error(e)
finally:
if conn is not None:
try:
@@ -240,7 +244,10 @@ class AsyncSSHPool:
conn = await asyncssh.connect(**connect_kwargs)
return conn
except asyncssh.Error as e:
raise ConnectionError(f"SSH connection failed to {server.domain}:{server.port}: {e}") from e
msg = _localized_ssh_error(e)
raise ConnectionError(
f"SSH 连接失败 {server.domain}:{server.port or 22}{msg}"
) from e
async def _evict_one(self) -> bool:
"""Evict the oldest idle connection from the pool.
@@ -459,7 +466,7 @@ async def exec_ssh_command(server: Server, command: str, timeout: int = 30) -> d
return {
"status": "timeout",
"stdout": "",
"stderr": f"Command timed out after {timeout}s",
"stderr": f"命令执行超时({timeout} 秒)",
"exit_code": -1,
}
except ConnectionError as e: