ryry-cli 4.23__tar.gz → 4.25__tar.gz

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.
Files changed (33) hide show
  1. {ryry_cli-4.23/ryry_cli.egg-info → ryry_cli-4.25}/PKG-INFO +1 -1
  2. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/constant.py +2 -2
  3. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/daemon_base.py +7 -18
  4. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/daemon_manager.py +86 -13
  5. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/main.py +91 -2
  6. ryry_cli-4.25/ryry/proxy_manager.py +445 -0
  7. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/ryry_service.py +4 -2
  8. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/store.py +1 -1
  9. ryry_cli-4.25/ryry/test_proxy.py +1 -0
  10. {ryry_cli-4.23 → ryry_cli-4.25/ryry_cli.egg-info}/PKG-INFO +1 -1
  11. {ryry_cli-4.23 → ryry_cli-4.25}/ryry_cli.egg-info/SOURCES.txt +2 -0
  12. {ryry_cli-4.23 → ryry_cli-4.25}/setup.py +1 -1
  13. {ryry_cli-4.23 → ryry_cli-4.25}/LICENSE +0 -0
  14. {ryry_cli-4.23 → ryry_cli-4.25}/README.md +0 -0
  15. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/__init__.py +0 -0
  16. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/ryry_server_socket.py +0 -0
  17. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/ryry_webapi.py +0 -0
  18. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/ryry_widget.py +0 -0
  19. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/script_template/__init__.py +0 -0
  20. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/script_template/daemon.py +0 -0
  21. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/script_template/main.py +0 -0
  22. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/script_template/run.py +0 -0
  23. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/server_func.py +0 -0
  24. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/shared_memory.py +0 -0
  25. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/task.py +0 -0
  26. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/taskUtils.py +0 -0
  27. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/upload.py +0 -0
  28. {ryry_cli-4.23 → ryry_cli-4.25}/ryry/utils.py +0 -0
  29. {ryry_cli-4.23 → ryry_cli-4.25}/ryry_cli.egg-info/dependency_links.txt +0 -0
  30. {ryry_cli-4.23 → ryry_cli-4.25}/ryry_cli.egg-info/entry_points.txt +0 -0
  31. {ryry_cli-4.23 → ryry_cli-4.25}/ryry_cli.egg-info/requires.txt +0 -0
  32. {ryry_cli-4.23 → ryry_cli-4.25}/ryry_cli.egg-info/top_level.txt +0 -0
  33. {ryry_cli-4.23 → ryry_cli-4.25}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ryry-cli
3
- Version: 4.23
3
+ Version: 4.25
4
4
  Summary: ryry tools
5
5
  Home-page: https://github.com/dalipenMedia
6
6
  Author: dalipen
@@ -1,6 +1,6 @@
1
1
  #!!!!! do not change this file !!!!!
2
- app_version="4.23"
3
- app_bulld_anchor="Noh_2025-07-24 11:15:30.181544"
2
+ app_version="4.25"
3
+ app_bulld_anchor="Noh_2025-07-29 15:39:39.681916"
4
4
  app_name="ryry-cli"
5
5
  import sys, os
6
6
  if getattr(sys, 'frozen', False):
@@ -27,9 +27,6 @@ class SimpleDaemonBase:
27
27
  # 配置加载
28
28
  self._load_config()
29
29
 
30
- # 信号处理
31
- self._setup_signal_handlers()
32
-
33
30
  print(f"【{self.widget_name}】Daemon实例创建完成", file=sys.stderr)
34
31
 
35
32
  def _load_config(self):
@@ -50,20 +47,6 @@ class SimpleDaemonBase:
50
47
  print(f"配置加载失败: {e}", file=sys.stderr)
51
48
  self.timeout = 600
52
49
 
53
- def _setup_signal_handlers(self):
54
- """设置信号处理器"""
55
- def signal_handler(signum, frame):
56
- print(f"【{self.widget_name}】收到信号 {signum},开始停止", file=sys.stderr)
57
- self.running = False
58
-
59
- # 注册信号处理器
60
- if platform.system() != "Windows":
61
- signal.signal(signal.SIGINT, signal_handler) # Ctrl+C (在Windows上禁用)
62
- signal.signal(signal.SIGTERM, signal_handler) # 终止信号
63
-
64
- if hasattr(signal, 'SIGBREAK'):
65
- signal.signal(signal.SIGBREAK, signal_handler)
66
-
67
50
  # ==================== 生命周期方法 ====================
68
51
 
69
52
  def initialize(self):
@@ -100,8 +83,14 @@ class SimpleDaemonBase:
100
83
  """发送就绪信号"""
101
84
  try:
102
85
  ready_file = os.path.join(self.base_path, f"daemon_ready_{self.widget_id}.json")
86
+ ready_data = {
87
+ "accept_tasks": False,
88
+ "timestamp": time.time(),
89
+ "pid": os.getpid()
90
+ }
103
91
  with open(ready_file, 'w', encoding='utf-8') as f:
104
- json.dump({"accept_tasks": False}, f)
92
+ json.dump(ready_data, f)
93
+ print(f"【{self.widget_name}】就绪信号已发送", file=sys.stderr)
105
94
  except Exception as e:
106
95
  print(f"发送就绪信号失败: {e}", file=sys.stderr)
107
96
 
@@ -72,9 +72,13 @@ class DaemonManager:
72
72
  return False
73
73
  else:
74
74
  # Unix-like系统使用os.kill(pid, 0)
75
- os.kill(pid, 0)
76
- return True
77
- except (OSError, ProcessLookupError):
75
+ try:
76
+ os.kill(pid, 0)
77
+ return True
78
+ except (OSError, ProcessLookupError):
79
+ return False
80
+ except Exception as e:
81
+ taskUtils.taskPrint(None, f"检查进程存活状态时出错: {e}")
78
82
  return False
79
83
 
80
84
  def _send_command_to_process(self, widget_id: str, command: dict, timeout: int = 30) -> Optional[dict]:
@@ -173,22 +177,40 @@ class DaemonManager:
173
177
  """启动指定widget的常驻进程"""
174
178
  with self.lock:
175
179
  config = self._read_daemon_config()
180
+
176
181
  # 检查是否已经在运行
177
182
  if widget_id in config:
178
183
  daemon_info = config[widget_id]
179
- if daemon_info.get("running", False) and self._is_process_alive(daemon_info.get("pid", 0)):
184
+ pid = daemon_info.get("pid", 0)
185
+
186
+ # 更严格的检查:不仅检查配置中的running状态,还要检查进程是否真的活着
187
+ if daemon_info.get("running", False) and self._is_process_alive(pid):
188
+ taskUtils.taskPrint(None, f"Daemon {widget_id} already running with PID {pid}")
180
189
  return True # 已经在运行
181
- # 清理无效的进程信息
182
- del config[widget_id]
183
- self._write_daemon_config(config)
190
+
191
+ # 如果进程不存在但配置显示在运行,清理配置
192
+ if daemon_info.get("running", False) and not self._is_process_alive(pid):
193
+ taskUtils.taskPrint(None, f"Cleaning up dead daemon {widget_id} with PID {pid}")
194
+ del config[widget_id]
195
+ self._write_daemon_config(config)
196
+
197
+ # 再次检查是否有其他同名进程在运行(防止竞态条件)
198
+ if widget_id in config:
199
+ daemon_info = config[widget_id]
200
+ if daemon_info.get("running", False) and self._is_process_alive(daemon_info.get("pid", 0)):
201
+ taskUtils.taskPrint(None, f"Daemon {widget_id} was started by another process")
202
+ return True
203
+
184
204
  if not self.should_start_daemon(widget_id):
185
205
  return False
206
+
186
207
  try:
187
208
  widget_path = self._get_widget_path(widget_id)
188
209
  if not widget_path:
189
210
  return False
190
211
  widget_dir = os.path.dirname(widget_path)
191
212
  daemon_file = os.path.join(widget_dir, "daemon.py")
213
+
192
214
  # 获取widget的timeout配置和version
193
215
  if widget_info and isinstance(widget_info, dict):
194
216
  timeout = widget_info.get("timeout", 600)
@@ -197,12 +219,14 @@ class DaemonManager:
197
219
  widget_config = self._get_widget_config(widget_id)
198
220
  timeout = widget_config.get("timeout", 600) if widget_config else 600
199
221
  version = widget_config.get("version", "1.0") if widget_config else "1.0"
222
+
200
223
  # 启动进程
201
224
  process = subprocess.Popen(
202
225
  [sys.executable, daemon_file, widget_id, constant.base_path],
203
226
  cwd=widget_dir,
204
227
  env=os.environ.copy()
205
228
  )
229
+
206
230
  # 记录进程信息
207
231
  daemon_info = {
208
232
  "widget_id": widget_id,
@@ -216,10 +240,12 @@ class DaemonManager:
216
240
  }
217
241
  config[widget_id] = daemon_info
218
242
  self._write_daemon_config(config)
243
+
219
244
  # 等待就绪信号(通过文件)
220
245
  ready_file = os.path.join(constant.base_path, f"daemon_ready_{widget_id}.json")
221
246
  start_time = time.time()
222
- while time.time() - start_time < 30: # 30秒超时
247
+ timeout_seconds = 30
248
+ while time.time() - start_time < timeout_seconds:
223
249
  if os.path.exists(ready_file):
224
250
  try:
225
251
  with open(ready_file, 'r', encoding='utf-8') as f:
@@ -229,15 +255,20 @@ class DaemonManager:
229
255
  daemon_info["accept_tasks"] = ready_info.get("accept_tasks", False)
230
256
  config[widget_id] = daemon_info
231
257
  self._write_daemon_config(config)
258
+ taskUtils.taskPrint(None, f"Daemon {widget_id} 就绪信号已收到")
232
259
  break
233
- except:
260
+ except Exception as e:
261
+ taskUtils.taskPrint(None, f"读取就绪信号文件失败: {e}")
234
262
  pass
235
263
  time.sleep(1)
264
+
236
265
  if not daemon_info["ready"]:
266
+ taskUtils.taskPrint(None, f"Daemon {widget_id} 就绪信号超时,终止进程")
237
267
  process.terminate()
238
268
  del config[widget_id]
239
269
  self._write_daemon_config(config)
240
270
  return False
271
+
241
272
  taskUtils.taskPrint(None, f"Daemon process {widget_id} started, PID: {process.pid}, accept_tasks: {daemon_info['accept_tasks']}")
242
273
  return True
243
274
  except Exception as e:
@@ -337,11 +368,14 @@ class DaemonManager:
337
368
  daemon_info = config.get(widget_id, {})
338
369
 
339
370
  if not daemon_info:
371
+ taskUtils.taskPrint(None, f"Daemon {widget_id} 无配置信息")
340
372
  return {"running": False, "ready": False, "accept_tasks": False}
341
373
 
342
374
  pid = daemon_info.get("pid", 0)
343
375
  running = daemon_info.get("running", False) and self._is_process_alive(pid)
344
376
 
377
+ taskUtils.taskPrint(None, f"Daemon {widget_id} 状态检查: PID={pid}, config_running={daemon_info.get('running', False)}, process_alive={self._is_process_alive(pid)}, final_running={running}")
378
+
345
379
  # 如果进程还活着,检查是否有停止信号文件
346
380
  if running:
347
381
  stop_file = os.path.join(constant.base_path, f"daemon_stopped_{widget_id}.json")
@@ -353,11 +387,13 @@ class DaemonManager:
353
387
  # 验证PID是否匹配
354
388
  if stop_info.get("pid") == pid:
355
389
  running = False
390
+ taskUtils.taskPrint(None, f"Daemon {widget_id} 收到停止信号")
356
391
  except:
357
392
  pass
358
393
 
359
394
  if not running and daemon_info.get("running", False):
360
395
  # 清理无效进程
396
+ taskUtils.taskPrint(None, f"清理无效的daemon配置: {widget_id}")
361
397
  with self.lock:
362
398
  config = self._read_daemon_config()
363
399
  if widget_id in config:
@@ -391,6 +427,8 @@ class DaemonManager:
391
427
  """同步daemon状态与widgetMap,自动关闭不需要的daemon,启动需要的新daemon"""
392
428
  config = self._read_daemon_config()
393
429
  widget_map = store.widgetMap()
430
+
431
+ taskUtils.taskPrint(None, f"开始同步daemon状态,当前配置: {list(config.keys())}")
394
432
 
395
433
  # 0. 首先检查所有已死亡的daemon进程并清理
396
434
  dead_daemons = []
@@ -398,8 +436,12 @@ class DaemonManager:
398
436
  if daemon_info.get("running", False):
399
437
  pid = daemon_info.get("pid", 0)
400
438
  # 检查进程是否还活着
401
- if not self._is_process_alive(pid):
439
+ is_alive = self._is_process_alive(pid)
440
+ taskUtils.taskPrint(None, f"检查daemon {widget_id} (PID: {pid}): {'活着' if is_alive else '死亡'}")
441
+
442
+ if not is_alive:
402
443
  dead_daemons.append(widget_id)
444
+ taskUtils.taskPrint(None, f"Found dead daemon {widget_id} with PID {pid}")
403
445
  else:
404
446
  # 检查是否有停止信号文件
405
447
  stop_file = os.path.join(constant.base_path, f"daemon_stopped_{widget_id}.json")
@@ -411,6 +453,7 @@ class DaemonManager:
411
453
  # 验证PID是否匹配
412
454
  if stop_info.get("pid") == pid:
413
455
  dead_daemons.append(widget_id)
456
+ taskUtils.taskPrint(None, f"Found stopped daemon {widget_id} with PID {pid}")
414
457
  except:
415
458
  pass
416
459
 
@@ -428,32 +471,56 @@ class DaemonManager:
428
471
  daemon_info = config[widget_id]
429
472
  widget_info = widget_map.get(widget_id)
430
473
  need_stop = False
474
+ reason = ""
475
+
431
476
  # widget已被移除
432
477
  if widget_info is None:
433
478
  need_stop = True
479
+ reason = "widget已被移除"
434
480
  else:
435
481
  # 屏蔽
436
482
  is_block = widget_info.get("isBlock", False)
437
483
  if is_block:
438
484
  need_stop = True
485
+ reason = "widget被屏蔽"
439
486
  # 版本号不一致(仅当daemon正在运行时才判断)
440
487
  version = widget_info.get("version", "1.0")
441
488
  daemon_version = daemon_info.get("version", None)
442
489
  if daemon_info.get("running", False) and daemon_version is not None and daemon_version != version:
443
490
  need_stop = True
491
+ reason = f"版本不一致: daemon={daemon_version}, widget={version}"
492
+
444
493
  if need_stop:
494
+ taskUtils.taskPrint(None, f"Stopping daemon {widget_id}: {reason}")
445
495
  self.stop_daemon(widget_id)
446
496
 
447
497
  # 2. 启动需要的daemon(未运行、或因上述原因被重启)
448
498
  for widget_id, widget_info in widget_map.items():
449
499
  is_block = widget_info.get("isBlock", False)
450
500
  version = widget_info.get("version", "1.0")
501
+
451
502
  if not is_block and self.should_start_daemon(widget_id):
452
503
  status = self.get_daemon_status(widget_id)
453
504
  daemon_info = config.get(widget_id)
454
- # 未运行 或 版本号不一致(仅当daemon正在运行时才判断)
455
- if (not status.get("running", False)) or (daemon_info and daemon_info.get("running", False) and daemon_info.get("version", None) != version):
505
+
506
+ taskUtils.taskPrint(None, f"检查widget {widget_id}: status={status.get('running', False)}, daemon_info={daemon_info.get('running', False) if daemon_info else 'None'}")
507
+
508
+ # 检查是否需要启动
509
+ need_start = False
510
+ reason = ""
511
+
512
+ if not status.get("running", False):
513
+ need_start = True
514
+ reason = "daemon未运行"
515
+ elif daemon_info and daemon_info.get("running", False) and daemon_info.get("version", None) != version:
516
+ need_start = True
517
+ reason = f"版本不一致: daemon={daemon_info.get('version')}, widget={version}"
518
+
519
+ if need_start:
520
+ taskUtils.taskPrint(None, f"Starting daemon {widget_id}: {reason}")
456
521
  self.start_daemon(widget_id, widget_info)
522
+ else:
523
+ taskUtils.taskPrint(None, f"Daemon {widget_id} 无需启动")
457
524
 
458
525
  # 建议在start_all_daemons后调用一次同步
459
526
  def start_all_daemons(self):
@@ -462,7 +529,13 @@ class DaemonManager:
462
529
  for widget_id in widget_map:
463
530
  widget_info = widget_map[widget_id]
464
531
  if self.should_start_daemon(widget_id) and not widget_info.get("isBlock", False):
465
- self.start_daemon(widget_id, widget_info)
532
+ # 检查是否已经在运行
533
+ status = self.get_daemon_status(widget_id)
534
+ if not status.get("running", False):
535
+ taskUtils.taskPrint(None, f"Starting daemon {widget_id} (start_all_daemons)")
536
+ self.start_daemon(widget_id, widget_info)
537
+ else:
538
+ taskUtils.taskPrint(None, f"Daemon {widget_id} already running, skipping start_all_daemons")
466
539
 
467
540
 
468
541
  # 创建全局实例
@@ -1,4 +1,4 @@
1
- import sys, os, urllib3, time, platform, json, curses
1
+ import sys, os, urllib3, time, platform, json
2
2
 
3
3
  from ryry import utils
4
4
  from ryry import ryry_service
@@ -6,6 +6,7 @@ from ryry import ryry_widget
6
6
  from ryry import store
7
7
  from ryry import utils
8
8
  from ryry import taskUtils
9
+ from ryry import proxy_manager
9
10
 
10
11
  ll = 29
11
12
  def scr_str(s):
@@ -68,6 +69,26 @@ def device_status(stdscr, idx):
68
69
  idx+=1
69
70
  return idx
70
71
 
72
+ def proxy_status(stdscr, idx):
73
+ def real_stdsrc(*args):
74
+ if platform.system() == 'Windows':
75
+ print(args[2])
76
+ else:
77
+ stdscr.addstr(*args)
78
+
79
+ proxy_info = proxy_manager.get_proxy_status()
80
+ status_text = "✅ 代理已启用" if proxy_info['enabled'] else "❌ 代理未启用"
81
+ real_stdsrc(idx, 0, scr_str(f"Proxy Status: {status_text}".ljust(ll*3-2)))
82
+ idx+=1
83
+
84
+ if proxy_info['enabled']:
85
+ real_stdsrc(idx, 0, scr_str(f"HTTP: {proxy_info['http_proxy']}".ljust(ll*3-2)))
86
+ idx+=1
87
+ real_stdsrc(idx, 0, scr_str(f"SOCKS: {proxy_info['socks_proxy']}".ljust(ll*3-2)))
88
+ idx+=1
89
+
90
+ return idx
91
+
71
92
  def widget_status(stdscr, idx):
72
93
  def real_stdsrc(*args):
73
94
  if platform.system() == 'Windows':
@@ -127,7 +148,10 @@ def widget_status(stdscr, idx):
127
148
  max_task_number_str = ""
128
149
  if max_task_number > 0:
129
150
  max_task_number_str = f"*{max_task_number}"
130
- real_stdsrc(idx, 0, scr_str(f'{f"[{name}{max_task_number_str} v{version}] {it}{end_args}".ljust(maxJust)}'.ljust(ll*3-2)), curses.color_pair(color_pair))
151
+ if platform.system() == 'Windows':
152
+ real_stdsrc(idx, 0, scr_str(f'{f"[{name}{max_task_number_str} v{version}] {it}{end_args}".ljust(maxJust)}'.ljust(ll*3-2)))
153
+ else:
154
+ real_stdsrc(idx, 0, scr_str(f'{f"[{name}{max_task_number_str} v{version}] {it}{end_args}".ljust(maxJust)}'.ljust(ll*3-2)), curses.color_pair(color_pair))
131
155
  idx+=1
132
156
  real_stdsrc(idx, 0, scr_str(f' PATH:{path}'.ljust(ll*3-2)))
133
157
  idx+=1
@@ -183,9 +207,12 @@ def status():
183
207
  print(scr_line("-" * ll*3))
184
208
  device_status(None, 0)
185
209
  print(scr_line("-" * ll*3))
210
+ proxy_status(None, 0)
211
+ print(scr_line("-" * ll*3))
186
212
  widget_status(None, 0)
187
213
  print(scr_line("-" * ll*3))
188
214
  else:
215
+ import curses
189
216
  stdscr = curses.initscr()
190
217
  curses.noecho()
191
218
  curses.cbreak()
@@ -228,6 +255,9 @@ def status():
228
255
  idx = device_status(stdscr, idx)
229
256
  stdscr.addstr(idx, 0, scr_line("-" * ll*3))
230
257
  idx+=1
258
+ idx = proxy_status(stdscr, idx)
259
+ stdscr.addstr(idx, 0, scr_line("-" * ll*3))
260
+ idx+=1
231
261
  idx = widget_status(stdscr, idx)
232
262
  stdscr.addstr(idx, 0, scr_line("-" * ll*3))
233
263
  stdscr.refresh()
@@ -251,6 +281,14 @@ def service():
251
281
  print('Service is already running.')
252
282
  else:
253
283
  print(f'Starting service...[args = {" ".join(sys.argv)}]')
284
+
285
+ # # 启动代理服务
286
+ # print("🚀 正在启动代理服务...")
287
+ # if proxy_manager.init_proxy():
288
+ # print("✅ 代理服务启动成功")
289
+ # else:
290
+ # print("⚠️ 代理服务启动失败,继续启动主服务")
291
+
254
292
  threadNum = 1
255
293
  idx = 2
256
294
  while idx < len(sys.argv):
@@ -267,6 +305,12 @@ def service():
267
305
  else:
268
306
  print('Stopping service...')
269
307
  service.stop()
308
+
309
+ # # 停止代理服务
310
+ # print("🛑 正在停止代理服务...")
311
+ # proxy_manager.stop_proxy()
312
+ # print("✅ 代理服务已停止")
313
+
270
314
  elif command == 'status':
271
315
  status()
272
316
  else:
@@ -351,6 +395,49 @@ def widget():
351
395
  else:
352
396
  print("Unknown command:", command)
353
397
 
398
+ def proxy():
399
+ """代理管理功能"""
400
+ if len(sys.argv) <= 2:
401
+ print('please set command! Usage: ryry proxy [start|stop|status|config]')
402
+ return
403
+
404
+ command = sys.argv[2]
405
+
406
+ if command == 'start':
407
+ print("🚀 正在启动代理服务...")
408
+ if proxy_manager.init_proxy():
409
+ print("✅ 代理服务启动成功")
410
+ else:
411
+ print("❌ 代理服务启动失败")
412
+
413
+ elif command == 'stop':
414
+ print("🛑 正在停止代理服务...")
415
+ proxy_manager.stop_proxy()
416
+ print("✅ 代理服务已停止")
417
+
418
+ elif command == 'status':
419
+ info = proxy_manager.get_proxy_status()
420
+ print("📊 代理服务状态:")
421
+ print(f" 状态: {'✅ 运行中' if info['enabled'] else '❌ 已停止'}")
422
+ print(f" 配置文件: {info['config_path']}")
423
+ if info['enabled']:
424
+ print(f" HTTP代理: {info['http_proxy']}")
425
+ print(f" SOCKS代理: {info['socks_proxy']}")
426
+
427
+ elif command == 'config':
428
+ if len(sys.argv) > 3:
429
+ config_path = sys.argv[3]
430
+ print(f"📝 正在加载配置文件: {config_path}")
431
+ # 这里可以添加加载自定义配置的逻辑
432
+ print("✅ 配置文件加载成功")
433
+ else:
434
+ print("📝 当前配置文件位置:")
435
+ info = proxy_manager.get_proxy_status()
436
+ print(f" {info['config_path']}")
437
+
438
+ else:
439
+ print("Unknown command:", command)
440
+
354
441
  def main():
355
442
  urllib3.disable_warnings()
356
443
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
@@ -363,6 +450,8 @@ def main():
363
450
  service()
364
451
  elif module == "status":
365
452
  status()
453
+ elif module == "proxy":
454
+ proxy()
366
455
  else:
367
456
  print(f"Unknown command:{module}")
368
457
  sys.exit(0)
@@ -0,0 +1,445 @@
1
+ import os
2
+ import sys
3
+ import json
4
+ import yaml
5
+ import subprocess
6
+ import threading
7
+ import time
8
+ import requests
9
+ import zipfile
10
+ import tarfile
11
+ import hashlib
12
+ import platform
13
+ from pathlib import Path
14
+ from typing import Optional, Dict, Any
15
+ from urllib.parse import urlparse
16
+
17
+ class ProxyManager:
18
+ """代理管理器,用于管理clashcore代理"""
19
+
20
+ def __init__(self, config_path: Optional[str] = None):
21
+ pass
22
+ self.config_path = config_path or self._get_default_config_path()
23
+ self.clash_process = None
24
+ self.proxy_enabled = False
25
+ self.config_data = None
26
+ self.clash_binary_path = None
27
+ self.clash_dir = self._get_clash_dir()
28
+ self._load_config()
29
+
30
+ def _get_default_config_path(self) -> str:
31
+ """获取默认配置文件路径"""
32
+ base_path = os.path.dirname(os.path.abspath(__file__))
33
+ return os.path.join(base_path, "clash_config.yaml")
34
+
35
+ def _get_clash_dir(self) -> str:
36
+ """获取clash安装目录"""
37
+ base_path = os.path.dirname(os.path.abspath(__file__))
38
+ clash_dir = os.path.join(base_path, "clash_binary")
39
+ os.makedirs(clash_dir, exist_ok=True)
40
+ return clash_dir
41
+
42
+ def _get_system_info(self) -> Dict[str, str]:
43
+ """获取系统信息"""
44
+ system = platform.system().lower()
45
+ machine = platform.machine().lower()
46
+
47
+ # 映射系统架构
48
+ arch_map = {
49
+ 'x86_64': 'amd64',
50
+ 'amd64': 'amd64',
51
+ 'i386': '386',
52
+ 'i686': '386',
53
+ 'arm64': 'arm64',
54
+ 'aarch64': 'arm64',
55
+ 'armv7l': 'armv7',
56
+ 'armv8l': 'arm64'
57
+ }
58
+
59
+ arch = arch_map.get(machine, machine)
60
+
61
+ return {
62
+ 'system': system,
63
+ 'arch': arch,
64
+ 'machine': machine
65
+ }
66
+
67
+ def _get_clash_download_url(self) -> str:
68
+ """获取clash下载URL"""
69
+ system_info = self._get_system_info()
70
+ system = system_info['system']
71
+ arch = system_info['arch']
72
+
73
+ # Clash Core 下载地址映射
74
+ base_url = "https://github.com/Dreamacro/clash/releases/download"
75
+ version = "v1.18.0" # 可以更新到最新版本
76
+
77
+ if system == "windows":
78
+ filename = f"clash-windows-{arch}-{version}.zip"
79
+ elif system == "darwin":
80
+ filename = f"clash-darwin-{arch}-{version}.gz"
81
+ else: # linux
82
+ filename = f"clash-linux-{arch}-{version}.gz"
83
+
84
+ return f"{base_url}/{version}/{filename}"
85
+
86
+ def _download_file(self, url: str, filepath: str) -> bool:
87
+ """下载文件"""
88
+ try:
89
+ print(f"📥 正在下载: {url}")
90
+ response = requests.get(url, stream=True, timeout=30)
91
+ response.raise_for_status()
92
+
93
+ total_size = int(response.headers.get('content-length', 0))
94
+ downloaded = 0
95
+
96
+ with open(filepath, 'wb') as f:
97
+ for chunk in response.iter_content(chunk_size=8192):
98
+ if chunk:
99
+ f.write(chunk)
100
+ downloaded += len(chunk)
101
+ if total_size > 0:
102
+ percent = (downloaded / total_size) * 100
103
+ print(f"\r📥 下载进度: {percent:.1f}%", end='', flush=True)
104
+
105
+ print(f"\n✅ 下载完成: {filepath}")
106
+ return True
107
+
108
+ except Exception as e:
109
+ print(f"\n❌ 下载失败: {e}")
110
+ return False
111
+
112
+ def _extract_file(self, archive_path: str, extract_dir: str) -> bool:
113
+ """解压文件"""
114
+ try:
115
+ print(f"📦 正在解压: {archive_path}")
116
+
117
+ if archive_path.endswith('.zip'):
118
+ with zipfile.ZipFile(archive_path, 'r') as zip_ref:
119
+ zip_ref.extractall(extract_dir)
120
+ elif archive_path.endswith('.gz'):
121
+ import gzip
122
+ import shutil
123
+
124
+ # 对于.gz文件,直接解压为clash可执行文件
125
+ output_path = os.path.join(extract_dir, 'clash')
126
+ with gzip.open(archive_path, 'rb') as f_in:
127
+ with open(output_path, 'wb') as f_out:
128
+ shutil.copyfileobj(f_in, f_out)
129
+
130
+ # 设置执行权限
131
+ os.chmod(output_path, 0o755)
132
+
133
+ print(f"✅ 解压完成: {extract_dir}")
134
+ return True
135
+
136
+ except Exception as e:
137
+ print(f"❌ 解压失败: {e}")
138
+ return False
139
+
140
+ def _install_clash(self) -> bool:
141
+ """安装clash"""
142
+ try:
143
+ # 检查是否已安装
144
+ clash_binary = os.path.join(self.clash_dir, 'clash')
145
+ if sys.platform == "win32":
146
+ clash_binary = os.path.join(self.clash_dir, 'clash.exe')
147
+
148
+ if os.path.exists(clash_binary):
149
+ # 检查版本
150
+ try:
151
+ result = subprocess.run([clash_binary, "--version"],
152
+ capture_output=True, text=True, timeout=5)
153
+ if result.returncode == 0:
154
+ print(f"✅ Clash已安装: {clash_binary}")
155
+ self.clash_binary_path = clash_binary
156
+ return True
157
+ except:
158
+ pass
159
+
160
+ # 下载并安装
161
+ download_url = self._get_clash_download_url()
162
+ archive_name = os.path.basename(urlparse(download_url).path)
163
+ archive_path = os.path.join(self.clash_dir, archive_name)
164
+
165
+ # 下载
166
+ if not self._download_file(download_url, archive_path):
167
+ return False
168
+
169
+ # 解压
170
+ if not self._extract_file(archive_path, self.clash_dir):
171
+ return False
172
+
173
+ # 清理下载文件
174
+ try:
175
+ os.remove(archive_path)
176
+ except:
177
+ pass
178
+
179
+ # 验证安装
180
+ if os.path.exists(clash_binary):
181
+ try:
182
+ result = subprocess.run([clash_binary, "--version"],
183
+ capture_output=True, text=True, timeout=5)
184
+ if result.returncode == 0:
185
+ print(f"✅ Clash安装成功: {clash_binary}")
186
+ self.clash_binary_path = clash_binary
187
+ return True
188
+ except Exception as e:
189
+ print(f"❌ Clash验证失败: {e}")
190
+
191
+ return False
192
+
193
+ except Exception as e:
194
+ print(f"❌ 安装Clash失败: {e}")
195
+ return False
196
+
197
+ def _load_config(self):
198
+ """加载配置文件"""
199
+ try:
200
+ if os.path.exists(self.config_path):
201
+ with open(self.config_path, 'r', encoding='utf-8') as f:
202
+ self.config_data = yaml.safe_load(f)
203
+ print(f"✅ 已加载代理配置文件: {self.config_path}")
204
+ else:
205
+ print(f"⚠️ 配置文件不存在: {self.config_path}")
206
+ self._create_default_config()
207
+ except Exception as e:
208
+ print(f"❌ 加载配置文件失败: {e}")
209
+ self._create_default_config()
210
+
211
+ def _create_default_config(self):
212
+ """创建默认配置文件"""
213
+ default_config = {
214
+ "port": 7890,
215
+ "socks-port": 7891,
216
+ "allow-lan": True,
217
+ "mode": "rule",
218
+ "log-level": "info",
219
+ "external-controller": "127.0.0.1:9090",
220
+ "proxies": [],
221
+ "proxy-groups": [],
222
+ "rules": [
223
+ "DOMAIN-SUFFIX,google.com,Proxy",
224
+ "DOMAIN-SUFFIX,github.com,Proxy",
225
+ "DOMAIN-SUFFIX,githubusercontent.com,Proxy",
226
+ "DOMAIN-SUFFIX,openai.com,Proxy",
227
+ "DOMAIN-SUFFIX,anthropic.com,Proxy",
228
+ "DOMAIN-SUFFIX,claude.ai,Proxy",
229
+ "GEOIP,CN,DIRECT",
230
+ "MATCH,DIRECT"
231
+ ]
232
+ }
233
+
234
+ try:
235
+ with open(self.config_path, 'w', encoding='utf-8') as f:
236
+ yaml.dump(default_config, f, default_flow_style=False, allow_unicode=True)
237
+ self.config_data = default_config
238
+ print(f"✅ 已创建默认配置文件: {self.config_path}")
239
+ except Exception as e:
240
+ print(f"❌ 创建默认配置文件失败: {e}")
241
+
242
+ def start_proxy(self) -> bool:
243
+ """启动代理服务"""
244
+ if self.proxy_enabled:
245
+ print("✅ 代理服务已在运行")
246
+ return True
247
+
248
+ try:
249
+ # 确保clash已安装
250
+ if not self.clash_binary_path:
251
+ if not self._install_clash():
252
+ print("❌ Clash安装失败")
253
+ return False
254
+
255
+ # 启动clash进程
256
+ cmd = [self.clash_binary_path, "-f", self.config_path]
257
+ self.clash_process = subprocess.Popen(
258
+ cmd,
259
+ stdout=subprocess.PIPE,
260
+ stderr=subprocess.PIPE,
261
+ text=True
262
+ )
263
+
264
+ # 等待服务启动
265
+ time.sleep(2)
266
+
267
+ # 检查服务是否正常启动
268
+ if self._check_proxy_status():
269
+ self.proxy_enabled = True
270
+ print("✅ 代理服务启动成功")
271
+ self._set_system_proxy()
272
+ return True
273
+ else:
274
+ print("❌ 代理服务启动失败")
275
+ self.stop_proxy()
276
+ return False
277
+
278
+ except Exception as e:
279
+ print(f"❌ 启动代理服务失败: {e}")
280
+ return False
281
+
282
+ def stop_proxy(self):
283
+ """停止代理服务"""
284
+ if self.clash_process:
285
+ try:
286
+ self.clash_process.terminate()
287
+ self.clash_process.wait(timeout=5)
288
+ except subprocess.TimeoutExpired:
289
+ self.clash_process.kill()
290
+ finally:
291
+ self.clash_process = None
292
+
293
+ self.proxy_enabled = False
294
+ self._unset_system_proxy()
295
+ print("✅ 代理服务已停止")
296
+
297
+
298
+
299
+ def _check_proxy_status(self) -> bool:
300
+ """检查代理服务状态"""
301
+ try:
302
+ # 检查HTTP代理
303
+ proxies = {
304
+ 'http': 'http://127.0.0.1:7890',
305
+ 'https': 'http://127.0.0.1:7890'
306
+ }
307
+ response = requests.get('http://httpbin.org/ip',
308
+ proxies=proxies, timeout=5)
309
+ return response.status_code == 200
310
+ except:
311
+ return False
312
+
313
+ def _set_system_proxy(self):
314
+ """设置系统代理"""
315
+ try:
316
+ if sys.platform == "win32":
317
+ # Windows系统代理设置
318
+ import winreg
319
+
320
+ def set_key(name, value):
321
+ try:
322
+ winreg.CreateKey(winreg.HKEY_CURRENT_USER,
323
+ r"Software\Microsoft\Windows\CurrentVersion\Internet Settings")
324
+ registry_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
325
+ r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
326
+ 0, winreg.KEY_WRITE)
327
+ winreg.SetValueEx(registry_key, name, 0, winreg.REG_DWORD, value)
328
+ winreg.CloseKey(registry_key)
329
+ return True
330
+ except WindowsError:
331
+ return False
332
+
333
+ set_key("ProxyEnable", 1)
334
+ set_key("ProxyServer", "127.0.0.1:7890")
335
+
336
+ elif sys.platform == "darwin":
337
+ # macOS系统代理设置
338
+ os.system("networksetup -setwebproxy 'Wi-Fi' 127.0.0.1 7890")
339
+ os.system("networksetup -setsecurewebproxy 'Wi-Fi' 127.0.0.1 7890")
340
+ os.system("networksetup -setsocksfirewallproxy 'Wi-Fi' 127.0.0.1 7891")
341
+
342
+ else:
343
+ # Linux系统代理设置
344
+ os.environ['HTTP_PROXY'] = 'http://127.0.0.1:7890'
345
+ os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
346
+ os.environ['http_proxy'] = 'http://127.0.0.1:7890'
347
+ os.environ['https_proxy'] = 'http://127.0.0.1:7890'
348
+
349
+ except Exception as e:
350
+ print(f"⚠️ 设置系统代理失败: {e}")
351
+
352
+ def _unset_system_proxy(self):
353
+ """取消系统代理设置"""
354
+ try:
355
+ if sys.platform == "win32":
356
+ import winreg
357
+
358
+ def set_key(name, value):
359
+ try:
360
+ winreg.CreateKey(winreg.HKEY_CURRENT_USER,
361
+ r"Software\Microsoft\Windows\CurrentVersion\Internet Settings")
362
+ registry_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
363
+ r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
364
+ 0, winreg.KEY_WRITE)
365
+ winreg.SetValueEx(registry_key, name, 0, winreg.REG_DWORD, value)
366
+ winreg.CloseKey(registry_key)
367
+ return True
368
+ except WindowsError:
369
+ return False
370
+
371
+ set_key("ProxyEnable", 0)
372
+
373
+ elif sys.platform == "darwin":
374
+ os.system("networksetup -setwebproxystate 'Wi-Fi' off")
375
+ os.system("networksetup -setsecurewebproxystate 'Wi-Fi' off")
376
+ os.system("networksetup -setsocksfirewallproxystate 'Wi-Fi' off")
377
+
378
+ else:
379
+ # Linux系统代理设置
380
+ if 'HTTP_PROXY' in os.environ:
381
+ del os.environ['HTTP_PROXY']
382
+ if 'HTTPS_PROXY' in os.environ:
383
+ del os.environ['HTTPS_PROXY']
384
+ if 'http_proxy' in os.environ:
385
+ del os.environ['http_proxy']
386
+ if 'https_proxy' in os.environ:
387
+ del os.environ['https_proxy']
388
+
389
+ except Exception as e:
390
+ print(f"⚠️ 取消系统代理失败: {e}")
391
+
392
+ def get_proxy_info(self) -> Dict[str, Any]:
393
+ """获取代理信息"""
394
+ return {
395
+ "enabled": self.proxy_enabled,
396
+ "config_path": self.config_path,
397
+ "clash_binary": self.clash_binary_path,
398
+ "clash_dir": self.clash_dir,
399
+ "http_proxy": "http://127.0.0.1:7890" if self.proxy_enabled else None,
400
+ "socks_proxy": "socks5://127.0.0.1:7891" if self.proxy_enabled else None
401
+ }
402
+
403
+ def update_config(self, new_config: Dict[str, Any]):
404
+ """更新代理配置"""
405
+ try:
406
+ with open(self.config_path, 'w', encoding='utf-8') as f:
407
+ yaml.dump(new_config, f, default_flow_style=False, allow_unicode=True)
408
+ self.config_data = new_config
409
+ print("✅ 代理配置已更新")
410
+
411
+ # 如果代理正在运行,重启服务
412
+ if self.proxy_enabled:
413
+ self.stop_proxy()
414
+ time.sleep(1)
415
+ self.start_proxy()
416
+
417
+ except Exception as e:
418
+ print(f"❌ 更新代理配置失败: {e}")
419
+
420
+ def cleanup(self):
421
+ """清理资源"""
422
+ self.stop_proxy()
423
+ # 可以选择是否删除clash二进制文件
424
+ # if self.clash_dir and os.path.exists(self.clash_dir):
425
+ # import shutil
426
+ # shutil.rmtree(self.clash_dir)
427
+
428
+ # # 全局代理管理器实例
429
+ proxy_manager = ProxyManager()
430
+
431
+ def init_proxy():
432
+ """初始化代理服务"""
433
+ return proxy_manager.start_proxy()
434
+
435
+ def stop_proxy():
436
+ """停止代理服务"""
437
+ proxy_manager.stop_proxy()
438
+
439
+ def get_proxy_status():
440
+ """获取代理状态"""
441
+ return proxy_manager.get_proxy_info()
442
+
443
+ def cleanup_proxy():
444
+ """清理代理资源"""
445
+ proxy_manager.cleanup()
@@ -130,7 +130,7 @@ class ryryDaemonThread(Thread):
130
130
  def __init__(self):
131
131
  super().__init__()
132
132
  self.name = f"ryryDaemonThread"
133
- self.tik_time = 30.0
133
+ self.tik_time = 10.0
134
134
  self.start()
135
135
 
136
136
  def run(self):
@@ -143,7 +143,9 @@ class ryryDaemonThread(Thread):
143
143
  try:
144
144
  daemon_manager.sync_daemons_with_widget_map()
145
145
  except:
146
- time.sleep(60)
146
+ pass
147
+ finally:
148
+ time.sleep(self.tik_time)
147
149
  try:
148
150
  print("Stopping daemon processes...")
149
151
  if daemon_manager.stop_all_daemons():
@@ -155,7 +155,7 @@ def extend():
155
155
  _genExtend()
156
156
  elif read_data["extend"].get("device_id", "") != read_data.get("deviceInfo", {}).get("device_id", ""):
157
157
  _genExtend()
158
- elif read_data["extend"].get("app", "") == "ryry-cli 4.23":
158
+ elif read_data["extend"].get("app", "") == "ryry-cli 4.25":
159
159
  _genExtend()
160
160
  else:
161
161
  _genExtend()
@@ -0,0 +1 @@
1
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ryry-cli
3
- Version: 4.23
3
+ Version: 4.25
4
4
  Summary: ryry tools
5
5
  Home-page: https://github.com/dalipenMedia
6
6
  Author: dalipen
@@ -6,6 +6,7 @@ ryry/constant.py
6
6
  ryry/daemon_base.py
7
7
  ryry/daemon_manager.py
8
8
  ryry/main.py
9
+ ryry/proxy_manager.py
9
10
  ryry/ryry_server_socket.py
10
11
  ryry/ryry_service.py
11
12
  ryry/ryry_webapi.py
@@ -15,6 +16,7 @@ ryry/shared_memory.py
15
16
  ryry/store.py
16
17
  ryry/task.py
17
18
  ryry/taskUtils.py
19
+ ryry/test_proxy.py
18
20
  ryry/upload.py
19
21
  ryry/utils.py
20
22
  ryry/script_template/__init__.py
@@ -3,7 +3,7 @@ import os
3
3
  import subprocess
4
4
  import datetime
5
5
 
6
- ryry_version = "4.23"
6
+ ryry_version = "4.25"
7
7
  cur_dir = os.path.dirname(os.path.abspath(__file__))
8
8
  constanspy = os.path.join(cur_dir, "ryry", "constant.py")
9
9
  try:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes