hh / Dockerfile
berryssx's picture
Update Dockerfile
744c47a verified
Raw
History Blame Contribute Delete
6.04 kB
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"]