feat(servers): 凭据轮询登录与连接失败列表
- 密码/SSH密钥预设增加用户名;快速添加(IP)轮询全部预设尝试 SSH 登录 - 成功入 servers 列表;失败写入 pending_servers 并支持重试/删除 - 新增 credential_poller、try_ssh_login 与 add-by-ip/pending API
This commit is contained in:
@@ -25,6 +25,51 @@ from server.infrastructure.database.crypto import decrypt_value
|
||||
logger = logging.getLogger("nexus.asyncssh_pool")
|
||||
|
||||
|
||||
async def try_ssh_login(
|
||||
host: str,
|
||||
port: int,
|
||||
username: str,
|
||||
*,
|
||||
password: str | None = None,
|
||||
private_key: str | None = None,
|
||||
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, "未提供密码或私钥"
|
||||
|
||||
connect_kwargs: dict = {
|
||||
"host": host,
|
||||
"port": port or 22,
|
||||
"username": username or "root",
|
||||
"connect_timeout": timeout,
|
||||
}
|
||||
if settings.SSH_STRICT_HOST_CHECKING.lower() in ("false", "0", "no"):
|
||||
connect_kwargs["known_hosts"] = None
|
||||
|
||||
if password:
|
||||
connect_kwargs["password"] = password
|
||||
elif private_key:
|
||||
connect_kwargs["client_keys"] = [asyncssh.import_private_key(private_key)]
|
||||
|
||||
conn: asyncssh.SSHClientConnection | None = None
|
||||
try:
|
||||
conn = await asyncssh.connect(**connect_kwargs)
|
||||
return True, ""
|
||||
except asyncssh.Error as e:
|
||||
return False, str(e)[:200]
|
||||
except Exception as e:
|
||||
return False, str(e)[:200]
|
||||
finally:
|
||||
if conn is not None:
|
||||
try:
|
||||
conn.close()
|
||||
except Exception: # noqa: S110 — best-effort cleanup
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class PooledConnection:
|
||||
"""A tracked asyncssh connection with reference counting"""
|
||||
|
||||
Reference in New Issue
Block a user