#!/usr/bin/env python3 """Stufe 5 der Pipeline: Server zuverlässig neu starten UND das Ergebnis prüfen. ⚠⚠ WARUM ES DIESES SKRIPT GIBT. `restart_server.bat` hat **zweimal still nicht neu gestartet** (auch als `Start-Process cmd /c … -WindowStyle Minimized`) — der alte Prozess lief weiter, und weil statische Dateien bei jedem Request frisch von der Platte gelesen werden, **belegt eine korrekt ausgelieferte `app.js?v=N` GAR NICHTS** über den geladenen Python-Code. Der Fehler fiel damals nur auf, weil ein NEUES Snapshot-Feld fehlte (`hl_live.basis_stale`). Dieses Skript macht daraus einen prüfbaren Ablauf: 1. Prozess auf dem Port gezielt beenden — **nicht** per `*server.py*`-Muster, das trifft auch das Hyperliquid-Dashboard auf 8001 (real passiert: der HL-Server wurde bei jedem MT5-Neustart still mit-erschlagen und riss die Lücken in die Datensammlung). 2. Neu starten und warten, bis `/api/snapshot` wirklich antwortet. 3. **Genau EINE** Instanz je Port verifizieren. 4. Optional `--feld ` prüfen: ist dieses Snapshot-Feld da, ist der PYTHON-Code nachweislich neu. Das ist der einzige belastbare Beweis. 5. Das Log seit dem Start auf Fehler durchsehen. Rückgabecode 0 nur, wenn ALLE Schritte durchlaufen — so lässt sich das Skript verketten, statt das Ergebnis von Hand zu lesen. Aufruf: python tools/deploy.py # Oil-Server (8000) neu starten python tools/deploy.py --feld rec_track # zusätzlich Code-Neuheit belegen python tools/deploy.py --hl # HL-Dashboard (8001) mitnehmen python tools/deploy.py --nur-pruefen # nichts anfassen, nur Zustand """ import argparse import json import subprocess import sys import time import urllib.request from pathlib import Path WURZEL = Path(__file__).resolve().parents[1] PY = r"C:\Users\ah\AppData\Local\Programs\Python\Python312\python.exe" HL = WURZEL.parent / "HyperLiquid-WTI Trader" LOG = WURZEL / "oil_widget.log" PORT, PORT_HL = 8000, 8001 START_TIMEOUT_S = 120 def ps(cmd: str) -> str: """PowerShell — für die Prozess-/Port-Abfragen der zuverlässigste Weg hier.""" r = subprocess.run(["powershell", "-NoProfile", "-Command", cmd], capture_output=True, text=True, timeout=60) return (r.stdout or "").strip() def pids(port: int) -> list[int]: out = ps(f"(Get-NetTCPConnection -LocalPort {port} -State Listen " f"-ErrorAction SilentlyContinue | Select-Object -Expand " f"OwningProcess -Unique) -join ','") return [int(x) for x in out.split(",") if x.strip().isdigit()] def snapshot(timeout: float = 5.0) -> dict | None: try: with urllib.request.urlopen( f"http://127.0.0.1:{PORT}/api/snapshot", timeout=timeout) as r: return json.loads(r.read().decode()) except Exception: return None def schritt(nr: int, text: str): print(f" [{nr}] {text}") def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--feld", help="Snapshot-Feld, das den neuen Code belegt") ap.add_argument("--hl", action="store_true", help="HL-Dashboard (8001) mit") ap.add_argument("--nur-pruefen", action="store_true") a = ap.parse_args() print("=" * 74) print(" DEPLOY — Stufe 5 (Aktivierung) + Stufe 6 (Verifikation)") print("=" * 74) fehler: list[str] = [] if a.nur_pruefen: for p in (PORT, PORT_HL): print(f" Port {p}: {pids(p) or 'nichts'}") s = snapshot() print(f" Snapshot: {'antwortet' if s else 'KEINE Antwort'}") return 0 if s else 1 # ── 1) gezielt beenden ─────────────────────────────────────────────── alt = pids(PORT) schritt(1, f"Port {PORT}: {alt or 'nichts'} → beenden") for pid in alt: ps(f"Stop-Process -Id {pid} -Force -ErrorAction SilentlyContinue") if alt: time.sleep(3) if a.hl and HL.exists(): for pid in pids(PORT_HL): ps(f"Stop-Process -Id {pid} -Force -ErrorAction SilentlyContinue") time.sleep(2) ps(f"Start-Process -FilePath '{PY}' -ArgumentList '-X','utf8','server.py' " f"-WorkingDirectory '{HL}' -WindowStyle Hidden") schritt(1, f"HL-Dashboard ({PORT_HL}) neu gestartet") log_ab = LOG.stat().st_size if LOG.exists() else 0 # ── 2) starten und auf echte Antwort warten ────────────────────────── schritt(2, "Server starten …") ps(f"Start-Process -FilePath '{PY}' -ArgumentList '-X','utf8','server.py' " f"-WorkingDirectory '{WURZEL}' -WindowStyle Minimized") t0 = time.time() snap = None while time.time() - t0 < START_TIMEOUT_S: time.sleep(3) snap = snapshot() if snap: break if not snap: print(f" ❌ /api/snapshot antwortet nach {START_TIMEOUT_S} s nicht.") return 1 schritt(2, f"läuft nach {time.time() - t0:.0f} s") # ── 3) genau EINE Instanz ──────────────────────────────────────────── for p, noetig in ((PORT, True), (PORT_HL, a.hl)): ids = pids(p) if not noetig and not ids: continue if len(ids) == 1: schritt(3, f"Port {p}: genau eine Instanz (PID {ids[0]}) ✅") else: fehler.append(f"Port {p}: {len(ids)} Instanzen {ids}") schritt(3, f"Port {p}: ⚠ {len(ids)} Instanzen {ids}") # ── 4) beweist ein neues Feld, dass der PYTHON-Code neu ist? ───────── if a.feld: if a.feld in snap: schritt(4, f"Feld `{a.feld}` vorhanden → Python-Code ist neu ✅") else: fehler.append(f"Feld `{a.feld}` fehlt im Snapshot") schritt(4, f"⚠ Feld `{a.feld}` FEHLT — der alte Code läuft weiter!") else: schritt(4, "kein --feld angegeben ⚠ ohne das ist NICHT belegt, dass der " "Python-Code neu ist (statische Dateien beweisen nichts)") # ── 5) Log seit dem Start ──────────────────────────────────────────── treffer = [] if LOG.exists(): with LOG.open(encoding="utf-8", errors="replace") as fh: fh.seek(log_ab) for z in fh: if any(w in z for w in ("ERROR", "Traceback", "CRITICAL")): treffer.append(z.rstrip()[:130]) if treffer: fehler.append(f"{len(treffer)} Fehlerzeile(n) im Log") schritt(5, f"⚠ {len(treffer)} Fehlerzeile(n) seit dem Start:") for z in treffer[:5]: print(f" {z}") else: schritt(5, "Log seit dem Start ohne ERROR/Traceback ✅") print("=" * 74) if fehler: print(" ERGEBNIS: ❌ " + " · ".join(fehler)) return 1 print(" ERGEBNIS: ✅ neu gestartet und verifiziert") return 0 if __name__ == "__main__": sys.exit(main())