File size: 6,044 Bytes
349e337 e4c2107 744c47a e4c2107 744c47a e4c2107 744c47a c26cc29 744c47a 349e337 c26cc29 349e337 744c47a c26cc29 744c47a 349e337 744c47a 50bb712 c26cc29 50bb712 744c47a 50bb712 744c47a c26cc29 50bb712 744c47a 50bb712 744c47a 50bb712 744c47a 50bb712 744c47a c26cc29 744c47a 50bb712 744c47a c26cc29 50bb712 744c47a 50bb712 c26cc29 50bb712 744c47a 50bb712 c26cc29 744c47a 50bb712 c26cc29 744c47a c26cc29 744c47a c26cc29 744c47a c26cc29 744c47a c26cc29 744c47a c26cc29 744c47a c26cc29 744c47a 50bb712 744c47a 50bb712 744c47a c26cc29 744c47a e4c2107 c26cc29 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | FROM python:3.10-slim
WORKDIR /app
RUN apt-get update && apt-get install -y git wget curl vim build-essential bash
RUN pip install --no-cache-dir fastapi uvicorn
RUN python3 -c "
content = '''
from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse
import asyncio, os, pty, fcntl, termios, struct, json, signal
app = FastAPI()
@app.get(\"/\")
async def root():
return HTMLResponse(open(\"index.html\").read())
@app.websocket(\"/ws\")
async def ws_endpoint(websocket: WebSocket):
await websocket.accept()
master_fd, slave_fd = pty.openpty()
env = {**os.environ, \"TERM\": \"xterm-256color\", \"HOME\": \"/root\", \"USER\": \"root\", \"SHELL\": \"/bin/bash\"}
proc = await asyncio.create_subprocess_exec(
\"/bin/bash\", \"--login\",
stdin=slave_fd, stdout=slave_fd, stderr=slave_fd,
close_fds=True, env=env
)
os.close(slave_fd)
loop = asyncio.get_event_loop()
queue = asyncio.Queue()
def on_output():
try:
data = os.read(master_fd, 4096)
loop.call_soon_threadsafe(queue.put_nowait, data)
except OSError:
loop.call_soon_threadsafe(queue.put_nowait, None)
loop.add_reader(master_fd, on_output)
async def sender():
while True:
data = await queue.get()
if data is None:
break
try:
await websocket.send_bytes(data)
except:
break
sender_task = asyncio.ensure_future(sender())
try:
while True:
msg = await websocket.receive_text()
try:
obj = json.loads(msg)
if obj.get(\"type\") == \"resize\":
fcntl.ioctl(master_fd, termios.TIOCSWINSZ,
struct.pack(\"HHHH\", obj[\"rows\"], obj[\"cols\"], 0, 0))
continue
except:
pass
os.write(master_fd, msg.encode())
except:
pass
finally:
loop.remove_reader(master_fd)
sender_task.cancel()
try: os.close(master_fd)
except: pass
try: proc.terminate()
except: pass
'''
with open(\"app.py\", \"w\") as f:
f.write(content)
"
RUN python3 -c "
content = open('index.html.tpl', 'r').read() if False else '''<!DOCTYPE html>
<html>
<head>
<meta charset=\"UTF-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">
<title>Terminal</title>
<script src=\"https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js\"></script>
<script src=\"https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js\"></script>
<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css\">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#1a1a2e;display:flex;flex-direction:column;height:100dvh;overflow:hidden}
#term{flex:1;overflow:hidden;padding:4px}
#toolbar{background:#16213e;padding:6px 8px;display:flex;flex-wrap:wrap;gap:5px;border-top:1px solid #0f3460}
.kb{background:#0f3460;color:#e0e0e0;border:1px solid #533483;border-radius:6px;padding:8px 12px;font-size:13px;font-family:monospace;cursor:pointer;min-width:44px;text-align:center;user-select:none;touch-action:manipulation}
.kb:active{background:#533483}
.kb.on{background:#533483;border-color:#e94560;color:#e94560}
</style>
</head>
<body>
<div id=\"term\"></div>
<div id=\"toolbar\">
<div style=\"display:flex;gap:5px\">
<button class=\"kb\" id=\"ctrl-btn\" onclick=\"toggleCtrl()\">Ctrl</button>
<button class=\"kb\" onclick=\"send(chr(27))\">Esc</button>
<button class=\"kb\" onclick=\"send(chr(9))\">Tab</button>
</div>
<div style=\"display:flex;gap:5px\">
<button class=\"kb\" onclick=\"ctrlKey(chr(99))\">C</button>
<button class=\"kb\" onclick=\"ctrlKey(chr(100))\">D</button>
<button class=\"kb\" onclick=\"ctrlKey(chr(108))\">L</button>
<button class=\"kb\" onclick=\"ctrlKey(chr(122))\">Z</button>
<button class=\"kb\" onclick=\"ctrlKey(chr(97))\">A</button>
<button class=\"kb\" onclick=\"ctrlKey(chr(101))\">E</button>
<button class=\"kb\" onclick=\"ctrlKey(chr(117))\">U</button>
<button class=\"kb\" onclick=\"ctrlKey(chr(107))\">K</button>
</div>
<div style=\"display:flex;gap:5px\">
<button class=\"kb\" onclick=\"send(ESC+chr(91)+chr(65))\">▲</button>
<button class=\"kb\" onclick=\"send(ESC+chr(91)+chr(66))\">▼</button>
<button class=\"kb\" onclick=\"send(ESC+chr(91)+chr(68))\">◀</button>
<button class=\"kb\" onclick=\"send(ESC+chr(91)+chr(67))\">▶</button>
</div>
</div>
<script>
const ESC=\"\x1b\";
function chr(n){return String.fromCharCode(n)}
const term=new Terminal({cursorBlink:true,theme:{background:\"#1a1a2e\",foreground:\"#e0e0e0\",cursor:\"#e94560\"},fontFamily:\"monospace\",fontSize:13,scrollback:3000});
const fit=new FitAddon.FitAddon();
term.loadAddon(fit);
term.open(document.getElementById(\"term\"));
fit.fit();
const ws=new WebSocket(\"ws://\"+location.host+\"/ws\");
ws.binaryType=\"arraybuffer\";
ws.onopen=()=>{term.focus();sendResize()};
ws.onmessage=e=>term.write(e.data instanceof ArrayBuffer?new Uint8Array(e.data):e.data);
ws.onclose=()=>term.write(\"\\r\\n[disconnected]\\r\\n\");
term.onData(d=>ws.readyState===1&&ws.send(d));
function sendResize(){ws.readyState===1&&ws.send(JSON.stringify({type:\"resize\",cols:term.cols,rows:term.rows}))}
new ResizeObserver(()=>{fit.fit();sendResize()}).observe(document.getElementById(\"term\"));
let ctrl=false;
function toggleCtrl(){ctrl=!ctrl;document.getElementById(\"ctrl-btn\").classList.toggle(\"on\",ctrl)}
function send(d){ws.readyState===1&&ws.send(d)}
function ctrlKey(k){send(String.fromCharCode(k.charCodeAt(0)-96));if(ctrl){ctrl=false;document.getElementById(\"ctrl-btn\").classList.remove(\"on\")}}
</script>
</body>
</html>'''
with open('index.html','w') as f:
f.write(content)
"
EXPOSE 7860
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"] |