69 lines
2.2 KiB
Python
69 lines
2.2 KiB
Python
|
|
"""YakPanel - Web Terminal API (WebSocket)"""
|
||
|
|
import asyncio
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/terminal", tags=["terminal"])
|
||
|
|
|
||
|
|
|
||
|
|
@router.websocket("/ws")
|
||
|
|
async def terminal_websocket(websocket: WebSocket):
|
||
|
|
"""WebSocket terminal - spawns shell and streams I/O"""
|
||
|
|
await websocket.accept()
|
||
|
|
|
||
|
|
token = websocket.query_params.get("token")
|
||
|
|
if token:
|
||
|
|
from app.core.security import decode_token
|
||
|
|
if not decode_token(token):
|
||
|
|
await websocket.close(code=4001)
|
||
|
|
return
|
||
|
|
|
||
|
|
if sys.platform == "win32":
|
||
|
|
proc = await asyncio.create_subprocess_shell(
|
||
|
|
"cmd.exe",
|
||
|
|
stdin=asyncio.subprocess.PIPE,
|
||
|
|
stdout=asyncio.subprocess.PIPE,
|
||
|
|
stderr=asyncio.subprocess.STDOUT,
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
proc = await asyncio.create_subprocess_shell(
|
||
|
|
"/bin/bash",
|
||
|
|
stdin=asyncio.subprocess.PIPE,
|
||
|
|
stdout=asyncio.subprocess.PIPE,
|
||
|
|
stderr=asyncio.subprocess.STDOUT,
|
||
|
|
env={**os.environ, "TERM": "xterm-256color"},
|
||
|
|
)
|
||
|
|
|
||
|
|
async def read_stdout():
|
||
|
|
try:
|
||
|
|
while proc.returncode is None and proc.stdout:
|
||
|
|
data = await proc.stdout.read(4096)
|
||
|
|
if data:
|
||
|
|
await websocket.send_text(data.decode("utf-8", errors="replace"))
|
||
|
|
except (WebSocketDisconnect, ConnectionResetError):
|
||
|
|
pass
|
||
|
|
finally:
|
||
|
|
try:
|
||
|
|
proc.kill()
|
||
|
|
except ProcessLookupError:
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def read_websocket():
|
||
|
|
try:
|
||
|
|
while True:
|
||
|
|
msg = await websocket.receive()
|
||
|
|
data = msg.get("text") or (msg.get("bytes") or b"").decode("utf-8", errors="replace")
|
||
|
|
if data and proc.stdin and not proc.stdin.is_closing():
|
||
|
|
proc.stdin.write(data.encode("utf-8"))
|
||
|
|
await proc.stdin.drain()
|
||
|
|
except (WebSocketDisconnect, ConnectionResetError):
|
||
|
|
pass
|
||
|
|
finally:
|
||
|
|
try:
|
||
|
|
proc.kill()
|
||
|
|
except ProcessLookupError:
|
||
|
|
pass
|
||
|
|
|
||
|
|
await asyncio.gather(read_stdout(), read_websocket())
|