- Added support for multiple Proxmox hosts (pve7.0bin.in:443, Healthport PVE:8006) - Enhanced VM management APIs to accept host parameter - Fixed WebSocket URL generation bug (dynamic port handling) - Added comprehensive SSL certificate trust help system - Implemented host selection dropdown in UI - Added VNC connection failure detection and automatic SSL help redirection - Updated session management to store host_key information - Enhanced error handling for different Proxmox configurations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
168 lines
6.2 KiB
Python
168 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
VNC WebSocket 프록시
|
|
브라우저와 Proxmox VNC 서버 간 WebSocket 연결을 중계하며
|
|
PVE 인증을 자동으로 처리합니다.
|
|
"""
|
|
|
|
import asyncio
|
|
import websockets
|
|
import ssl
|
|
import logging
|
|
from utils.proxmox_client import ProxmoxClient
|
|
|
|
# 로깅 설정
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class VNCWebSocketProxy:
|
|
def __init__(self, proxmox_host, proxmox_username, proxmox_password):
|
|
self.proxmox_host = proxmox_host
|
|
self.proxmox_username = proxmox_username
|
|
self.proxmox_password = proxmox_password
|
|
self.proxmox_client = None
|
|
|
|
async def create_proxmox_client(self):
|
|
"""Proxmox 클라이언트 생성 및 로그인"""
|
|
if not self.proxmox_client:
|
|
self.proxmox_client = ProxmoxClient(
|
|
self.proxmox_host,
|
|
self.proxmox_username,
|
|
self.proxmox_password
|
|
)
|
|
|
|
if not self.proxmox_client.login():
|
|
logger.error("Proxmox 로그인 실패")
|
|
return False
|
|
|
|
logger.info("Proxmox 로그인 성공")
|
|
return True
|
|
|
|
async def get_vnc_connection_info(self, node, vm_id):
|
|
"""VNC 연결 정보 생성"""
|
|
if not await self.create_proxmox_client():
|
|
return None
|
|
|
|
# VM 상태 확인
|
|
vm_status = self.proxmox_client.get_vm_status(node, vm_id)
|
|
if vm_status.get('status') != 'running':
|
|
logger.error(f"VM {vm_id}가 실행 중이 아님: {vm_status.get('status')}")
|
|
return None
|
|
|
|
# VNC 티켓 생성
|
|
vnc_data = self.proxmox_client.get_vnc_ticket(node, vm_id)
|
|
if not vnc_data:
|
|
logger.error(f"VM {vm_id} VNC 티켓 생성 실패")
|
|
return None
|
|
|
|
# WebSocket 연결 정보 준비
|
|
connection_info = {
|
|
'websocket_url': vnc_data['websocket_url'],
|
|
'password': vnc_data['password'],
|
|
'auth_headers': {
|
|
'Cookie': f'PVEAuthCookie={self.proxmox_client.ticket}'
|
|
}
|
|
}
|
|
|
|
logger.info(f"VM {vm_id} VNC 연결 정보 생성 완료")
|
|
return connection_info
|
|
|
|
async def create_proxmox_websocket(self, connection_info):
|
|
"""Proxmox VNC WebSocket 연결 생성"""
|
|
ssl_context = ssl.create_default_context()
|
|
ssl_context.check_hostname = False
|
|
ssl_context.verify_mode = ssl.CERT_NONE
|
|
|
|
try:
|
|
websocket = await websockets.connect(
|
|
connection_info['websocket_url'],
|
|
ssl=ssl_context,
|
|
additional_headers=connection_info['auth_headers']
|
|
)
|
|
logger.info("Proxmox VNC WebSocket 연결 성공")
|
|
return websocket
|
|
|
|
except Exception as e:
|
|
logger.error(f"Proxmox VNC WebSocket 연결 실패: {e}")
|
|
return None
|
|
|
|
async def proxy_data(self, browser_ws, proxmox_ws):
|
|
"""브라우저와 Proxmox 간 WebSocket 데이터 양방향 중계"""
|
|
async def forward_browser_to_proxmox():
|
|
"""브라우저 → Proxmox 데이터 전달"""
|
|
try:
|
|
async for message in browser_ws:
|
|
await proxmox_ws.send(message)
|
|
logger.debug(f"브라우저 → Proxmox: {len(message)} bytes")
|
|
except websockets.exceptions.ConnectionClosed:
|
|
logger.info("브라우저 연결 종료")
|
|
except Exception as e:
|
|
logger.error(f"브라우저 → Proxmox 전달 오류: {e}")
|
|
|
|
async def forward_proxmox_to_browser():
|
|
"""Proxmox → 브라우저 데이터 전달"""
|
|
try:
|
|
async for message in proxmox_ws:
|
|
await browser_ws.send(message)
|
|
logger.debug(f"Proxmox → 브라우저: {len(message)} bytes")
|
|
except websockets.exceptions.ConnectionClosed:
|
|
logger.info("Proxmox 연결 종료")
|
|
except Exception as e:
|
|
logger.error(f"Proxmox → 브라우저 전달 오류: {e}")
|
|
|
|
# 양방향 데이터 전달을 병렬로 실행
|
|
await asyncio.gather(
|
|
forward_browser_to_proxmox(),
|
|
forward_proxmox_to_browser(),
|
|
return_exceptions=True
|
|
)
|
|
|
|
async def handle_vnc_proxy(self, browser_websocket, node, vm_id):
|
|
"""VNC 프록시 메인 핸들러"""
|
|
logger.info(f"VNC 프록시 시작: VM {vm_id}")
|
|
|
|
try:
|
|
# 1. Proxmox VNC 연결 정보 생성
|
|
connection_info = await self.get_vnc_connection_info(node, vm_id)
|
|
if not connection_info:
|
|
await browser_websocket.send("ERROR: VNC 연결 정보 생성 실패")
|
|
return False
|
|
|
|
# 2. Proxmox VNC WebSocket 연결
|
|
proxmox_websocket = await self.create_proxmox_websocket(connection_info)
|
|
if not proxmox_websocket:
|
|
await browser_websocket.send("ERROR: Proxmox VNC 연결 실패")
|
|
return False
|
|
|
|
# 3. 연결 성공 알림
|
|
logger.info(f"VM {vm_id} VNC 프록시 연결 완료")
|
|
|
|
# 4. 데이터 중계 시작
|
|
await self.proxy_data(browser_websocket, proxmox_websocket)
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"VNC 프록시 처리 오류: {e}")
|
|
try:
|
|
await browser_websocket.send(f"ERROR: {str(e)}")
|
|
except:
|
|
pass
|
|
return False
|
|
|
|
finally:
|
|
logger.info(f"VNC 프록시 종료: VM {vm_id}")
|
|
|
|
|
|
# 전역 VNC 프록시 인스턴스 (설정값은 app.py에서 주입)
|
|
vnc_proxy_instance = None
|
|
|
|
def init_vnc_proxy(proxmox_host, proxmox_username, proxmox_password):
|
|
"""VNC 프록시 인스턴스 초기화"""
|
|
global vnc_proxy_instance
|
|
vnc_proxy_instance = VNCWebSocketProxy(proxmox_host, proxmox_username, proxmox_password)
|
|
logger.info("VNC WebSocket 프록시 초기화 완료")
|
|
|
|
def get_vnc_proxy():
|
|
"""VNC 프록시 인스턴스 반환"""
|
|
global vnc_proxy_instance
|
|
return vnc_proxy_instance |