jean-router: ajout Wake-on-LAN

- Detection si Jean/Ollama inaccessible
- Envoi magic packet WoL vers TARGET_MAC (fallback PC_GAMING_MAC)
- Attente WAKE_TIMEOUT secondes que la machine demarre
- Integration dans get_presets, get_ollama_models, chat_completions
- Verrous pour eviter les WoL simultanes
- Active via ENABLE_WOL=true
This commit is contained in:
Folzo
2026-08-01 08:20:35 +02:00
parent 68a76aa254
commit 4f8a5288ff
+94
View File
@@ -9,6 +9,7 @@ import os
import time
import logging
import threading
import socket
from typing import Optional
import requests
@@ -30,6 +31,12 @@ SWITCH_TIMEOUT = int(os.getenv("SWITCH_TIMEOUT", "90"))
CHECK_INTERVAL = int(os.getenv("CHECK_INTERVAL", "2"))
JEAN_STOP_TIMEOUT = int(os.getenv("JEAN_STOP_TIMEOUT", "30"))
OLLAMA_UNLOAD_TIMEOUT = int(os.getenv("OLLAMA_UNLOAD_TIMEOUT", "15"))
ENABLE_WOL = os.getenv("ENABLE_WOL", "false").lower() == "true"
TARGET_MAC = os.getenv("TARGET_MAC", os.getenv("PC_GAMING_MAC", ""))
BROADCAST_IP = os.getenv("BROADCAST_IP", "192.168.1.255")
WAKE_TIMEOUT = int(os.getenv("WAKE_TIMEOUT", "120"))
wol_in_progress = threading.Lock()
jean_alive_cache = {"alive": False, "timestamp": 0}
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger(__name__)
@@ -65,10 +72,83 @@ def get_presets(force_refresh=False):
return presets_cache["data"]
except Exception as e:
log.warning(f"[Presets] Erreur récupération: {e}")
if ENABLE_WOL and (not presets_cache["data"] or force_refresh):
wait_for_jean(trigger_wol=True)
try:
r = requests.get(f"http://{JEAN_WEB_HOST}:{JEAN_WEB_PORT}/api/presets", timeout=5)
if r.ok:
presets_cache["data"] = r.json()
presets_cache["timestamp"] = now
return presets_cache["data"]
except Exception:
pass
return presets_cache["data"]
def send_wol():
"""Envoie un magic packet WoL vers TARGET_MAC"""
if not TARGET_MAC:
log.warning("[WoL] TARGET_MAC non defini, WoL desactive")
return
mac = TARGET_MAC.replace(":", "").replace("-", "")
if len(mac) != 12:
log.error(f"[WoL] MAC invalide: {TARGET_MAC}")
return
data = bytes.fromhex("FF" * 6 + mac * 16)
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
for i in range(3):
s.sendto(data, (BROADCAST_IP, 9))
log.info(f"[WoL] Magic packet #{i+1} envoye vers {TARGET_MAC}")
def jean_alive() -> bool:
"""Verifie si Jean est accessible"""
now = time.time()
if now - jean_alive_cache["timestamp"] < 5:
return jean_alive_cache["alive"]
try:
url = f"http://{JEAN_WEB_HOST}:{JEAN_WEB_PORT}/api/presets"
r = requests.get(url, timeout=3)
alive = r.ok
except Exception:
alive = False
jean_alive_cache["alive"] = alive
jean_alive_cache["timestamp"] = now
return alive
def wait_for_jean(trigger_wol=True):
"""Attend que Jean soit accessible. Si ENABLE_WOL et trigger_wol, envoie WoL d'abord."""
if jean_alive():
return
if not ENABLE_WOL or not trigger_wol:
return
with wol_in_progress:
if jean_alive():
return
log.info("[WoL] Jean inaccessible, envoi WoL...")
send_wol()
deadline = time.time() + WAKE_TIMEOUT
while time.time() < deadline:
if jean_alive():
jean_alive_cache["timestamp"] = 0
log.info(f"[WoL] Jean UP apres {int(time.time() - deadline + WAKE_TIMEOUT)}s")
return
time.sleep(CHECK_INTERVAL)
log.error(f"[WoL] Jean ne repond pas apres {WAKE_TIMEOUT}s")
def get_ollama_models(force_refresh=False):
"""Récupère la liste des modèles depuis Ollama"""
if not ENABLE_OLLAMA:
@@ -88,6 +168,17 @@ def get_ollama_models(force_refresh=False):
return data
except Exception as e:
log.warning(f"[Ollama] Erreur récupération: {e}")
if ENABLE_WOL and (not ollama_cache["data"] or force_refresh):
wait_for_jean(trigger_wol=True)
try:
r = requests.get(f"http://{OLLAMA_PROXY_HOST}:{OLLAMA_PROXY_PORT}/api/tags", timeout=5)
if r.ok:
data = r.json().get("models", [])
ollama_cache["data"] = data
ollama_cache["timestamp"] = now
return data
except Exception:
pass
return ollama_cache["data"]
@@ -667,6 +758,8 @@ def chat_completions():
if model_id in jean_preset_ids or model_id in jean_names:
internal_id = jean_name_to_id.get(model_id, model_id)
wait_for_jean(trigger_wol=True)
if not ensure_vram_for_backend("jean"):
return jsonify({"error": "Failed to prepare VRAM for Jean"}), 503
@@ -695,6 +788,7 @@ def chat_completions():
upstream_url = f"http://{JEAN_PROXY_HOST}:{JEAN_PROXY_PORT}/v1/chat/completions"
log.info(f"[Router] → Jean ({model_id})")
elif ENABLE_OLLAMA:
wait_for_jean(trigger_wol=True)
ollama_models = get_ollama_models()
ollama_ids = [m.get("name", "").replace(":latest", "") for m in ollama_models]