5df38bad2d
Closes TG-4
93 lines
2.6 KiB
Python
93 lines
2.6 KiB
Python
import os
|
|
import json
|
|
import struct
|
|
import signal
|
|
import select
|
|
import threading
|
|
|
|
|
|
def _pty_set_winsize(fd, rows, cols):
|
|
import fcntl
|
|
import termios
|
|
|
|
winsz = struct.pack("HHHH", int(rows), int(cols), 0, 0)
|
|
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsz)
|
|
|
|
|
|
def terminal_session(ws):
|
|
"""PTY + bash, обмен через WebSocket (как ttyd, но на том же порту что и Flask)."""
|
|
import pty
|
|
|
|
pid, master_fd = pty.fork()
|
|
if pid == 0:
|
|
os.environ.setdefault("TERM", "xterm-256color")
|
|
os.environ.setdefault("SHELL", "/bin/bash")
|
|
try:
|
|
os.execvp("/bin/bash", ["/bin/bash"])
|
|
except OSError:
|
|
os.execvp("/bin/sh", ["/bin/sh"])
|
|
os._exit(127)
|
|
|
|
relay_done = threading.Event()
|
|
ws_lock = threading.Lock()
|
|
|
|
def relay_out():
|
|
try:
|
|
while not relay_done.is_set():
|
|
r, _, _ = select.select([master_fd], [], [], 0.25)
|
|
if master_fd not in r:
|
|
continue
|
|
try:
|
|
chunk = os.read(master_fd, 65536)
|
|
except OSError:
|
|
break
|
|
if not chunk:
|
|
break
|
|
try:
|
|
with ws_lock:
|
|
ws.send(chunk)
|
|
except Exception:
|
|
break
|
|
finally:
|
|
relay_done.set()
|
|
|
|
t = threading.Thread(target=relay_out, daemon=True)
|
|
t.start()
|
|
|
|
try:
|
|
while True:
|
|
msg = ws.receive()
|
|
if msg is None:
|
|
break
|
|
if isinstance(msg, str) and msg.startswith("{"):
|
|
try:
|
|
j = json.loads(msg)
|
|
if j.get("type") == "resize":
|
|
r, c = int(j.get("rows") or 24), int(j.get("cols") or 80)
|
|
if r > 0 and c > 0:
|
|
_pty_set_winsize(master_fd, r, c)
|
|
continue
|
|
except (json.JSONDecodeError, TypeError, ValueError, KeyError):
|
|
pass
|
|
if isinstance(msg, str):
|
|
msg = msg.encode("utf-8", errors="replace")
|
|
try:
|
|
os.write(master_fd, msg)
|
|
except OSError:
|
|
break
|
|
finally:
|
|
relay_done.set()
|
|
try:
|
|
os.close(master_fd)
|
|
except OSError:
|
|
pass
|
|
try:
|
|
os.kill(pid, signal.SIGTERM)
|
|
except ProcessLookupError:
|
|
pass
|
|
try:
|
|
os.waitpid(pid, 0)
|
|
except ChildProcessError:
|
|
pass
|
|
t.join(timeout=1.5)
|