Обновление файла
This commit is contained in:
@@ -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 asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
import websockets
|
import websockets
|
||||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Query
|
from fastapi import APIRouter, Query, WebSocket, WebSocketDisconnect
|
||||||
|
|
||||||
from ..config import settings
|
from ..config import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter(prefix="/console", tags=["console"])
|
router = APIRouter(prefix="/console", tags=["console"])
|
||||||
|
|
||||||
|
|
||||||
@@ -18,13 +33,12 @@ async def console_ws(
|
|||||||
ticket: str = Query(...),
|
ticket: str = Query(...),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Проксирует бинарный VNC-поток между браузером клиента и websocket-эндпоинтом
|
Проксирует бинарный VNC-поток между браузером клиента и WebSocket-эндпоинтом
|
||||||
Proxmox (nodes/{node}/(qemu|lxc)/{vmid}/vncwebsocket). Тикет и порт берутся
|
Proxmox (nodes/{node}/(qemu|lxc)/{vmid}/vncwebsocket).
|
||||||
из ответа /instances/{id}/console (см. instances.py -> pve.get_vnc_ticket).
|
|
||||||
|
|
||||||
Примечание: в зависимости от версии Proxmox и способа аутентификации
|
Если Proxmox отказывает в аутентификации — см. README, раздел «Консоль VNC».
|
||||||
(API-токен vs cookie-тикет) может понадобиться донастройка заголовков —
|
Возможные варианты: обновить PVE, либо задать PVE_USERNAME/PVE_PASSWORD в .env
|
||||||
см. README, раздел "Консоль VNC".
|
для cookie-авторизации через POST /access/ticket.
|
||||||
"""
|
"""
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
|
|
||||||
@@ -39,6 +53,11 @@ async def console_ws(
|
|||||||
"Authorization": f"PVEAPIToken={settings.pve_token_name}={settings.pve_token_value}"
|
"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:
|
try:
|
||||||
async with websockets.connect(
|
async with websockets.connect(
|
||||||
upstream_url,
|
upstream_url,
|
||||||
@@ -46,6 +65,7 @@ async def console_ws(
|
|||||||
subprotocols=["binary"],
|
subprotocols=["binary"],
|
||||||
ssl=None if settings.pve_verify_ssl else False,
|
ssl=None if settings.pve_verify_ssl else False,
|
||||||
) as upstream:
|
) as upstream:
|
||||||
|
logger.info("console_ws: upstream connected")
|
||||||
|
|
||||||
async def client_to_upstream():
|
async def client_to_upstream():
|
||||||
try:
|
try:
|
||||||
@@ -53,7 +73,9 @@ async def console_ws(
|
|||||||
data = await websocket.receive_bytes()
|
data = await websocket.receive_bytes()
|
||||||
await upstream.send(data)
|
await upstream.send(data)
|
||||||
except WebSocketDisconnect:
|
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():
|
async def upstream_to_client():
|
||||||
try:
|
try:
|
||||||
@@ -61,9 +83,21 @@ async def console_ws(
|
|||||||
if isinstance(message, str):
|
if isinstance(message, str):
|
||||||
message = message.encode()
|
message = message.encode()
|
||||||
await websocket.send_bytes(message)
|
await websocket.send_bytes(message)
|
||||||
except websockets.ConnectionClosed:
|
except websockets.ConnectionClosed as exc:
|
||||||
pass
|
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())
|
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
|
except Exception as exc: # noqa: BLE001
|
||||||
|
logger.exception("console_ws: ошибка подключения к Proxmox")
|
||||||
await websocket.close(code=1011, reason=str(exc)[:120])
|
await websocket.close(code=1011, reason=str(exc)[:120])
|
||||||
|
|||||||
Reference in New Issue
Block a user