feat(servers): 凭据轮询登录与连接失败列表

- 密码/SSH密钥预设增加用户名;快速添加(IP)轮询全部预设尝试 SSH 登录
- 成功入 servers 列表;失败写入 pending_servers 并支持重试/删除
- 新增 credential_poller、try_ssh_login 与 add-by-ip/pending API
This commit is contained in:
Nexus Deploy
2026-06-06 20:08:48 +08:00
parent 5a56e5a8cb
commit 4ba45abf38
51 changed files with 1372 additions and 21 deletions
@@ -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"""