From 1edd7cd555d460b198e8e52b68abcdbaa73100f5 Mon Sep 17 00:00:00 2001 From: host Date: Fri, 24 Jul 2026 23:06:48 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/console.py | 54 +++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/backend/app/routers/console.py b/backend/app/routers/console.py index 297e3fc..7af4887 100644 --- a/backend/app/routers/console.py +++ b/backend/app/routers/console.py @@ -1,10 +1,25 @@ +"""WebSocket-прокси для VNC-консоли. + +Берёт у Proxmox одноразовый тикет через `vncproxy` (это работает с API-токеном), +затем проксирует бинарный поток от браузера к WebSocket-эндпоинту Proxmox. + +В зависимости от версии Proxmox WebSocket-эндпоинт может требовать: + 1. Заголовок `Authorization: PVEAPIToken=...` (новые версии PVE ≥ 7.x) + 2. Cookie `PVEAuthCookie` (старые версии или строгие настройки) + +В консоль пишется подробный лог, чтобы было видно, на каком этапе отваливается. +""" + import asyncio +import logging import websockets -from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Query +from fastapi import APIRouter, Query, WebSocket, WebSocketDisconnect from ..config import settings +logger = logging.getLogger(__name__) + router = APIRouter(prefix="/console", tags=["console"]) @@ -18,13 +33,12 @@ async def console_ws( ticket: str = Query(...), ): """ - Проксирует бинарный VNC-поток между браузером клиента и websocket-эндпоинтом - Proxmox (nodes/{node}/(qemu|lxc)/{vmid}/vncwebsocket). Тикет и порт берутся - из ответа /instances/{id}/console (см. instances.py -> pve.get_vnc_ticket). + Проксирует бинарный VNC-поток между браузером клиента и WebSocket-эндпоинтом + Proxmox (nodes/{node}/(qemu|lxc)/{vmid}/vncwebsocket). - Примечание: в зависимости от версии Proxmox и способа аутентификации - (API-токен vs cookie-тикет) может понадобиться донастройка заголовков — - см. README, раздел "Консоль VNC". + Если Proxmox отказывает в аутентификации — см. README, раздел «Консоль VNC». + Возможные варианты: обновить PVE, либо задать PVE_USERNAME/PVE_PASSWORD в .env + для cookie-авторизации через POST /access/ticket. """ await websocket.accept() @@ -39,6 +53,11 @@ async def console_ws( "Authorization": f"PVEAPIToken={settings.pve_token_name}={settings.pve_token_value}" } + logger.info( + "console_ws: подключение к %s (node=%s, vmid=%s, guest=%s, port=%s)", + pve_host, node, vmid, guest_type, port, + ) + try: async with websockets.connect( upstream_url, @@ -46,6 +65,7 @@ async def console_ws( subprotocols=["binary"], ssl=None if settings.pve_verify_ssl else False, ) as upstream: + logger.info("console_ws: upstream connected") async def client_to_upstream(): try: @@ -53,7 +73,9 @@ async def console_ws( data = await websocket.receive_bytes() await upstream.send(data) except WebSocketDisconnect: - pass + logger.info("console_ws: клиент отключился") + except Exception as exc: + logger.warning("console_ws: client_to_upstream error: %s", exc) async def upstream_to_client(): try: @@ -61,9 +83,21 @@ async def console_ws( if isinstance(message, str): message = message.encode() await websocket.send_bytes(message) - except websockets.ConnectionClosed: - pass + except websockets.ConnectionClosed as exc: + logger.info("console_ws: upstream closed: %s", exc) + except Exception as exc: + logger.warning("console_ws: upstream_to_client error: %s", exc) await asyncio.gather(client_to_upstream(), upstream_to_client()) + except websockets.exceptions.InvalidStatus as exc: + # Proxmox отверг соединение — скорее всего из-за аутентификации. + logger.error( + "console_ws: Proxmox отклонил подключение (status=%s). " + "Возможно, требуется cookie PVEAuthCookie вместо API-токена — " + "задайте PVE_USERNAME и PVE_PASSWORD в backend/.env", + exc.response.status_code if exc.response else "?", + ) + await websocket.close(code=1011, reason=f"Proxmox auth failed: {exc.response.status_code if exc.response else exc}") except Exception as exc: # noqa: BLE001 + logger.exception("console_ws: ошибка подключения к Proxmox") await websocket.close(code=1011, reason=str(exc)[:120])