⬅ חזרה לתיקייה
✏️ עריכת admin_server_RESCUE_REPLACED_CURRENT_20260701_112603.py
C:\TripServer\backups\admin_server\admin_server_RESCUE_REPLACED_CURRENT_20260701_112603.py
# -*- coding: utf-8 -*- """ admin_server_PANEL_AUTO_RESCUE.py קובץ חירום להעלאה דרך הפאנל החכם, בשדה "העלה admin_server.py חדש". מטרתו: לשחזר אוטומטית את הגרסה העדכנית ביותר של admin_server.py מתוך הגיבויים, בלי להחזיר את הפאנל אחורה. שימוש מהפאנל: 1. העלה את הקובץ הזה במקום admin_server.py. 2. לחץ "אתחל פאנל". 3. המתן 10–20 שניות ורענן. הקובץ מחפש גיבויים בעיקר ב: C:\TripServer\backups\admin_server ומדרג אותם לפי סימנים של הגרסה המתקדמת: /admin/usa, USA_TV, v5 וכו'. """ from __future__ import annotations import os import sys import time import glob import shutil import hashlib import py_compile import subprocess import threading from datetime import datetime from pathlib import Path BASE_DIR = Path(r"C:\TripServer") TARGET = BASE_DIR / "admin_server.py" BACKUP_DIRS = [ BASE_DIR / "backups" / "admin_server", BASE_DIR / "backups", BASE_DIR, ] LOG_PATH = BASE_DIR / "admin_panel_auto_rescue.log" MIN_AUTO_SCORE = 120 MARKERS = { "multi-page USA phone TV separated v5 canonical self-update": 300, "v5 canonical self-update": 200, "USA_TV": 90, "/USA_TV": 90, "admin_usa": 70, "/admin/usa": 90, "upload_usa_presentation_image": 65, "usa_presentation_image": 55, "restore_admin_server_backup": 35, "upload_admin_server": 35, "restart_admin_server": 25, "/health": 20, "טיול ארצות הברית 27 מותאם לטלוויזיה": 45, "מצגת ארצות הברית מותאמת למסך טלוויזיה": 45, } PENALTIES = { "USA2027 panel v3 reliable restart": -120, "slides\": 43": -60, } def log(msg: str) -> None: try: LOG_PATH.parent.mkdir(parents=True, exist_ok=True) with LOG_PATH.open("a", encoding="utf-8") as f: f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}\n") except Exception: pass print(msg, flush=True) def read_text(path: Path) -> str: data = path.read_bytes() for enc in ("utf-8", "utf-8-sig", "cp1255", "latin-1"): try: return data.decode(enc) except UnicodeDecodeError: continue return data.decode("utf-8", errors="ignore") def sha12(path: Path) -> str: h = hashlib.sha256() with path.open("rb") as f: for chunk in iter(lambda: f.read(1024 * 1024), b""): h.update(chunk) return h.hexdigest()[:12] def compile_ok(path: Path) -> tuple[bool, str]: try: py_compile.compile(str(path), doraise=True) return True, "OK" except Exception as exc: return False, repr(exc) def score_file(path: Path) -> tuple[int, list[str], str]: try: text = read_text(path) except Exception as exc: return -9999, [], f"read failed: {exc!r}" score = 0 found: list[str] = [] for marker, pts in MARKERS.items(): if marker in text: score += pts found.append(marker) for marker, pts in PENALTIES.items(): if marker in text: score += pts found.append(f"PENALTY:{marker}") size_kb = path.stat().st_size / 1024 if size_kb > 50: score += 5 if size_kb > 75: score += 10 if size_kb > 100: score += 10 ok, msg = compile_ok(path) if not ok: score -= 1000 found.append("COMPILE_FAIL") return score, found, msg return score, found, "OK" def collect_candidates() -> list[dict]: files: dict[str, Path] = {} patterns = [ "admin_server_backup_*.py", "admin_server_before_restore_*.py", "admin_server_CURRENT_BEFORE_RESCUE_*.py", "admin_server*.py", "trip_server*.py", "*_admin_server*.py", ] for root in BACKUP_DIRS: if not root.exists(): continue for pat in patterns: for p in root.glob(pat): try: rp = p.resolve() # לא לבחור את קובץ החירום הפעיל עצמו if rp == TARGET.resolve(): continue if rp.is_file() and rp.suffix.lower() == ".py": files[str(rp)] = rp except Exception: continue rows = [] for p in files.values(): try: score, markers, status = score_file(p) rows.append({ "path": p, "score": score, "markers": markers, "status": status, "mtime": p.stat().st_mtime, "size": p.stat().st_size, "sha": sha12(p), }) except Exception as exc: rows.append({ "path": p, "score": -9999, "markers": [], "status": repr(exc), "mtime": 0, "size": 0, "sha": "ERR", }) rows.sort(key=lambda r: (r["score"], r["mtime"]), reverse=True) return rows def delayed_launch_restored_server() -> None: py = sys.executable or "python" target = str(TARGET) if os.name == "nt": # נותן לתהליך החירום להשתחרר מהפורט לפני הרצת הפאנל המשוחזר cmd = f'ping 127.0.0.1 -n 3 > nul && "{py}" "{target}"' creationflags = getattr(subprocess, "CREATE_NEW_CONSOLE", 0) subprocess.Popen(["cmd", "/c", cmd], cwd=str(BASE_DIR), creationflags=creationflags) else: subprocess.Popen([py, target], cwd=str(BASE_DIR)) def restore_candidate(row: dict) -> tuple[bool, str]: src: Path = row["path"] ok, msg = compile_ok(src) if not ok: return False, f"המועמד לא עבר קומפילציה: {msg}" rescue_backup_dir = BASE_DIR / "backups" / "admin_server" rescue_backup_dir.mkdir(parents=True, exist_ok=True) stamp = datetime.now().strftime("%Y%m%d_%H%M%S") if TARGET.exists(): current_backup = rescue_backup_dir / f"admin_server_RESCUE_REPLACED_CURRENT_{stamp}.py" try: shutil.copy2(TARGET, current_backup) log(f"current target backed up to {current_backup}") except Exception as exc: log(f"could not backup current target: {exc!r}") shutil.copy2(src, TARGET) ok2, msg2 = compile_ok(TARGET) if not ok2: return False, f"הקובץ הועתק אבל נכשל בקומפילציה: {msg2}" log(f"RESTORED {src} -> {TARGET} | score={row['score']} | sha={row['sha']}") return True, f"שוחזר בהצלחה מתוך: {src}" def start_rescue_ui(rows: list[dict], reason: str) -> None: try: from flask import Flask, redirect except Exception as exc: log(f"Cannot start rescue UI, Flask import failed: {exc!r}") while True: time.sleep(60) app = Flask(__name__) def html_page(msg: str = "") -> str: trs = [] for i, r in enumerate(rows[:30], 1): dt = datetime.fromtimestamp(r["mtime"]).strftime("%Y-%m-%d %H:%M:%S") if r["mtime"] else "?" markers = ", ".join(r["markers"][:8]) or "-" kb = r["size"] / 1024 trs.append( f"<tr><td>{i}</td><td>{r['score']}</td><td>{dt}</td><td>{kb:.1f}KB</td>" f"<td style='direction:ltr;text-align:left'>{r['path']}</td><td>{markers}</td>" f"<td><a class='btn' href='/restore/{i}'>שחזר</a></td></tr>" ) table = "\n".join(trs) return f"""<!doctype html><html lang='he' dir='rtl'><meta charset='utf-8'> <title>חילוץ פאנל חכם</title> <style> body{{font-family:Arial,sans-serif;background:#0f172a;color:#e5e7eb;margin:0;padding:24px}} .box{{max-width:1180px;margin:auto;background:#111827;border:1px solid #334155;border-radius:18px;padding:22px}} h1{{color:#38bdf8}} .warn{{background:#451a03;border:1px solid #f97316;padding:12px;border-radius:12px}} .ok{{background:#052e16;border:1px solid #22c55e;padding:12px;border-radius:12px}} table{{width:100%;border-collapse:collapse;margin-top:16px;font-size:13px}}td,th{{border-bottom:1px solid #334155;padding:8px;vertical-align:top}} .btn{{display:inline-block;background:#16a34a;color:white;padding:8px 12px;border-radius:10px;text-decoration:none;font-weight:bold}} .small{{opacity:.8;font-size:13px;direction:ltr;text-align:left;white-space:pre-wrap}} </style><div class='box'> <h1>מצב חילוץ פאנל חכם</h1> <div class='warn'><strong>סיבה:</strong> {reason}</div> {f"<div class='ok'>{msg}</div>" if msg else ""} <p>בחר לשחזר רק מועמד עם ציון גבוה. בדרך כלל הגרסה הנכונה תהיה עם סימנים כמו <strong>/admin/usa</strong>, <strong>USA_TV</strong>, או <strong>v5</strong>.</p> <table><tr><th>#</th><th>ציון</th><th>תאריך</th><th>גודל</th><th>קובץ</th><th>סימנים</th><th>פעולה</th></tr>{table}</table> <p class='small'>לוג: {LOG_PATH}</p> </div></html>""" @app.route("/") def index(): return html_page() @app.route("/health") def health(): return {"ok": True, "mode": "admin_server_PANEL_AUTO_RESCUE", "candidates": len(rows), "best_score": rows[0]["score"] if rows else None} @app.route("/restore/<int:index>") def restore(index: int): if index < 1 or index > len(rows): return html_page("מועמד לא קיים"), 404 row = rows[index - 1] ok, msg = restore_candidate(row) if not ok: return html_page(msg), 500 threading.Thread(target=lambda: (time.sleep(1), delayed_launch_restored_server(), time.sleep(1), os._exit(0)), daemon=True).start() return f"""<!doctype html><html lang='he' dir='rtl'><meta charset='utf-8'> <body style='font-family:Arial;background:#052e16;color:white;padding:30px'> <h1>שוחזר</h1><p>{msg}</p><p>הפאנל יופעל מחדש בעוד כמה שניות. רענן את הדף.</p> </body></html>""" log("starting emergency rescue UI on 0.0.0.0:8000") app.run(host="0.0.0.0", port=8000, debug=False) def main() -> int: log("=== admin_server PANEL AUTO RESCUE started ===") rows = collect_candidates() log(f"candidates found: {len(rows)}") for i, r in enumerate(rows[:10], 1): log(f"candidate {i}: score={r['score']} mtime={datetime.fromtimestamp(r['mtime']).strftime('%Y-%m-%d %H:%M:%S') if r['mtime'] else '?'} sha={r['sha']} path={r['path']} markers={r['markers'][:6]}") if rows and rows[0]["score"] >= MIN_AUTO_SCORE: ok, msg = restore_candidate(rows[0]) log(msg) if ok: delayed_launch_restored_server() log("restored server launch requested; rescue process exiting") time.sleep(1) return 0 reason = "לא נמצא גיבוי עם ציון מספיק גבוה לשחזור אוטומטי. הפעלתי ממשק חילוץ ידני." if rows: reason += f" המועמד הכי טוב קיבל ציון {rows[0]['score']}." start_rescue_ui(rows, reason) return 0 if __name__ == "__main__": raise SystemExit(main())
שמור קובץ