Files
mission-control/CLAUDE.md
T
2026-06-20 21:47:57 +02:00

5.9 KiB

Mission Control

Web-Dashboard zur Verwaltung eines lokalen LLM-Stacks (llama-swap) auf dem Bosgame M5. FastAPI-Backend + Vanilla-JS-Dashboard. Leitprinzip: KISS — kein Build-Schritt, kein Frontend-Framework, keine Datenbank. Concerns sind getrennt (SoC) — aber ohne Build: native ES-Module im Frontend, FastAPI-APIRouter im Backend.

Architektur

Backend (Top-Level-Helfer + ein Router je Bereich):

  • app.py — dünner Einstieg: baut FastAPI, hängt die Router ein, liefert das statische UI aus, registriert den Exception-Handler. Sonst nichts.
  • config.py — alle Env-Vars + die gemeinsame ruamel.yaml-Instanz.
  • auth.py — optionale Token-Auth (X-MC-Token).
  • jobengine.py — In-Memory-Job-System (Threads + Subprocess) mit Live-Log; fährt Downloads/Updates.
  • llamaswap.py — spricht llama-swap an (/running, /v1/models, unload) und liest/schreibt dessen config.yaml per ruamel.yaml (Kommentare bleiben erhalten).
  • routers/*.py — ein Router je Bereich. Aktuell: models.py (status, download, register, unload, chat), jobs.py (jobs), maintenance.py (update), system.py (status). Alle Endpoints unter /api/*.

Frontend (static/, dünne Hülle + ES-Module, kein Build):

  • index.html — nur Gerüst: Sidebar-Nav, Topbar, Alert-Banner, ein .view-Container je Bereich (Hash-Routing). Lädt css/* und js/main.js als Modul.

  • css/base.css — Design-Tokens (:root), Reset, App-Layout (Sidebar/Topbar/Content). css/components.css — Karten, KPI-Kacheln, Listen, Forms, Log, Toast.

  • js/core/*api.js (Fetch + Token), ui.js (DOM-Helfer, Toast, Icons), nav.js (View-Switch).

  • js/panels/* — ein Panel je Bereich (overview, models, maintenance, jobs). Panel-Vertrag: { id, mount?(), onStatus?(s), onJobs?(jobs) }.

  • js/main.js — bootet Panels, pflegt Topbar/Alert, fährt das Polling (/api/status 3 s, /api/jobs 1.5 s) und verteilt an die Panels.

  • mission-control.service — systemd-Unit (uvicorn auf Port 9000).

  • Konfiguration rein über Env-Vars: MC_LLAMA_SWAP_URL, MC_CONFIG_PATH, MC_MODELS_DIR, MC_CMD_TEMPLATE, MC_UPDATE_CMD, MC_DEFAULT_TTL, MC_TOKEN.

Der Stack drumherum (Kontext)

  • Bosgame M5: AMD Strix Halo (gfx1151), Ubuntu 26.04, Kernel 7.0, ~124 GB GTT-Speicher. LAN-IP 192.168.178.151.
  • Inferenz: vorgebaute llama.cpp-ROCm-Binaries → llama-swap auf :8080 (läuft mit -watch-config).
  • 3 Modelle / 5 Rollen: coder, scout, vision (Qwen3-Familie). Modelle werden geswappt, nicht parallel geladen.
  • Mission Control: Produktiv unter /opt/mission-control (als Dienst), Source-Repo unter ~/mission-control.

Entwickeln & Deployen

Wo entwickelt wird: primär auf einem Windows-PC (Repo z. B. F:\Coding Stuff\mission-control), Zielsystem ist der Linux-Bosgame. Lokal nur Smoke-Test (rendert/bootet sauber?), echter Funktionstest nur auf dem Bosgame (dort läuft llama-swap).

Lokaler Smoke-Test (Windows):

python -m venv .venv && .venv/Scripts/python -m pip install -r requirements.txt
.venv/Scripts/python -m uvicorn app:app --port 9001

Ohne llama-swap ist alles im Offline-Zustand (rote Pill, Warn-Banner) — das ist erwartet.

Bosgame-Zugang (SSH, key-basiert, passwortlos):

ssh -i ~/.ssh/id_ed25519_hermes -o IdentitiesOnly=yes hitonabi@192.168.178.151

User ist hitonabi (klein!). sudo braucht ein Passwort (kein passwortloses sudo).

Deploy-Kette (Windows → Gitea → Bosgame):

  1. Lokal bauen + Smoke-Test → git push (origin = Gitea http://192.168.178.153:3000/Hitonabi/mission-control.git).
  2. Bosgame: cd ~/mission-control && git pull --ff-only (Source-Repo).
  3. Nach /opt ausrollen — ohne sudo (hitonabi besitzt /opt/mission-control), .venv ausnehmen:
    rsync -a --exclude='.git' --exclude='.venv' --exclude='__pycache__' --exclude='*.pyc' ~/mission-control/ /opt/mission-control/
    
  4. sudo systemctl restart mission-control (Passwort nötig). Prod = :9000, Dev-Spielwiese = :9001.
  5. Niemals direkt in /opt arbeiten. Logs: journalctl -u mission-control -f.

Statische Dateien werden je Request frisch von Platte gelesen → UI-Änderungen wirken schon nach rsync; Python-Code-Änderungen brauchen den Restart.

Konventionen

  • KISS über alles: kein schweres Framework, kein Build-Schritt, solange es ohne geht.
  • SoC ohne Build: neuer Bereich = ein routers/<bereich>.py (FastAPI-Router) + ein static/js/panels/<bereich>.js (ES-Modul nach Panel-Vertrag) + ein Nav-Eintrag in index.html. Gemeinsame Backend-Logik in config/auth/jobengine/llamaswap, gemeinsame Frontend-Logik in js/core/*. Keine schweren Libs, kein CDN — nur relative imports.
  • Endpoint-URLs bleiben unter /api/*; neue Bereiche degradieren sauber, wenn ihre Quelle (sysfs, amd-smi, systemctl) fehlt (z. B. beim Entwickeln auf Windows).
  • Funktion darf nicht von localStorage abhängen (nur das Token-Feld nutzt es, das ist ok).
  • Sicherheit: Das Backend führt Shell-Befehle aus → ausschließlich im vertrauenswürdigen LAN betreiben, niemals offen ins Internet.

Gotchas (wichtig!)

  • ${PORT} in der generierten llama-swap-Config muss literal stehen bleiben → beim Bauen des cmd-Strings str.replace benutzen, NICHT .format (sonst KeyError auf PORT).
  • llama-swap muss mit -watch-config laufen, sonst greift das Auto-Einpflegen neuer Modelle nicht.
  • HuggingFace-Downloads mit HF_HUB_DISABLE_XET=1 (sonst reproduzierbarer Hänger bei ~6 MB).
  • Vision-Modelle in llama.cpp brauchen zusätzlich --mmproj <projektor> und --jinja.

Projektstatus & Roadmap

Die v2-Roadmap ist vollständig umgesetzt (siehe ROADMAP.md). Wir befinden uns nun im Feinschliff- und Wartungsmodus.

Nordstern: den Server nie wieder via SSH/Putty anfassen müssen — 100 % Automatisierung / Klicki-Bunti.