Initial commit: Debian Manager — управление Debian-серверами
- SSH/SFTP управление без агентов - Пакеты (apt), Docker, файлы, systemd, ufw, cron - Плейбуки, групповые операции, планировщик - Бэкапы, отчёты, Portainer, Proxmox VE, Gitea Deploy - Терминал, Telegram-уведомления, тёмная/светлая тема
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""Интерактивный терминал: PTY-сессия по SSH с потоковым вводом/выводом."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Callable, Optional
|
||||
|
||||
import asyncssh
|
||||
|
||||
from app.db.models import Server
|
||||
|
||||
|
||||
class TerminalSession:
|
||||
"""PTY-терминал к серверу. output_cb(str) вызывается для каждого куска вывода."""
|
||||
|
||||
def __init__(self, server: Server, output_cb: Callable[[str], None],
|
||||
cols: int = 120, rows: int = 30):
|
||||
self.server = server
|
||||
self.output_cb = output_cb
|
||||
self.cols = cols
|
||||
self.rows = rows
|
||||
self._conn: Optional[asyncssh.SSHClientConnection] = None
|
||||
self._proc: Optional[asyncssh.SSHClientProcess] = None
|
||||
self._closed = False
|
||||
|
||||
async def open(self) -> None:
|
||||
kwargs: dict = dict(
|
||||
host=self.server.host, port=self.server.port,
|
||||
username=self.server.username, known_hosts=None, connect_timeout=15,
|
||||
)
|
||||
if self.server.auth_type == "key" and self.server.key_path:
|
||||
kwargs["client_keys"] = [self.server.key_path]
|
||||
if self.server.password:
|
||||
kwargs["passphrase"] = self.server.password
|
||||
else:
|
||||
kwargs["password"] = self.server.password
|
||||
self._conn = await asyncssh.connect(**kwargs)
|
||||
self._proc = await self._conn.create_process(
|
||||
term_type="xterm-256color",
|
||||
term_size=(self.cols, self.rows),
|
||||
)
|
||||
asyncio.ensure_future(self._reader())
|
||||
|
||||
async def _reader(self) -> None:
|
||||
try:
|
||||
while not self._closed and self._proc:
|
||||
data = await self._proc.stdout.read(4096)
|
||||
if not data:
|
||||
break
|
||||
self.output_cb(data)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def write(self, data: str) -> None:
|
||||
if self._proc:
|
||||
self._proc.stdin.write(data)
|
||||
|
||||
def resize(self, cols: int, rows: int) -> None:
|
||||
if self._proc:
|
||||
self._proc.change_terminal_size(cols, rows)
|
||||
|
||||
def close(self) -> None:
|
||||
self._closed = True
|
||||
try:
|
||||
if self._proc:
|
||||
self._proc.terminate()
|
||||
if self._conn:
|
||||
self._conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
Reference in New Issue
Block a user