Skip to content

Commit

Permalink
Merge pull request #99 from Thymis-io/feat/terminal
Browse files Browse the repository at this point in the history
Add Terminal
  • Loading branch information
MSchmoecker authored Oct 3, 2024
2 parents 7aa42d9 + 2bdbda2 commit 2036ee4
Show file tree
Hide file tree
Showing 9 changed files with 619 additions and 4 deletions.
1 change: 1 addition & 0 deletions controller/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ let
preferWheels = true;
overrides = poetry2nix.overrides.withDefaults (self: super: {
thymis-enterprise = null;
bcrypt = python312.pkgs.bcrypt;
});
groups = [ ];
checkGroups = [ "test" ];
Expand Down
228 changes: 227 additions & 1 deletion controller/poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions controller/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pydantic-settings = "^2.4.0"
alembic = "^1.13.2"
sqlalchemy = "^2.0.32"
python-multipart = "^0.0.9"
paramiko = "^3.5.0"

[tool.poetry.group.enterprise]
optional = true
Expand Down
52 changes: 50 additions & 2 deletions controller/thymis_controller/routers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from fastapi import APIRouter, Depends, Request, WebSocket
from fastapi.responses import FileResponse, RedirectResponse
from paramiko import SSHClient
from thymis_controller import dependencies, models, modules, project
from thymis_controller.dependencies import (
SessionAD,
Expand All @@ -10,7 +11,12 @@
)
from thymis_controller.models.state import State
from thymis_controller.routers import task
from thymis_controller.tcp_to_ws import tcp_to_websocket, websocket_to_tcp
from thymis_controller.tcp_to_ws import (
channel_to_websocket,
tcp_to_websocket,
websocket_to_channel,
websocket_to_tcp,
)

router = APIRouter(
dependencies=[Depends(require_valid_user_session)],
Expand Down Expand Up @@ -119,7 +125,7 @@ async def update(


@router.websocket("/vnc/{identifier}")
async def websocket_endpoint(
async def vnc_websocket(
identifier: str,
websocket: WebSocket,
state: State = Depends(dependencies.get_state),
Expand Down Expand Up @@ -148,6 +154,48 @@ async def websocket_endpoint(
ws_to_tcp_task.cancel()


@router.websocket("/terminal/{identifier}")
async def terminal_websocket(
identifier: str,
websocket: WebSocket,
state: State = Depends(dependencies.get_state),
):
device = next(device for device in state.devices if device.identifier == identifier)

if device is None:
await websocket.close()
return

await websocket.accept()

tcp_ip = device.targetHost
tcp_port = 22

client = SSHClient()
client.load_system_host_keys()

try:
client.connect(tcp_ip, tcp_port, "root")
channel = client.invoke_shell()
except Exception as e:
await websocket.send_bytes(str(e).encode())
await websocket.close()
client.close()
return

channel_to_ws_task = asyncio.create_task(channel_to_websocket(channel, websocket))
ws_to_channel_task = asyncio.create_task(websocket_to_channel(channel, websocket))

try:
await asyncio.gather(channel_to_ws_task, ws_to_channel_task)
except Exception as e:
print(f"Error: {e}")
finally:
channel_to_ws_task.cancel()
ws_to_channel_task.cancel()
client.close()


@router.get("/testSession")
def test_session(session: SessionAD):
session
Expand Down
44 changes: 44 additions & 0 deletions controller/thymis_controller/tcp_to_ws.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import asyncio
import json

from fastapi import WebSocket, WebSocketDisconnect
from paramiko import Channel


async def tcp_to_websocket(tcp_reader, websocket):
Expand Down Expand Up @@ -32,3 +34,45 @@ async def websocket_to_tcp(tcp_writer, websocket):
print(f"Error in websocket_to_tcp: {e}")
finally:
tcp_writer.close()


async def channel_to_websocket(channel: Channel, websocket: WebSocket):
try:
while True:
if channel.closed:
break
if not channel.recv_ready():
await asyncio.sleep(0.01)
continue
data = channel.recv(1024)
if not data:
break
await websocket.send_bytes(data)
except asyncio.CancelledError:
pass
except Exception as e:
print(f"Error in channel_to_websocket: {e}")
finally:
await websocket.close()


async def websocket_to_channel(channel: Channel, websocket: WebSocket):
try:
while True:
data = await websocket.receive_text()
if not data:
break
if data.startswith("\x04"):
data = data[1:]
data = json.loads(data)
channel.resize_pty(width=data["cols"], height=data["rows"])
else:
channel.send(data.encode())
except WebSocketDisconnect:
pass
except asyncio.CancelledError:
pass
except Exception as e:
print(f"Error in websocket_to_channel: {e}")
finally:
channel.close()
2 changes: 1 addition & 1 deletion frontend/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ buildNpmPackage {
pname = "thymis-frontend";
version = "0.0.1";
src = ./.;
npmDepsHash = "sha256-Fw+j3dqqH5zWGuZScJA+8GmCAGqsgbyGK3hU/XNY8Kc=";
npmDepsHash = "sha256-xu0kr0YS/7iYJKrHefKPrTAHBK36k4VGgvqCnSV36cE=";
postInstall = ''
mkdir -p $packageOut/build
cp -r ./build/* $packageOut/build
Expand Down
Loading

0 comments on commit 2036ee4

Please sign in to comment.