From f07a8440b25787e2e1b9c3c71640ac82f5192953 Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Sun, 21 Jun 2026 17:53:49 +0200 Subject: [PATCH] fix: 500er bei register/update_model/install-recipe, Download & favicon - write_config schreibt atomar (tmp + os.replace) -> llama-swap (-watch-config) sieht nie eine halb geschriebene config.yaml; PermissionError wird in eine klare Klartext-Meldung uebersetzt (Hinweis auf chown) - generischer Exception-Handler: unerwartete Fehler kommen als lesbare JSON- Meldung im UI an statt als nackter 500 (Anfaenger-Diagnose) - Download: nutzt die `hf`-CLI aus dem venv (Dienst-PATH kennt venv/bin nicht) + HF_HUB_DISABLE_XET=1 statt HF_XET_HIGH_PERFORMANCE (XET-Haenger bei ~6 MB) - huggingface_hub als Dependency ergaenzt (liefert `hf`) - favicon.ico: themed SVG (Link-Tag + Route) -> kein 404 mehr Co-Authored-By: Claude Opus 4.8 --- app.py | 22 +++++++++++++++++++++- llamaswap.py | 19 ++++++++++++++++--- requirements.txt | 1 + routers/models.py | 14 ++++++++++++-- static/index.html | 1 + 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/app.py b/app.py index 185c5ba..c92c024 100644 --- a/app.py +++ b/app.py @@ -17,7 +17,7 @@ Neue Bereiche kommen als routers/.py + static/js/panels/.js da from pathlib import Path from fastapi import FastAPI, HTTPException -from fastapi.responses import FileResponse, JSONResponse +from fastapi.responses import FileResponse, JSONResponse, Response from fastapi.staticfiles import StaticFiles from routers import jobs, maintenance, models, system, cookbook, integration, news @@ -52,9 +52,29 @@ def index(): return FileResponse(_STATIC / "index.html") +_FAVICON = ( + b"" + b"" + b"" + b"" +) + + +@app.get("/favicon.ico") +def favicon(): + return Response(content=_FAVICON, media_type="image/svg+xml") + + app.mount("/static", StaticFiles(directory=_STATIC), name="static") @app.exception_handler(HTTPException) def _http_exc(_req, exc: HTTPException): return JSONResponse(status_code=exc.status_code, content={"error": exc.detail}) + + +@app.exception_handler(Exception) +def _any_exc(_req, exc: Exception): + """Unerwartete Fehler als lesbare Meldung ans (vertrauenswuerdige LAN-)UI geben, + statt nur einen generischen 500 ohne Hinweis. Erleichtert Anfaengern die Diagnose.""" + return JSONResponse(status_code=500, content={"error": str(exc) or exc.__class__.__name__}) diff --git a/llamaswap.py b/llamaswap.py index a23d128..264299c 100644 --- a/llamaswap.py +++ b/llamaswap.py @@ -6,6 +6,8 @@ Helfer rund um llama-swap und dessen config.yaml. sodass Kommentare und Quotes erhalten bleiben. """ +import os + import httpx from config import CONFIG_PATH, LLAMA_SWAP_URL, yaml @@ -29,6 +31,17 @@ def read_config() -> dict: def write_config(cfg: dict) -> None: - CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True) - with CONFIG_PATH.open("w", encoding="utf-8") as f: - yaml.dump(cfg, f) + """Schreibt die config.yaml atomar (tmp-Datei + os.replace), damit llama-swap + mit -watch-config nie eine halb geschriebene Datei sieht. Fehlende Schreibrechte + werden in eine klare Meldung uebersetzt statt als roher Traceback zu landen.""" + try: + CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True) + tmp = CONFIG_PATH.with_name(CONFIG_PATH.name + ".tmp") + with tmp.open("w", encoding="utf-8") as f: + yaml.dump(cfg, f) + os.replace(tmp, CONFIG_PATH) + except PermissionError as exc: + raise PermissionError( + f"Mission Control darf '{CONFIG_PATH}' nicht schreiben. " + f"Einmalig Besitz uebergeben: sudo chown -R hitonabi:hitonabi {CONFIG_PATH.parent}" + ) from exc diff --git a/requirements.txt b/requirements.txt index e04c6be..04e0a50 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ uvicorn[standard]>=0.29 httpx>=0.27 ruamel.yaml>=0.18 psutil>=5.9.0 +huggingface_hub>=0.34 # liefert die `hf`-CLI fuer Modell-Downloads diff --git a/routers/models.py b/routers/models.py index 2a65fa0..c581cd5 100644 --- a/routers/models.py +++ b/routers/models.py @@ -19,11 +19,20 @@ from llamaswap import _swap_get, read_config, write_config from hw_math import extract_params_b, max_ctx_for, estimate_memory_gb import re import os +import sys import psutil router = APIRouter(prefix="/api", dependencies=[Depends(auth)]) +def _hf_bin() -> str: + """Pfad zur `hf`-CLI. Bevorzugt die im venv installierte (neben dem laufenden + Python), da der Dienst-PATH das venv/bin meist nicht enthaelt. Faellt sonst auf + ein global installiertes `hf` zurueck.""" + cand = os.path.join(os.path.dirname(sys.executable), "hf") + return cand if os.path.exists(cand) else "hf" + + # --------------------------------------------------------------------------- # Request-Modelle # --------------------------------------------------------------------------- @@ -139,8 +148,9 @@ def download(req: DownloadReq): sub = req.subdir or req.repo.split("/")[-1] target = MODELS_DIR / sub target.mkdir(parents=True, exist_ok=True) - args = ["hf", "download", req.repo, req.file, "--local-dir", str(target)] - env = {"HF_XET_HIGH_PERFORMANCE": "1"} + args = [_hf_bin(), "download", req.repo, req.file, "--local-dir", str(target)] + # XET deaktivieren: mit aktivem XET haengt der Download reproduzierbar bei ~6 MB (siehe CLAUDE.md). + env = {"HF_HUB_DISABLE_XET": "1"} if req.hf_token: env["HF_TOKEN"] = req.hf_token job_id = start_job(args, f"download {req.repo}/{req.file}", env=env) diff --git a/static/index.html b/static/index.html index 7fe0128..e1176d8 100644 --- a/static/index.html +++ b/static/index.html @@ -4,6 +4,7 @@ Mission Control +