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

Update Dockerfile

Browse files
Files changed (1) hide show
  1. Dockerfile +105 -161
Dockerfile CHANGED
@@ -3,59 +3,82 @@ FROM python:3.10-slim
3
  WORKDIR /app
4
 
5
  RUN apt-get update && apt-get install -y \
6
- git wget curl vim build-essential
7
 
8
- RUN pip install --no-cache-dir \
9
- fastapi \
10
- uvicorn \
11
- huggingface-hub
12
 
13
  RUN cat > app.py << 'PYEOF'
14
  from fastapi import FastAPI, WebSocket
15
  from fastapi.responses import HTMLResponse
16
- import asyncio, subprocess, os, json
17
 
18
  app = FastAPI()
19
 
20
- HTML = open("index.html").read()
21
-
22
  @app.get("/")
23
  async def root():
24
- return HTMLResponse(HTML)
25
 
26
  @app.websocket("/ws")
27
- async def ws(websocket: WebSocket):
28
  await websocket.accept()
29
- proc = await asyncio.create_subprocess_shell(
 
 
 
30
  "/bin/bash",
31
- stdin=asyncio.subprocess.PIPE,
32
- stdout=asyncio.subprocess.PIPE,
33
- stderr=asyncio.subprocess.STDOUT,
34
- env={**os.environ, "TERM": "xterm-256color"}
 
35
  )
 
 
 
 
 
 
 
 
 
 
36
  async def read_output():
37
  while True:
38
- data = await proc.stdout.read(1024)
39
- if not data:
 
 
 
40
  break
41
- await websocket.send_text(data.decode("utf-8", errors="replace"))
42
- asyncio.ensure_future(read_output())
 
43
  try:
44
  while True:
45
  msg = await websocket.receive_text()
46
- if proc.stdin:
47
- proc.stdin.write(msg.encode())
48
- await proc.stdin.drain()
 
 
 
 
 
49
  except:
50
  pass
51
  finally:
 
 
 
 
 
52
  proc.terminate()
53
 
54
  PYEOF
55
 
56
  RUN cat > index.html << 'EOF'
57
  <!DOCTYPE html>
58
- <html lang="en">
59
  <head>
60
  <meta charset="UTF-8">
61
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
@@ -64,170 +87,91 @@ RUN cat > index.html << 'EOF'
64
  <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
65
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css">
66
  <style>
67
- * { margin: 0; padding: 0; box-sizing: border-box; }
68
- body { background: #1a1a2e; display: flex; flex-direction: column; height: 100dvh; font-family: monospace; overflow: hidden; }
69
- #terminal-container { flex: 1; overflow: hidden; }
70
- #toolbar { background: #16213e; padding: 6px 8px; display: flex; flex-wrap: wrap; gap: 6px; border-top: 1px solid #0f3460; }
71
- .key-group { display: flex; gap: 4px; }
72
- .key-btn {
73
- background: #0f3460;
74
- color: #e0e0e0;
75
- border: 1px solid #533483;
76
- border-radius: 6px;
77
- padding: 8px 12px;
78
- font-size: 13px;
79
- font-family: monospace;
80
- cursor: pointer;
81
- min-width: 44px;
82
- text-align: center;
83
- user-select: none;
84
- -webkit-user-select: none;
85
- touch-action: manipulation;
86
- }
87
- .key-btn:active { background: #533483; }
88
- .key-btn.ctrl-active { background: #533483; border-color: #e94560; color: #e94560; }
89
- #ctrl-btn { min-width: 52px; }
90
- #input-row { background: #16213e; padding: 4px 8px; display: flex; gap: 6px; border-top: 1px solid #0f3460; }
91
- #mobile-input {
92
- flex: 1;
93
- background: #0f3460;
94
- color: #e0e0e0;
95
- border: 1px solid #533483;
96
- border-radius: 6px;
97
- padding: 8px 10px;
98
- font-size: 14px;
99
- font-family: monospace;
100
- outline: none;
101
- }
102
- #send-btn {
103
- background: #533483;
104
- color: white;
105
- border: none;
106
- border-radius: 6px;
107
- padding: 8px 14px;
108
- font-size: 14px;
109
- cursor: pointer;
110
- touch-action: manipulation;
111
- }
112
  </style>
113
  </head>
114
  <body>
115
- <div id="terminal-container"></div>
116
-
117
  <div id="toolbar">
118
- <div class="key-group">
119
- <button class="key-btn" id="ctrl-btn" onclick="toggleCtrl()">Ctrl</button>
120
- <button class="key-btn" onclick="sendSpecial('escape')">Esc</button>
121
- <button class="key-btn" onclick="sendSpecial('tab')">Tab</button>
122
  </div>
123
- <div class="key-group">
124
- <button class="key-btn" onclick="sendCtrlKey('c')">C</button>
125
- <button class="key-btn" onclick="sendCtrlKey('d')">D</button>
126
- <button class="key-btn" onclick="sendCtrlKey('l')">L</button>
127
- <button class="key-btn" onclick="sendCtrlKey('z')">Z</button>
128
- <button class="key-btn" onclick="sendCtrlKey('a')">A</button>
129
- <button class="key-btn" onclick="sendCtrlKey('e')">E</button>
130
- <button class="key-btn" onclick="sendCtrlKey('u')">U</button>
131
- <button class="key-btn" onclick="sendCtrlKey('k')">K</button>
132
  </div>
133
- <div class="key-group">
134
- <button class="key-btn" onclick="sendSpecial('up')">▲</button>
135
- <button class="key-btn" onclick="sendSpecial('down')">▼</button>
136
- <button class="key-btn" onclick="sendSpecial('left')">◀</button>
137
- <button class="key-btn" onclick="sendSpecial('right')">▶</button>
138
  </div>
139
  </div>
140
-
141
- <div id="input-row">
142
- <input id="mobile-input" type="text" placeholder="Введи команду..." autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
143
- <button id="send-btn" onclick="sendInputLine()">↵</button>
144
- </div>
145
-
146
  <script>
147
  const term = new Terminal({
148
  cursorBlink: true,
149
- theme: {
150
- background: '#1a1a2e',
151
- foreground: '#e0e0e0',
152
- cursor: '#e94560',
153
- selectionBackground: '#533483',
154
- black: '#1a1a2e', brightBlack: '#404060',
155
- red: '#e94560', brightRed: '#ff6b8a',
156
- green: '#00d2ff', brightGreen: '#4effa0',
157
- yellow: '#f5a623', brightYellow: '#ffd700',
158
- blue: '#7b8cde', brightBlue: '#a0b4ff',
159
- magenta: '#c678dd', brightMagenta: '#e0a0ff',
160
- cyan: '#56b6c2', brightCyan: '#80dfea',
161
- white: '#abb2bf', brightWhite: '#ffffff',
162
- },
163
- fontFamily: '"Courier New", monospace',
164
  fontSize: 13,
165
- lineHeight: 1.2,
166
- scrollback: 2000,
167
  });
168
-
169
- const fitAddon = new FitAddon.FitAddon();
170
- term.loadAddon(fitAddon);
171
- term.open(document.getElementById('terminal-container'));
172
- fitAddon.fit();
173
 
174
  const ws = new WebSocket(`ws://${location.host}/ws`);
175
- ws.onopen = () => { term.focus(); };
176
- ws.onmessage = e => term.write(e.data);
177
- ws.onclose = () => term.write('\r\n[connection closed]\r\n');
 
 
 
 
 
 
 
 
 
178
 
179
  term.onData(data => ws.readyState === 1 && ws.send(data));
180
 
181
- new ResizeObserver(() => fitAddon.fit()).observe(document.getElementById('terminal-container'));
182
-
183
- let ctrlActive = false;
184
- function toggleCtrl() {
185
- ctrlActive = !ctrlActive;
186
- document.getElementById('ctrl-btn').classList.toggle('ctrl-active', ctrlActive);
187
- }
188
-
189
- function sendCtrlKey(key) {
190
- if (ws.readyState !== 1) return;
191
- const code = key.toUpperCase().charCodeAt(0) - 64;
192
- ws.send(String.fromCharCode(code));
193
- if (ctrlActive) { ctrlActive = false; document.getElementById('ctrl-btn').classList.remove('ctrl-active'); }
194
- }
195
-
196
- function sendSpecial(key) {
197
- if (ws.readyState !== 1) return;
198
- const map = {
199
- escape: '\x1b',
200
- tab: '\t',
201
- up: '\x1b[A',
202
- down: '\x1b[B',
203
- right: '\x1b[C',
204
- left: '\x1b[D',
205
- };
206
- if (map[key]) ws.send(map[key]);
207
- }
208
-
209
- const inp = document.getElementById('mobile-input');
210
- inp.addEventListener('keydown', e => {
211
- if (e.key === 'Enter') { sendInputLine(); e.preventDefault(); }
212
- });
213
-
214
- function sendInputLine() {
215
- const val = inp.value;
216
  if (ws.readyState === 1) {
217
- ws.send(val + '\n');
218
- inp.value = '';
219
  }
220
  }
 
 
221
 
222
- term.element.addEventListener('click', () => {
223
- term.focus();
224
- inp.blur();
225
- });
 
 
 
 
 
 
226
  </script>
227
  </body>
228
  </html>
229
  EOF
230
 
231
  EXPOSE 7860
232
-
233
  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
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
66
+ os.write(master_fd, msg.encode())
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">
 
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"]