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:
@@ -9,6 +9,7 @@ import os
|
|||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
import socket
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@@ -30,6 +31,12 @@ SWITCH_TIMEOUT = int(os.getenv("SWITCH_TIMEOUT", "90"))
|
|||||||
CHECK_INTERVAL = int(os.getenv("CHECK_INTERVAL", "2"))
|
CHECK_INTERVAL = int(os.getenv("CHECK_INTERVAL", "2"))
|
||||||
JEAN_STOP_TIMEOUT = int(os.getenv("JEAN_STOP_TIMEOUT", "30"))
|
JEAN_STOP_TIMEOUT = int(os.getenv("JEAN_STOP_TIMEOUT", "30"))
|
||||||
OLLAMA_UNLOAD_TIMEOUT = int(os.getenv("OLLAMA_UNLOAD_TIMEOUT", "15"))
|
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")
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -65,10 +72,83 @@ def get_presets(force_refresh=False):
|
|||||||
return presets_cache["data"]
|
return presets_cache["data"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning(f"[Presets] Erreur récupération: {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"]
|
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):
|
def get_ollama_models(force_refresh=False):
|
||||||
"""Récupère la liste des modèles depuis Ollama"""
|
"""Récupère la liste des modèles depuis Ollama"""
|
||||||
if not ENABLE_OLLAMA:
|
if not ENABLE_OLLAMA:
|
||||||
@@ -88,6 +168,17 @@ def get_ollama_models(force_refresh=False):
|
|||||||
return data
|
return data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning(f"[Ollama] Erreur récupération: {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"]
|
return ollama_cache["data"]
|
||||||
|
|
||||||
@@ -667,6 +758,8 @@ def chat_completions():
|
|||||||
if model_id in jean_preset_ids or model_id in jean_names:
|
if model_id in jean_preset_ids or model_id in jean_names:
|
||||||
internal_id = jean_name_to_id.get(model_id, model_id)
|
internal_id = jean_name_to_id.get(model_id, model_id)
|
||||||
|
|
||||||
|
wait_for_jean(trigger_wol=True)
|
||||||
|
|
||||||
if not ensure_vram_for_backend("jean"):
|
if not ensure_vram_for_backend("jean"):
|
||||||
return jsonify({"error": "Failed to prepare VRAM for Jean"}), 503
|
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"
|
upstream_url = f"http://{JEAN_PROXY_HOST}:{JEAN_PROXY_PORT}/v1/chat/completions"
|
||||||
log.info(f"[Router] → Jean ({model_id})")
|
log.info(f"[Router] → Jean ({model_id})")
|
||||||
elif ENABLE_OLLAMA:
|
elif ENABLE_OLLAMA:
|
||||||
|
wait_for_jean(trigger_wol=True)
|
||||||
ollama_models = get_ollama_models()
|
ollama_models = get_ollama_models()
|
||||||
ollama_ids = [m.get("name", "").replace(":latest", "") for m in ollama_models]
|
ollama_ids = [m.get("name", "").replace(":latest", "") for m in ollama_models]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user