berryssx commited on
Commit
744c47a
·
verified ·
1 Parent(s): 50bb712

Update Dockerfile

Browse files
Files changed (1) hide show
  1. 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 websockets
9
 
10
- RUN cat > app.py << 'PYEOF'
 
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
- stdout=slave_fd,
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 resize(cols, rows):
40
- fcntl.ioctl(master_fd, termios.TIOCSWINSZ,
41
- struct.pack("HHHH", rows, cols, 0, 0))
 
 
 
42
 
43
- resize(220, 50)
44
 
45
- async def read_output():
46
  while True:
 
 
 
47
  try:
48
- data = await loop.run_in_executor(None, lambda: os.read(master_fd, 1024))
49
- if data:
50
- await websocket.send_bytes(data)
51
- except OSError:
52
  break
53
 
54
- reader_task = asyncio.ensure_future(read_output())
55
 
56
  try:
57
  while True:
58
  msg = await websocket.receive_text()
59
  try:
60
- data = json.loads(msg)
61
- if data.get("type") == "resize":
62
- resize(data["cols"], data["rows"])
 
63
  continue
64
  except:
65
  pass
@@ -67,111 +69,88 @@ async def ws_endpoint(websocket: WebSocket):
67
  except:
68
  pass
69
  finally:
70
- reader_task.cancel()
71
- try:
72
- os.close(master_fd)
73
- except:
74
- pass
75
- proc.terminate()
76
-
77
- PYEOF
78
-
79
- RUN cat > index.html << 'EOF'
80
- <!DOCTYPE html>
 
 
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
- * { margin:0; padding:0; box-sizing:border-box; }
91
- body { background:#1a1a2e; display:flex; flex-direction:column; height:100dvh; overflow:hidden; }
92
- #term { flex:1; overflow:hidden; }
93
- #toolbar { background:#16213e; padding:6px 8px; display:flex; flex-wrap:wrap; gap:5px; border-top:1px solid #0f3460; }
94
- .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; }
95
- .kb:active { background:#533483; }
96
- .kb.on { background:#533483; border-color:#e94560; color:#e94560; }
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('\x1b')">Esc</button>
105
- <button class="kb" onclick="send('\t')">Tab</button>
106
  </div>
107
- <div style="display:flex;gap:5px">
108
- <button class="kb" onclick="ctrlKey('c')">C</button>
109
- <button class="kb" onclick="ctrlKey('d')">D</button>
110
- <button class="kb" onclick="ctrlKey('l')">L</button>
111
- <button class="kb" onclick="ctrlKey('z')">Z</button>
112
- <button class="kb" onclick="ctrlKey('a')">A</button>
113
- <button class="kb" onclick="ctrlKey('e')">E</button>
114
- <button class="kb" onclick="ctrlKey('u')">U</button>
115
- <button class="kb" onclick="ctrlKey('k')">K</button>
116
  </div>
117
- <div style="display:flex;gap:5px">
118
- <button class="kb" onclick="send('\x1b[A')">▲</button>
119
- <button class="kb" onclick="send('\x1b[B')">▼</button>
120
- <button class="kb" onclick="send('\x1b[D')">◀</button>
121
- <button class="kb" onclick="send('\x1b[C')">▶</button>
122
  </div>
123
  </div>
124
  <script>
125
- const term = new Terminal({
126
- cursorBlink: true,
127
- theme: { background:'#1a1a2e', foreground:'#e0e0e0', cursor:'#e94560' },
128
- fontFamily:'"Courier New",monospace',
129
- fontSize: 13,
130
- scrollback: 3000,
131
- });
132
- const fit = new FitAddon.FitAddon();
133
  term.loadAddon(fit);
134
- term.open(document.getElementById('term'));
135
  fit.fit();
136
-
137
- const ws = new WebSocket(`ws://${location.host}/ws`);
138
- ws.binaryType = 'arraybuffer';
139
- ws.onopen = () => {
140
- term.focus();
141
- sendResize();
142
- };
143
- ws.onmessage = e => {
144
- if (e.data instanceof ArrayBuffer) {
145
- term.write(new Uint8Array(e.data));
146
- } else {
147
- term.write(e.data);
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
- EOF
 
 
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"]