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