dangthr commited on
Commit
fef6820
·
verified ·
1 Parent(s): e8cef92

Create remote_client.py

Browse files
Files changed (1) hide show
  1. remote_client.py +231 -0
remote_client.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import socketio
3
+ import subprocess
4
+ import json
5
+ import logging
6
+ import sys
7
+ import base64
8
+ import os
9
+ import threading
10
+ import time
11
+ from pathlib import Path
12
+
13
+ # 配置日志
14
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
15
+ logger = logging.getLogger(__name__)
16
+
17
+ class RemoteClient:
18
+ def __init__(self, space_id, user_token, server_url):
19
+ self.space_id = space_id
20
+ self.user_token = user_token
21
+ self.server_url = server_url
22
+ self.sio = socketio.AsyncClient(ping_timeout=60, ping_interval=25)
23
+ self.connected = False
24
+
25
+ # 绑定事件
26
+ self.sio.on('connect', self.on_connect)
27
+ self.sio.on('disconnect', self.on_disconnect)
28
+ self.sio.on('registered', self.on_registered)
29
+ self.sio.on('execute_command', self.on_execute_command)
30
+ self.sio.on('error', self.on_error)
31
+ self.sio.on('upload_success', self.on_upload_success)
32
+
33
+ async def on_connect(self):
34
+ logger.info("Connected to server")
35
+ # 注册客户端
36
+ await self.sio.emit('register_client', {
37
+ 'space_id': self.space_id,
38
+ 'token': self.user_token
39
+ })
40
+
41
+ async def on_disconnect(self):
42
+ logger.info("Disconnected from server")
43
+ self.connected = False
44
+
45
+ async def on_registered(self, data):
46
+ logger.info(f"Successfully registered: {data}")
47
+ self.connected = True
48
+
49
+ async def on_error(self, data):
50
+ logger.error(f"Server error: {data}")
51
+
52
+ async def on_upload_success(self, data):
53
+ logger.info(f"File upload successful: {data}")
54
+
55
+ async def on_execute_command(self, data):
56
+ """处理执行命令请求"""
57
+ task_id = data.get('task_id')
58
+ command = data.get('command')
59
+ token = data.get('token')
60
+
61
+ logger.info(f"Received command for task {task_id}: {command}")
62
+
63
+ # 发送开始执行的状态
64
+ await self.sio.emit('command_result', {
65
+ 'task_id': task_id,
66
+ 'status': 'running',
67
+ 'output': f'开始执行命令: {command}'
68
+ })
69
+
70
+ # 在后台执行命令
71
+ asyncio.create_task(self.execute_command_async(task_id, command, token))
72
+
73
+ async def execute_command_async(self, task_id, command, token):
74
+ """异步执行命令"""
75
+ try:
76
+ # 创建 output 目录
77
+ os.makedirs('./output', exist_ok=True)
78
+
79
+ logger.info(f"Executing: {command}")
80
+
81
+ # 执行命令
82
+ process = await asyncio.create_subprocess_shell(
83
+ command,
84
+ stdout=asyncio.subprocess.PIPE,
85
+ stderr=asyncio.subprocess.STDOUT,
86
+ text=True
87
+ )
88
+
89
+ # 实时读取输出
90
+ output_buffer = ""
91
+ while True:
92
+ line = await process.stdout.readline()
93
+ if not line:
94
+ break
95
+ line_str = line.decode() if isinstance(line, bytes) else line
96
+ output_buffer += line_str
97
+ logger.info(f"Command output: {line_str.strip()}")
98
+
99
+ # 发送实时输出
100
+ await self.sio.emit('command_result', {
101
+ 'task_id': task_id,
102
+ 'status': 'running',
103
+ 'output': line_str
104
+ })
105
+
106
+ # 等待进程完成
107
+ await process.wait()
108
+
109
+ if process.returncode == 0:
110
+ logger.info("Command executed successfully")
111
+
112
+ # 扫描 output 目录并上传文件
113
+ await self.upload_output_files(token)
114
+
115
+ await self.sio.emit('command_result', {
116
+ 'task_id': task_id,
117
+ 'status': 'completed',
118
+ 'output': '命令执行完成,文件已上传到网盘'
119
+ })
120
+ else:
121
+ logger.error(f"Command failed with return code: {process.returncode}")
122
+ await self.sio.emit('command_result', {
123
+ 'task_id': task_id,
124
+ 'status': 'failed',
125
+ 'output': f'命令执行失败,退出码: {process.returncode}'
126
+ })
127
+
128
+ except Exception as e:
129
+ logger.error(f"Error executing command: {e}")
130
+ await self.sio.emit('command_result', {
131
+ 'task_id': task_id,
132
+ 'status': 'failed',
133
+ 'output': f'执行命令时出错: {str(e)}'
134
+ })
135
+
136
+ async def upload_output_files(self, token):
137
+ """上传 output 目录中的文件"""
138
+ output_dir = Path('./output')
139
+ if not output_dir.exists():
140
+ logger.warning("Output directory does not exist")
141
+ return
142
+
143
+ uploaded_count = 0
144
+ failed_count = 0
145
+
146
+ for file_path in output_dir.iterdir():
147
+ if file_path.is_file():
148
+ try:
149
+ logger.info(f"Uploading file: {file_path.name}")
150
+
151
+ # 读取文件内容
152
+ with open(file_path, 'rb') as f:
153
+ file_content = f.read()
154
+
155
+ # 编码为 base64
156
+ file_b64 = base64.b64encode(file_content).decode('utf-8')
157
+
158
+ # 发送文件上传请求
159
+ await self.sio.emit('file_upload', {
160
+ 'filename': file_path.name,
161
+ 'content': file_b64,
162
+ 'token': token
163
+ })
164
+
165
+ uploaded_count += 1
166
+ logger.info(f"Successfully uploaded: {file_path.name}")
167
+
168
+ # 删除已上传的文件
169
+ file_path.unlink()
170
+
171
+ # 避免发送过大的数据包,稍作延迟
172
+ await asyncio.sleep(0.5)
173
+
174
+ except Exception as e:
175
+ logger.error(f"Failed to upload {file_path.name}: {e}")
176
+ failed_count += 1
177
+
178
+ logger.info(f"Upload completed. Success: {uploaded_count}, Failed: {failed_count}")
179
+
180
+ async def connect(self):
181
+ """连接到服务器"""
182
+ try:
183
+ # 构建 WebSocket URL
184
+ ws_url = self.server_url.replace('https://', 'wss://').replace('http://', 'ws://')
185
+ if not ws_url.endswith('/'):
186
+ ws_url += '/'
187
+ ws_url += 'socket.io/'
188
+
189
+ logger.info(f"Connecting to {ws_url}")
190
+ await self.sio.connect(ws_url, transports=['websocket'])
191
+
192
+ # 保持连接
193
+ while self.connected:
194
+ await asyncio.sleep(1)
195
+
196
+ except Exception as e:
197
+ logger.error(f"Connection failed: {e}")
198
+ raise
199
+
200
+ async def disconnect(self):
201
+ """断开连接"""
202
+ if self.sio.connected:
203
+ await self.sio.disconnect()
204
+
205
+ async def main():
206
+ if len(sys.argv) != 4:
207
+ print("Usage: python3 remote_client.py <space_id> <user_token> <server_url>")
208
+ print("Example: python3 remote_client.py 'abc123' 'token456' 'https://gkbtyo-rqvays-5001.preview.cloudstudio.work'")
209
+ sys.exit(1)
210
+
211
+ space_id = sys.argv[1]
212
+ user_token = sys.argv[2]
213
+ server_url = sys.argv[3]
214
+
215
+ logger.info(f"Starting remote client for Space: {space_id}")
216
+ logger.info(f"Server: {server_url}")
217
+
218
+ client = RemoteClient(space_id, user_token, server_url)
219
+
220
+ try:
221
+ await client.connect()
222
+ except KeyboardInterrupt:
223
+ logger.info("Received interrupt signal")
224
+ except Exception as e:
225
+ logger.error(f"Client error: {e}")
226
+ finally:
227
+ await client.disconnect()
228
+ logger.info("Client stopped")
229
+
230
+ if __name__ == "__main__":
231
+ asyncio.run(main())