rcoder 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rcoder/core.py ADDED
@@ -0,0 +1,584 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Rcoder核心模块
4
+ 实现远程代码执行与管理的核心功能
5
+ """
6
+ import ssl
7
+ import socket
8
+ import json
9
+ import time
10
+ import asyncio
11
+ import hashlib
12
+ import threading
13
+ import queue
14
+ from typing import Dict, Any, Optional, List, Tuple, Callable
15
+ from dataclasses import dataclass
16
+
17
+ @dataclass
18
+ class CommandResult:
19
+ """命令执行结果"""
20
+ stdout: str
21
+ stderr: str
22
+ returncode: int
23
+ execution_time: float
24
+
25
+ @dataclass
26
+ class BatchResult:
27
+ """批量命令执行结果"""
28
+ command: str
29
+ results: Dict[str, CommandResult]
30
+ success_count: int
31
+ failure_count: int
32
+ total_time: float
33
+
34
+ class RcoderCore:
35
+ """
36
+ Rcoder核心类
37
+ 实现远程代码执行与管理的核心功能
38
+ """
39
+
40
+ def __init__(self, host: str = '192.168.1.8', port: int = 443, use_https_disguise: bool = True, proxy_server: Optional[Tuple[str, int]] = None):
41
+ """
42
+ 初始化Rcoder核心
43
+
44
+ Args:
45
+ host: 服务器主机
46
+ port: 服务器端口 (默认443,支持HTTPS伪装)
47
+ use_https_disguise: 是否使用HTTPS伪装
48
+ proxy_server: 中转服务器 (host, port),如 ('1.2.3.4', 443)
49
+ """
50
+ self.host = host
51
+ self.port = port
52
+ self.use_https_disguise = use_https_disguise
53
+ self.proxy_server = proxy_server
54
+ self.ssl_context = self._create_ssl_context()
55
+ self.token = None
56
+ self.public_key = None
57
+ self.private_key = None
58
+ self._monitoring_enabled = False
59
+ self._monitoring_thread = None
60
+ self._alert_queue = queue.Queue()
61
+ self._command_queue = queue.Queue()
62
+ self._results = {}
63
+ self._lock = threading.Lock()
64
+ self._session_id = hashlib.sha256(str(time.time()).encode()).hexdigest()
65
+
66
+ config_info = f"{host}:{port} (HTTPS伪装: {'启用' if use_https_disguise else '禁用'})"
67
+ if proxy_server:
68
+ config_info += f" (中转服务器: {proxy_server[0]}:{proxy_server[1]})"
69
+
70
+ print(f"✅ Rcoder初始化完成 (会话ID: {self._session_id[:8]})")
71
+ print(f"📡 连接配置: {config_info}")
72
+
73
+ def _create_ssl_context(self) -> ssl.SSLContext:
74
+ """创建SSL上下文,增强HTTPS伪装"""
75
+ context = ssl.create_default_context()
76
+
77
+ # 增强HTTPS伪装
78
+ if self.use_https_disguise:
79
+ # 模拟标准HTTPS客户端行为
80
+ context.minimum_version = ssl.TLSVersion.TLSv1_2
81
+ context.maximum_version = ssl.TLSVersion.TLSv1_3
82
+
83
+ # 禁用主机名检查以支持自定义端口
84
+ context.check_hostname = False
85
+ context.verify_mode = ssl.CERT_NONE
86
+
87
+ # 模拟常见浏览器的密码套件偏好
88
+ context.set_ciphers('ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384')
89
+ else:
90
+ # 标准模式
91
+ context.check_hostname = False
92
+ context.verify_mode = ssl.CERT_NONE
93
+
94
+ return context
95
+
96
+ def _call(self, method: str, params: Dict[str, Any] = None,
97
+ retry_on_failure: bool = True, max_retries: int = 5) -> Dict[str, Any]:
98
+ """执行JSON-RPC调用,增强HTTPS伪装
99
+
100
+ Args:
101
+ method: RPC方法名
102
+ params: RPC参数
103
+ retry_on_failure: 是否在失败时重试
104
+ max_retries: 最大重试次数
105
+ """
106
+ retry_count = 0
107
+
108
+ while retry_count <= max_retries:
109
+ try:
110
+ # 建立TCP连接(支持中转服务器)
111
+ if self.proxy_server:
112
+ print(f" 🔄 通过中转服务器连接: {self.proxy_server[0]}:{self.proxy_server[1]}")
113
+ sock = socket.create_connection((self.proxy_server[0], self.proxy_server[1]), timeout=30)
114
+
115
+ # 发送代理连接请求
116
+ proxy_connect = f"CONNECT {self.host}:{self.port} HTTP/1.1\r\n"
117
+ proxy_connect += f"Host: {self.host}:{self.port}\r\n"
118
+ proxy_connect += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\r\n"
119
+ proxy_connect += "Connection: Keep-Alive\r\n"
120
+ proxy_connect += "\r\n"
121
+
122
+ sock.send(proxy_connect.encode())
123
+ # 读取代理响应
124
+ proxy_response = sock.recv(4096)
125
+ if b"200 Connection established" not in proxy_response:
126
+ raise Exception(f"Proxy connection failed: {proxy_response.decode()}")
127
+ print(f" ✅ 中转服务器连接成功")
128
+ else:
129
+ # 直接连接
130
+ sock = socket.create_connection((self.host, self.port), timeout=30)
131
+
132
+ # 增强HTTPS伪装:添加HTTP请求头模拟
133
+ if self.use_https_disguise:
134
+ # 模拟HTTP请求头
135
+ http_headers = {
136
+ "Host": f"{self.host}:{self.port}",
137
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
138
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
139
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
140
+ "Accept-Encoding": "gzip, deflate, br",
141
+ "Connection": "keep-alive",
142
+ "Upgrade-Insecure-Requests": "1",
143
+ "Sec-Fetch-Dest": "document",
144
+ "Sec-Fetch-Mode": "navigate",
145
+ "Sec-Fetch-Site": "none",
146
+ "Sec-Fetch-User": "?1",
147
+ "Cache-Control": "max-age=0"
148
+ }
149
+
150
+ # 构建HTTP请求
151
+ http_request = "GET / HTTP/1.1\r\n"
152
+ for key, value in http_headers.items():
153
+ http_request += f"{key}: {value}\r\n"
154
+ http_request += "\r\n"
155
+
156
+ # 发送HTTP请求作为伪装
157
+ sock.send(http_request.encode())
158
+ # 读取HTTP响应(丢弃)
159
+ sock.recv(4096)
160
+
161
+ # 包装为TLS连接
162
+ server_hostname = f"{self.host}" if self.use_https_disguise else "rcoder"
163
+ tls_sock = self.ssl_context.wrap_socket(sock, server_hostname=server_hostname)
164
+
165
+ # 构建请求,增强安全性
166
+ request = {
167
+ "jsonrpc": "2.0",
168
+ "id": int(time.time() * 1000),
169
+ "method": method,
170
+ "params": params or {},
171
+ "session_id": self._session_id[:16],
172
+ "timestamp": int(time.time()),
173
+ "version": "1.0"
174
+ }
175
+
176
+ # 发送请求
177
+ request_data = json.dumps(request).encode() + b"\n"
178
+ tls_sock.send(request_data)
179
+
180
+ # 接收响应
181
+ response = tls_sock.recv(65536)
182
+ tls_sock.close()
183
+
184
+ if not response:
185
+ if retry_on_failure and retry_count < max_retries:
186
+ retry_count += 1
187
+ time.sleep(1)
188
+ continue
189
+ raise Exception("Empty response from server")
190
+
191
+ # 解析响应
192
+ return json.loads(response)
193
+
194
+ except (socket.error, ConnectionResetError, ConnectionRefusedError) as e:
195
+ if retry_on_failure and retry_count < max_retries:
196
+ retry_count += 1
197
+ time.sleep(1)
198
+ continue
199
+ raise
200
+ except json.JSONDecodeError as e:
201
+ if retry_on_failure and retry_count < max_retries:
202
+ retry_count += 1
203
+ time.sleep(1)
204
+ continue
205
+ raise
206
+ except Exception as e:
207
+ if retry_on_failure and retry_count < max_retries:
208
+ retry_count += 1
209
+ time.sleep(1)
210
+ continue
211
+ raise
212
+
213
+ def execute(self, server: str, command: str, timeout: int = 60,
214
+ wait_for_restart: bool = False, restart_check_interval: int = 2,
215
+ restart_max_wait: int = 60) -> str:
216
+ """执行命令
217
+
218
+ Args:
219
+ server: 服务器名称
220
+ command: 命令
221
+ timeout: 超时时间
222
+ wait_for_restart: 是否等待重启完成
223
+ restart_check_interval: 重启检查间隔
224
+ restart_max_wait: 最大重启等待时间
225
+ """
226
+ # 检查是否为重启命令
227
+ is_restart_command = any(keyword in command.lower() for keyword in [
228
+ 'restart', 'systemctl restart', 'service restart', 'reboot'
229
+ ])
230
+
231
+ if is_restart_command and wait_for_restart:
232
+ print(f"执行重启命令并等待完成...")
233
+ print(f"命令: {command}")
234
+ print(f"最大等待时间: {restart_max_wait}秒")
235
+
236
+ try:
237
+ # 执行重启命令
238
+ result = self._call("tools/call", {
239
+ "name": "ssh_exec",
240
+ "arguments": {"name": server, "command": command, "timeout": timeout}
241
+ })
242
+
243
+ if "result" in result:
244
+ data = json.loads(result["result"]["content"][0]["text"])
245
+ print(f"重启命令执行结果: {data.get('stdout', '').strip() or data.get('stderr', '').strip()}")
246
+ except Exception as e:
247
+ print(f"重启命令执行异常: {e}")
248
+ # 继续等待,因为重启命令可能已经开始执行
249
+
250
+ # 等待重启完成
251
+ start_time = time.time()
252
+ elapsed = 0
253
+
254
+ print("等待服务重启...")
255
+ while elapsed < restart_max_wait:
256
+ time.sleep(restart_check_interval)
257
+ elapsed = time.time() - start_time
258
+
259
+ try:
260
+ # 尝试重新连接并检查服务是否可用
261
+ test_result = self._call(
262
+ "tools/call",
263
+ {
264
+ "name": "ssh_exec",
265
+ "arguments": {"name": server, "command": "echo 'Rcoder service available'", "timeout": 10}
266
+ },
267
+ retry_on_failure=True,
268
+ max_retries=2
269
+ )
270
+
271
+ if "result" in test_result:
272
+ test_data = json.loads(test_result["result"]["content"][0]["text"])
273
+ if "Rcoder service available" in test_data.get("stdout", ""):
274
+ print(f"✅ 服务已重启完成 (耗时: {elapsed:.1f}秒)")
275
+ return f"重启完成 (耗时: {elapsed:.1f}秒)"
276
+
277
+ except (socket.error, ConnectionResetError, ConnectionRefusedError) as e:
278
+ # 重启过程中的连接错误是预期的
279
+ print(f" ⏳ 服务正在重启中... ({elapsed:.1f}秒)")
280
+ except json.JSONDecodeError as e:
281
+ # 重启过程中可能会出现响应不完整的情况
282
+ print(f" ⏳ 服务响应不完整,继续等待... ({elapsed:.1f}秒)")
283
+ except Exception as e:
284
+ # 其他错误
285
+ print(f" ⏳ 等待服务恢复... ({elapsed:.1f}秒)")
286
+
287
+ print(f"⏳ 等待中... ({elapsed:.1f}/{restart_max_wait}秒)")
288
+
289
+ print(f"❌ 重启等待超时 ({restart_max_wait}秒)")
290
+ return f"重启命令已执行,但等待超时 ({restart_max_wait}秒)"
291
+ else:
292
+ # 正常命令执行
293
+ start_time = time.time()
294
+ result = self._call("tools/call", {
295
+ "name": "ssh_exec",
296
+ "arguments": {"name": server, "command": command, "timeout": timeout}
297
+ })
298
+ execution_time = time.time() - start_time
299
+
300
+ if "result" in result:
301
+ data = json.loads(result["result"]["content"][0]["text"])
302
+ return data.get("stdout", "") or data.get("stderr", "") or data.get("error", "")
303
+ return str(result)
304
+
305
+ async def execute_async(self, server: str, command: str, timeout: int = 60) -> str:
306
+ """异步执行命令"""
307
+ loop = asyncio.get_event_loop()
308
+ return await loop.run_in_executor(
309
+ None,
310
+ self.execute,
311
+ server,
312
+ command,
313
+ timeout
314
+ )
315
+
316
+ def execute_batch(self, server: str, commands: List[str], timeout: int = 60) -> Dict[str, str]:
317
+ """批量执行命令
318
+
319
+ Args:
320
+ server: 服务器名称
321
+ commands: 命令列表
322
+ timeout: 超时时间
323
+ """
324
+ results = {}
325
+ for command in commands:
326
+ try:
327
+ result = self.execute(server, command, timeout=timeout)
328
+ results[command] = result
329
+ except Exception as e:
330
+ results[command] = f"Error: {e}"
331
+ return results
332
+
333
+ async def execute_batch_async(self, server: str, commands: List[str], timeout: int = 60) -> Dict[str, str]:
334
+ """异步批量执行命令"""
335
+ tasks = []
336
+ for command in commands:
337
+ task = self.execute_async(server, command, timeout)
338
+ tasks.append(task)
339
+
340
+ results = await asyncio.gather(*tasks)
341
+ return dict(zip(commands, results))
342
+
343
+ def list_connections(self) -> Dict[str, Any]:
344
+ """列出SSH连接"""
345
+ result = self._call("tools/call", {"name": "ssh_list", "arguments": {}})
346
+ if "result" in result:
347
+ return json.loads(result["result"]["content"][0]["text"])
348
+ return result
349
+
350
+ def connect(self, server: str) -> Dict[str, Any]:
351
+ """连接到SSH服务器"""
352
+ result = self._call("tools/call", {
353
+ "name": "ssh_connect",
354
+ "arguments": {"name": server}
355
+ })
356
+ if "result" in result:
357
+ return json.loads(result["result"]["content"][0]["text"])
358
+ return result
359
+
360
+ def disconnect(self, server: str) -> Dict[str, Any]:
361
+ """断开SSH连接"""
362
+ result = self._call("tools/call", {
363
+ "name": "ssh_disconnect",
364
+ "arguments": {"name": server}
365
+ })
366
+ if "result" in result:
367
+ return json.loads(result["result"]["content"][0]["text"])
368
+ return result
369
+
370
+ def start_monitoring(self, interval: int = 30):
371
+ """启动监控
372
+
373
+ Args:
374
+ interval: 监控间隔(秒)
375
+ """
376
+ def monitor():
377
+ while self._monitoring_enabled:
378
+ try:
379
+ # 检查服务器状态
380
+ status = self.execute('local', 'uptime')
381
+ print(f"[监控] 服务器状态: {status.strip()}")
382
+
383
+ # 检查内存使用
384
+ memory = self.execute('local', 'free -h')
385
+ print(f"[监控] 内存使用:\n{memory.strip()}")
386
+
387
+ # 检查磁盘使用
388
+ disk = self.execute('local', 'df -h')
389
+ print(f"[监控] 磁盘使用:\n{disk.strip()}")
390
+
391
+ except Exception as e:
392
+ print(f"[监控] 错误: {e}")
393
+
394
+ time.sleep(interval)
395
+
396
+ self._monitoring_enabled = True
397
+ self._monitoring_thread = threading.Thread(target=monitor, daemon=True)
398
+ self._monitoring_thread.start()
399
+ print("✅ 监控已启动")
400
+
401
+ def stop_monitoring(self):
402
+ """停止监控"""
403
+ self._monitoring_enabled = False
404
+ if self._monitoring_thread:
405
+ self._monitoring_thread.join(timeout=5)
406
+ print("✅ 监控已停止")
407
+
408
+ def add_alert(self, condition: Callable[[], bool], message: str):
409
+ """添加告警
410
+
411
+ Args:
412
+ condition: 告警条件
413
+ message: 告警消息
414
+ """
415
+ if condition():
416
+ self._alert_queue.put(message)
417
+ print(f"⚠️ 告警: {message}")
418
+
419
+ def get_alerts(self) -> List[str]:
420
+ """获取告警列表"""
421
+ alerts = []
422
+ while not self._alert_queue.empty():
423
+ alerts.append(self._alert_queue.get())
424
+ return alerts
425
+
426
+ def get_server_info(self) -> Dict[str, Any]:
427
+ """获取服务器信息"""
428
+ result = self._call("initialize")
429
+ if "result" in result:
430
+ return result["result"]
431
+ return result
432
+
433
+ def get_available_tools(self) -> Dict[str, Any]:
434
+ """获取可用工具"""
435
+ result = self._call("tools/list")
436
+ if "result" in result:
437
+ return result["result"]
438
+ return result
439
+
440
+ def setup_key_auth(self, public_key: str, private_key: str):
441
+ """设置密钥认证
442
+
443
+ Args:
444
+ public_key: 公钥
445
+ private_key: 私钥
446
+ """
447
+ self.public_key = public_key
448
+ self.private_key = private_key
449
+ print("✅ 密钥认证已设置")
450
+
451
+ def shutdown(self):
452
+ """关闭资源"""
453
+ self.stop_monitoring()
454
+ print("✅ Rcoder已关闭")
455
+
456
+
457
+ class RemoteHost:
458
+ """
459
+ 远程主机管理类
460
+ 提供类似本地主机的使用体验
461
+ """
462
+
463
+ def __init__(self, rcoder: RcoderCore, server: str = 'local'):
464
+ """
465
+ 初始化远程主机管理
466
+
467
+ Args:
468
+ rcoder: Rcoder核心实例
469
+ server: 服务器名称
470
+ """
471
+ self.rcoder = rcoder
472
+ self.server = server
473
+
474
+ def run(self, command: str, timeout: int = 60, wait_for_restart: bool = False) -> str:
475
+ """运行命令
476
+
477
+ Args:
478
+ command: 命令
479
+ timeout: 超时时间
480
+ wait_for_restart: 是否等待重启完成
481
+ """
482
+ return self.rcoder.execute(
483
+ self.server,
484
+ command,
485
+ timeout=timeout,
486
+ wait_for_restart=wait_for_restart
487
+ )
488
+
489
+ async def run_async(self, command: str, timeout: int = 60) -> str:
490
+ """异步运行命令"""
491
+ return await self.rcoder.execute_async(self.server, command, timeout)
492
+
493
+ def run_batch(self, commands: List[str], timeout: int = 60) -> Dict[str, str]:
494
+ """批量运行命令"""
495
+ return self.rcoder.execute_batch(self.server, commands, timeout)
496
+
497
+ async def run_batch_async(self, commands: List[str], timeout: int = 60) -> Dict[str, str]:
498
+ """异步批量运行命令"""
499
+ return await self.rcoder.execute_batch_async(self.server, commands, timeout)
500
+
501
+ def ls(self, path: str = '.') -> str:
502
+ """列出目录内容"""
503
+ return self.run(f'ls -la {path}')
504
+
505
+ def cat(self, file: str) -> str:
506
+ """查看文件内容"""
507
+ return self.run(f'cat {file}')
508
+
509
+ def mkdir(self, path: str) -> str:
510
+ """创建目录"""
511
+ return self.run(f'mkdir -p {path}')
512
+
513
+ def rm(self, path: str, recursive: bool = False) -> str:
514
+ """删除文件或目录"""
515
+ if recursive:
516
+ return self.run(f'rm -rf {path}')
517
+ return self.run(f'rm {path}')
518
+
519
+ def cp(self, source: str, destination: str) -> str:
520
+ """复制文件或目录"""
521
+ return self.run(f'cp -r {source} {destination}')
522
+
523
+ def mv(self, source: str, destination: str) -> str:
524
+ """移动文件或目录"""
525
+ return self.run(f'mv {source} {destination}')
526
+
527
+ def systemctl(self, action: str, service: str) -> str:
528
+ """管理系统服务
529
+
530
+ Args:
531
+ action: 动作 (start, stop, restart, status)
532
+ service: 服务名称
533
+ """
534
+ wait_for_restart = action == 'restart'
535
+ return self.run(
536
+ f'sudo systemctl {action} {service}',
537
+ wait_for_restart=wait_for_restart
538
+ )
539
+
540
+ def ps(self) -> str:
541
+ """查看进程"""
542
+ return self.run('ps aux')
543
+
544
+ def top(self) -> str:
545
+ """查看系统负载"""
546
+ return self.run('top -b -n 1')
547
+
548
+ def free(self) -> str:
549
+ """查看内存使用"""
550
+ return self.run('free -h')
551
+
552
+ def df(self) -> str:
553
+ """查看磁盘使用"""
554
+ return self.run('df -h')
555
+
556
+ def uptime(self) -> str:
557
+ """查看系统运行时间"""
558
+ return self.run('uptime')
559
+
560
+ def hostname(self) -> str:
561
+ """查看主机名"""
562
+ return self.run('hostname')
563
+
564
+ def ip(self) -> str:
565
+ """查看IP地址"""
566
+ return self.run('ip addr')
567
+
568
+ def ping(self, host: str, count: int = 4) -> str:
569
+ """ping主机"""
570
+ return self.run(f'ping -c {count} {host}')
571
+
572
+
573
+ def get_remote_host(host: str = '192.168.1.8', port: int = 443, server: str = 'local', use_https_disguise: bool = True, proxy_server: Optional[Tuple[str, int]] = None) -> RemoteHost:
574
+ """获取远程主机管理实例
575
+
576
+ Args:
577
+ host: Rcoder服务器主机
578
+ port: Rcoder服务器端口 (默认443,支持HTTPS伪装)
579
+ server: 服务器名称
580
+ use_https_disguise: 是否使用HTTPS伪装
581
+ proxy_server: 中转服务器 (host, port),如 ('1.2.3.4', 443)
582
+ """
583
+ rcoder = RcoderCore(host=host, port=port, use_https_disguise=use_https_disguise, proxy_server=proxy_server)
584
+ return RemoteHost(rcoder, server=server)