Bl4ckSpaces commited on
Commit
1a051f1
Β·
verified Β·
1 Parent(s): 9f3a0c6

Update app_relay2.py

Browse files
Files changed (1) hide show
  1. app_relay2.py +94 -63
app_relay2.py CHANGED
@@ -1,12 +1,10 @@
1
  # ═══════════════════════════════════════════════════════════════════════════════
2
- # πŸ§… SODA-GEN ANONYMOUS RELAY V1.3 β€” HF SPACE CPU (BACKEND ONLY)
3
  #
4
- # MODE: Anonymous + Tor IP Rotation
5
- # QUOTA: 120s per Tor exit node (auto-rotate on exhaustion)
6
- # TARGET: https://r3gm-wan2-2-fp8da-aoti-preview-2.hf.space
7
- # PLATFORM: Hugging Face Spaces (Docker / CPU Basic)
8
- #
9
- # V1.3 FIX: socks5h:// β†’ socks5:// (httpx compatibility)
10
  # ═══════════════════════════════════════════════════════════════════════════════
11
 
12
  import json
@@ -35,12 +33,7 @@ import threading
35
  # ═══════════════════════════════════════════════════════════════════════════════
36
 
37
  TARGET_URL = "https://r3gm-wan2-2-fp8da-aoti-preview-2.hf.space"
38
-
39
- # ═══════════════════════════════════════════════════════════
40
- # πŸ”§ FIX V1.3: socks5h β†’ socks5 (httpx only supports socks5://)
41
- # ═══════════════════════════════════════════════════════════
42
  TOR_PROXY = "socks5://127.0.0.1:9050"
43
-
44
  PREDICT_TIMEOUT = 300
45
  MAX_TOR_RETRIES = 5
46
  GPU_RETRY_DELAY = 15
@@ -59,12 +52,14 @@ UPLOAD_DIR.mkdir(exist_ok=True)
59
  TASKS: Dict[str, Dict[str, Any]] = {}
60
 
61
  _last_tor_ip = {"ip": "unknown", "time": 0}
 
62
 
63
  # ═══════════════════════════════════════════════════════════════════════════════
64
  # πŸ§… TOR ENGINE
65
  # ═══════════════════════════════════════════════════════════════════════════════
66
 
67
  def ensure_tor_running():
 
68
  try:
69
  result = subprocess.run(
70
  ["service", "tor", "status"],
@@ -75,11 +70,15 @@ def ensure_tor_running():
75
  time.sleep(3)
76
  print("πŸ§… Tor started")
77
  except:
78
- subprocess.run(["service", "tor", "start"], capture_output=True, timeout=10)
79
- time.sleep(3)
 
 
 
80
 
81
 
82
  def get_tor_ip() -> str:
 
83
  now = time.time()
84
  if now - _last_tor_ip["time"] < 10 and _last_tor_ip["ip"] != "unknown":
85
  return _last_tor_ip["ip"]
@@ -96,6 +95,7 @@ def get_tor_ip() -> str:
96
 
97
 
98
  def rotate_tor_circuit() -> str:
 
99
  try:
100
  with Controller.from_port(port=9051) as controller:
101
  controller.authenticate()
@@ -123,18 +123,40 @@ def rotate_tor_circuit() -> str:
123
  return "rotation_failed"
124
 
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  # ═══════════════════════════════════════════════════════════════════════════════
127
  # πŸ“‘ GRADIO CLIENT WITH PROXY
128
  # ════════════════════════════════���══════════════════════════════════════════════
129
 
130
  class GradioClientWithProxy:
131
- """
132
- Wrapper gradio_client yang route traffic via Tor SOCKS5 proxy.
133
-
134
- V1.3: Menggunakan socks5:// (bukan socks5h://) agar compatible dengan httpx.
135
- httpx internal gradio_client hanya recognize socks5:// scheme.
136
- """
137
-
138
  def __init__(self, base_url: str, proxy: str = None):
139
  self.base_url = base_url
140
  self.proxy = proxy
@@ -157,7 +179,7 @@ class GradioClientWithProxy:
157
 
158
  self.client = Client(
159
  self.base_url,
160
- headers={"User-Agent": "SodaGen-Anonymous/1.3"}
161
  )
162
 
163
  def submit(self, **kwargs):
@@ -269,7 +291,7 @@ def resize_image_for_video(image_path, width=768, height=512):
269
 
270
 
271
  # ═══════════════════════════════════════════════════════════════════════════════
272
- # πŸš€ GENERATION TASK (ANONYMOUS MODE)
273
  # ═══════════════════════════════════════════════════════════════════════════════
274
 
275
  def run_generation_task(
@@ -281,27 +303,26 @@ def run_generation_task(
281
  steps: float,
282
  frame_mult: int,
283
  ):
 
284
  task = TASKS[task_id]
285
 
286
  try:
287
  task["status"] = "processing"
288
  task["log"] += f"🎬 [ANONYMOUS MODE] Prompt: {prompt[:80]}...\n"
289
  task["log"] += f"⏱️ Duration: {duration}s | Steps: {steps} | FPS Mult: {frame_mult}\n"
290
- task["log"] += f"⏱️ Timeout: {PREDICT_TIMEOUT}s | Max Tor retries: {MAX_TOR_RETRIES}\n"
291
- task["log"] += f"πŸ”§ Proxy scheme: socks5:// (V1.3 fix)\n"
292
 
 
293
  ensure_tor_running()
294
 
295
- # Verify Tor is actually working
296
- test_ip = get_tor_ip()
297
- task["log"] += f"πŸ§… Initial Tor IP: {test_ip}\n"
298
- if test_ip == "unknown":
299
- task["log"] += f"⚠️ WARNING: Could not determine Tor IP. Tor may not be routing.\n"
300
- task["log"] += f" Attempting to restart Tor...\n"
301
- subprocess.run(["service", "tor", "restart"], capture_output=True, timeout=15)
302
- time.sleep(5)
303
- test_ip = get_tor_ip()
304
- task["log"] += f" After restart: {test_ip}\n"
305
 
306
  img1_resized = resize_image_for_video(img1_path) if img1_path else None
307
  img2_resized = resize_image_for_video(img2_path) if img2_path else None
@@ -368,9 +389,6 @@ def run_generation_task(
368
 
369
  if is_quota_error(err_msg):
370
  task["log"] += f"⚠️ QUOTA EXCEEDED on IP {current_ip}\n"
371
- retry_time = parse_retry_time(err_msg)
372
- if retry_time:
373
- task["log"] += f" ⏰ Retry in: {retry_time}s\n"
374
  task["log"] += f"πŸ”„ Rotating Tor circuit...\n"
375
  new_ip = rotate_tor_circuit()
376
  task["log"] += f" πŸ§… New IP: {new_ip}\n"
@@ -509,7 +527,7 @@ def stream_video(video_path: str, request: Request):
509
  # 🌐 FASTAPI APPLICATION
510
  # ═══════════════════════════════════════════════════════════════════════════════
511
 
512
- app = FastAPI(title="SODA-GEN Anonymous Relay V1.3")
513
 
514
  app.add_middleware(
515
  CORSMiddleware,
@@ -520,16 +538,45 @@ app.add_middleware(
520
  )
521
 
522
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  @app.get("/api/health")
524
  async def health_check():
525
- ip = get_tor_ip()
526
- tor_ok = ip != "unknown" and ip != "rotation_failed"
 
 
 
527
  return JSONResponse({
528
  "status": "ok",
529
  "mode": "anonymous",
530
- "version": "1.3",
531
- "tor_ip": ip,
532
- "tor_active": tor_ok,
533
  "proxy_scheme": "socks5://",
534
  "ffmpeg": shutil.which("ffmpeg") is not None,
535
  })
@@ -623,15 +670,15 @@ async def serve_thumbnail(filename: str):
623
 
624
  @app.get("/api/stats")
625
  async def api_stats():
626
- ip = get_tor_ip()
627
  return JSONResponse({
628
  "mode": "anonymous",
629
- "tor_ip": ip,
 
630
  "quota_per_ip": 120,
631
  "total_tokens": "infinity (Tor rotation)",
632
  "active_tokens": "∞",
633
  "cooldown_tokens": 0,
634
- "version": "1.3",
635
  })
636
 
637
 
@@ -645,19 +692,3 @@ async def api_tor_ip():
645
  async def api_tor_rotate():
646
  new_ip = rotate_tor_circuit()
647
  return JSONResponse({"new_ip": new_ip, "success": new_ip != "rotation_failed"})
648
-
649
-
650
- @app.on_event("startup")
651
- async def on_startup():
652
- ensure_tor_running()
653
- ip = get_tor_ip()
654
- print("=" * 60)
655
- print("πŸ§… SODA-GEN Anonymous Relay V1.3 β€” HF Space CPU")
656
- print(f" 🌐 Tor IP: {ip}")
657
- print(f" πŸ“‘ Target: {TARGET_URL}")
658
- print(f" πŸ§… Proxy: {TOR_PROXY}")
659
- print(f" πŸ”§ Scheme: socks5:// (V1.3 fix)")
660
- print(f" ⏱️ Timeout: {PREDICT_TIMEOUT}s")
661
- print(f" 🎬 ffmpeg: {'βœ…' if shutil.which('ffmpeg') else '❌'}")
662
- print(f" 🎬 ffprobe: {'βœ…' if shutil.which('ffprobe') else '❌'}")
663
- print("=" * 60)
 
1
  # ═══════════════════════════════════════════════════════════════════════════════
2
+ # πŸ§… SODA-GEN ANONYMOUS RELAY V1.4 β€” HF SPACE CPU (BACKEND ONLY)
3
  #
4
+ # V1.4 FIX: Non-blocking startup (was causing 503 on HF Space)
5
+ # - on_startup: lightweight only, no Tor network calls
6
+ # - Tor IP check runs in background thread
7
+ # - Health endpoint always responds instantly
 
 
8
  # ═══════════════════════════════════════════════════════════════════════════════
9
 
10
  import json
 
33
  # ═══════════════════════════════════════════════════════════════════════════════
34
 
35
  TARGET_URL = "https://r3gm-wan2-2-fp8da-aoti-preview-2.hf.space"
 
 
 
 
36
  TOR_PROXY = "socks5://127.0.0.1:9050"
 
37
  PREDICT_TIMEOUT = 300
38
  MAX_TOR_RETRIES = 5
39
  GPU_RETRY_DELAY = 15
 
52
  TASKS: Dict[str, Dict[str, Any]] = {}
53
 
54
  _last_tor_ip = {"ip": "unknown", "time": 0}
55
+ _tor_ready = False # Set True once Tor is confirmed working
56
 
57
  # ═══════════════════════════════════════════════════════════════════════════════
58
  # πŸ§… TOR ENGINE
59
  # ═══════════════════════════════════════════════════════════════════════════════
60
 
61
  def ensure_tor_running():
62
+ """Ensure Tor daemon is active (no network calls)"""
63
  try:
64
  result = subprocess.run(
65
  ["service", "tor", "status"],
 
70
  time.sleep(3)
71
  print("πŸ§… Tor started")
72
  except:
73
+ try:
74
+ subprocess.run(["service", "tor", "start"], capture_output=True, timeout=10)
75
+ time.sleep(3)
76
+ except Exception as e:
77
+ print(f"⚠️ Cannot start Tor: {e}")
78
 
79
 
80
  def get_tor_ip() -> str:
81
+ """Get current Tor exit node IP (cached for 10s)"""
82
  now = time.time()
83
  if now - _last_tor_ip["time"] < 10 and _last_tor_ip["ip"] != "unknown":
84
  return _last_tor_ip["ip"]
 
95
 
96
 
97
  def rotate_tor_circuit() -> str:
98
+ """Request new Tor circuit β†’ new exit node β†’ new IP"""
99
  try:
100
  with Controller.from_port(port=9051) as controller:
101
  controller.authenticate()
 
123
  return "rotation_failed"
124
 
125
 
126
+ def _background_tor_init():
127
+ """
128
+ Run in background thread AFTER server is ready.
129
+ This prevents blocking the startup event.
130
+ """
131
+ global _tor_ready
132
+ print("πŸ§… Background Tor init starting...")
133
+
134
+ # Wait extra time for Tor to build circuits
135
+ time.sleep(10)
136
+
137
+ ensure_tor_running()
138
+
139
+ # Try to get IP (might take time)
140
+ ip = get_tor_ip()
141
+ if ip != "unknown":
142
+ _tor_ready = True
143
+ print(f"πŸ§… Tor ready! IP: {ip}")
144
+ else:
145
+ # Retry once after short delay
146
+ time.sleep(5)
147
+ ip = get_tor_ip()
148
+ if ip != "unknown":
149
+ _tor_ready = True
150
+ print(f"πŸ§… Tor ready (retry)! IP: {ip}")
151
+ else:
152
+ print("⚠️ Tor init incomplete β€” will retry on first request")
153
+
154
+
155
  # ═══════════════════════════════════════════════════════════════════════════════
156
  # πŸ“‘ GRADIO CLIENT WITH PROXY
157
  # ════════════════════════════════���══════════════════════════════════════════════
158
 
159
  class GradioClientWithProxy:
 
 
 
 
 
 
 
160
  def __init__(self, base_url: str, proxy: str = None):
161
  self.base_url = base_url
162
  self.proxy = proxy
 
179
 
180
  self.client = Client(
181
  self.base_url,
182
+ headers={"User-Agent": "SodaGen-Anonymous/1.4"}
183
  )
184
 
185
  def submit(self, **kwargs):
 
291
 
292
 
293
  # ═══════════════════════════════════════════════════════════════════════════════
294
+ # πŸš€ GENERATION TASK
295
  # ═══════════════════════════════════════════════════════════════════════════════
296
 
297
  def run_generation_task(
 
303
  steps: float,
304
  frame_mult: int,
305
  ):
306
+ global _tor_ready
307
  task = TASKS[task_id]
308
 
309
  try:
310
  task["status"] = "processing"
311
  task["log"] += f"🎬 [ANONYMOUS MODE] Prompt: {prompt[:80]}...\n"
312
  task["log"] += f"⏱️ Duration: {duration}s | Steps: {steps} | FPS Mult: {frame_mult}\n"
 
 
313
 
314
+ # Ensure Tor is running (may not be ready yet on first call)
315
  ensure_tor_running()
316
 
317
+ # Get Tor IP (might fail first time, that's ok)
318
+ current_ip = get_tor_ip()
319
+ task["log"] += f"πŸ§… Tor IP: {current_ip}\n"
320
+
321
+ if current_ip == "unknown":
322
+ task["log"] += f"⚠️ Tor not ready yet, waiting 15s for circuits...\n"
323
+ time.sleep(15)
324
+ current_ip = get_tor_ip()
325
+ task["log"] += f"πŸ§… Tor IP (retry): {current_ip}\n"
 
326
 
327
  img1_resized = resize_image_for_video(img1_path) if img1_path else None
328
  img2_resized = resize_image_for_video(img2_path) if img2_path else None
 
389
 
390
  if is_quota_error(err_msg):
391
  task["log"] += f"⚠️ QUOTA EXCEEDED on IP {current_ip}\n"
 
 
 
392
  task["log"] += f"πŸ”„ Rotating Tor circuit...\n"
393
  new_ip = rotate_tor_circuit()
394
  task["log"] += f" πŸ§… New IP: {new_ip}\n"
 
527
  # 🌐 FASTAPI APPLICATION
528
  # ═══════════════════════════════════════════════════════════════════════════════
529
 
530
+ app = FastAPI(title="SODA-GEN Anonymous Relay V1.4")
531
 
532
  app.add_middleware(
533
  CORSMiddleware,
 
538
  )
539
 
540
 
541
+ # ═══════════════════════════════════════════════════════════
542
+ # πŸ”§ V1.4 FIX: STARTUP IS LIGHTWEIGHT β€” NO BLOCKING CALLS
543
+ # ═══════════════════════════════════════════════════════════
544
+
545
+ @app.on_event("startup")
546
+ async def on_startup():
547
+ """
548
+ V1.4: Startup event is FAST and NON-BLOCKING.
549
+ Only prints basic info. Tor IP check runs in background thread.
550
+ This ensures HF Space health check gets instant response β†’ no 503.
551
+ """
552
+ print("=" * 60)
553
+ print("πŸ§… SODA-GEN Anonymous Relay V1.4 β€” HF Space CPU")
554
+ print(f" πŸ“‘ Target: {TARGET_URL}")
555
+ print(f" πŸ§… Proxy: {TOR_PROXY}")
556
+ print(f" ⏱️ Timeout: {PREDICT_TIMEOUT}s")
557
+ print(f" 🎬 ffmpeg: {'βœ…' if shutil.which('ffmpeg') else '❌'}")
558
+ print(f" πŸ§… Tor init: running in background thread...")
559
+ print("=" * 60)
560
+
561
+ # Start Tor initialization in BACKGROUND thread
562
+ # This does NOT block the startup event
563
+ t = threading.Thread(target=_background_tor_init, daemon=True)
564
+ t.start()
565
+
566
+
567
  @app.get("/api/health")
568
  async def health_check():
569
+ """
570
+ Lightweight health check β€” responds INSTANTLY.
571
+ Tor IP is optional (shows cached or 'initializing').
572
+ HF Space uses this to determine if space is alive.
573
+ """
574
  return JSONResponse({
575
  "status": "ok",
576
  "mode": "anonymous",
577
+ "version": "1.4",
578
+ "tor_ip": _last_tor_ip["ip"],
579
+ "tor_ready": _tor_ready,
580
  "proxy_scheme": "socks5://",
581
  "ffmpeg": shutil.which("ffmpeg") is not None,
582
  })
 
670
 
671
  @app.get("/api/stats")
672
  async def api_stats():
 
673
  return JSONResponse({
674
  "mode": "anonymous",
675
+ "tor_ip": _last_tor_ip["ip"],
676
+ "tor_ready": _tor_ready,
677
  "quota_per_ip": 120,
678
  "total_tokens": "infinity (Tor rotation)",
679
  "active_tokens": "∞",
680
  "cooldown_tokens": 0,
681
+ "version": "1.4",
682
  })
683
 
684
 
 
692
  async def api_tor_rotate():
693
  new_ip = rotate_tor_circuit()
694
  return JSONResponse({"new_ip": new_ip, "success": new_ip != "rotation_failed"})