Amnezia-style SSH server connect with verify, ping and server_info
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy import func, select
|
||||
@@ -16,6 +18,7 @@ from app.services.crypto import (
|
||||
render_server_config,
|
||||
server_address,
|
||||
)
|
||||
from app.services.ssh import probe_tcp, verify_ssh
|
||||
|
||||
|
||||
async def ensure_default_servers(session: AsyncSession) -> None:
|
||||
@@ -137,6 +140,14 @@ async def create_server(
|
||||
if not username:
|
||||
raise ValueError("Укажите SSH логин")
|
||||
|
||||
# Like Amnezia-Web-Panel: verify SSH before saving
|
||||
try:
|
||||
server_info = await asyncio.to_thread(
|
||||
verify_ssh, host, ssh_port or 22, username, password, private_key
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
raise ValueError(f"Подключение не удалось: {exc}") from exc
|
||||
|
||||
defaults = _defaults_for_protocol(protocol)
|
||||
keys = generate_keypair()
|
||||
# Unique interface per server id is assigned after insert; use temp then update
|
||||
@@ -150,6 +161,7 @@ async def create_server(
|
||||
raise ValueError("Слишком много серверов для авто-подсети")
|
||||
subnet = f"10.{octet}.0.0/24"
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
server = VpnServer(
|
||||
name=name.strip() or host,
|
||||
protocol=protocol,
|
||||
@@ -165,6 +177,9 @@ async def create_server(
|
||||
ssh_username=username,
|
||||
ssh_password=password,
|
||||
ssh_private_key=private_key,
|
||||
ssh_server_info=server_info,
|
||||
ssh_reachable=True,
|
||||
ssh_last_checked=now,
|
||||
jc=defaults["jc"],
|
||||
jmin=defaults["jmin"],
|
||||
jmax=defaults["jmax"],
|
||||
@@ -183,6 +198,52 @@ async def create_server(
|
||||
return server
|
||||
|
||||
|
||||
async def check_server_ssh(session: AsyncSession, server_id: int) -> VpnServer:
|
||||
server = await session.get(VpnServer, server_id)
|
||||
if not server:
|
||||
raise ValueError("Server not found")
|
||||
if not server.ssh_host or not server.ssh_username:
|
||||
raise ValueError("SSH данные не заданы")
|
||||
if not server.ssh_password and not server.ssh_private_key:
|
||||
raise ValueError("Нет пароля и ключа SSH")
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
try:
|
||||
info = await asyncio.to_thread(
|
||||
verify_ssh,
|
||||
server.ssh_host,
|
||||
server.ssh_port or 22,
|
||||
server.ssh_username,
|
||||
server.ssh_password,
|
||||
server.ssh_private_key,
|
||||
)
|
||||
server.ssh_server_info = info
|
||||
server.ssh_reachable = True
|
||||
server.ssh_last_checked = now
|
||||
except Exception as exc: # noqa: BLE001
|
||||
server.ssh_reachable = False
|
||||
server.ssh_last_checked = now
|
||||
await session.commit()
|
||||
raise ValueError(f"Подключение не удалось: {exc}") from exc
|
||||
|
||||
await session.commit()
|
||||
await session.refresh(server)
|
||||
return server
|
||||
|
||||
|
||||
async def probe_servers(servers: list[VpnServer]) -> dict[int, dict]:
|
||||
"""TCP ping SSH ports for UI online indicators."""
|
||||
|
||||
async def one(s: VpnServer) -> tuple[int, dict]:
|
||||
if not s.ssh_host:
|
||||
return s.id, {"online": False, "latency_ms": None}
|
||||
result = await asyncio.to_thread(probe_tcp, s.ssh_host, s.ssh_port or 22)
|
||||
return s.id, {"online": result.online, "latency_ms": result.latency_ms}
|
||||
|
||||
pairs = await asyncio.gather(*(one(s) for s in servers))
|
||||
return dict(pairs)
|
||||
|
||||
|
||||
async def update_server_settings(
|
||||
session: AsyncSession,
|
||||
server_id: int,
|
||||
|
||||
Reference in New Issue
Block a user