This repository has been archived on 2026-07-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
mission-control/config.py
T
Hitonabi 09aabbb86e feat(v9): Phase 5 — Hermes-Web-Dashboard eingebettet, Eigenbau-Chat raus
Das Framework bringt eine fertige Web-UI mit (Chat mit Live-Tool-Aktivitaet,
Approval-Prompts, Settings, Sessions). Statt sie nachzubauen, betten wir sie ein:

- routers/hermes_ui.py: HTTP+WS-Reverse-Proxy auf das lokale Dashboard (:9119)
  unter /hermes-ui/ mit X-Forwarded-Prefix -> Dashboard rewritet Assets/Base-Path
  selbst, injiziert seinen Session-Token (kein zweiter Login). WS-Bruecke fuer
  pty/ws/pub/events.
- HermesPanel: Chat -> iframe auf /hermes-ui/; Eigenbau-Chat/Voice/WS entfernt.
  Cockpit + Setup bleiben. Loest damit Kontext-/Lern-/Tool-Sichtbarkeits-Themen,
  da die UI direkt mit dem Agent-Loop spricht (kein Proxy-Bug mehr).
- routers/hermes.py: Chat-WS + _proxy_chat entfernt (Cutover).
- config.py: HERMES_DASHBOARD_URL. requirements: websockets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 16:47:52 +02:00

108 lines
5.6 KiB
Python

"""
Zentrale Konfiguration fuer Mission Control.
Alles ueber Umgebungsvariablen ueberschreibbar. Wird von allen Modulen importiert,
damit es genau eine Quelle der Wahrheit fuer Pfade, URLs und Defaults gibt.
"""
import os
import sys
from pathlib import Path
from ruamel.yaml import YAML
# ---------------------------------------------------------------------------
# Env-Vars (siehe README fuer Bedeutung)
# ---------------------------------------------------------------------------
LLAMA_SWAP_URL = os.environ.get("MC_LLAMA_SWAP_URL", "http://127.0.0.1:8080").rstrip("/")
CONFIG_PATH = Path(os.environ.get("MC_CONFIG_PATH", "/etc/llama-swap/config.yaml"))
MODELS_DIR = Path(os.environ.get("MC_MODELS_DIR", "/srv/models"))
# Eigene Cookbook-Setups (vom Nutzer angelegt). Bewusst NICHT im App-Verzeichnis, sonst
# wuerde ein Deploy (rsync) sie ueberschreiben -> persistent neben den Modellen ablegen.
USER_RECIPES_PATH = Path(os.environ.get("MC_USER_RECIPES", str(MODELS_DIR / "mission-control-recipes.json")))
MEMORY_DB = Path(os.environ.get("MC_MEMORY_DB", str(MODELS_DIR / "mission-control-memory.db")))
# Cache der automatischen Modell-Entdeckung ("aktuell beste Modelle", live von HuggingFace).
# Ebenfalls persistent neben den Modellen (uebersteht Deploys). TTL = wie lange der Cache
# als frisch gilt, bevor lazy neu von den Quellen geladen wird (Default 12 h).
DISCOVER_CACHE_PATH = Path(os.environ.get("MC_DISCOVER_CACHE", str(MODELS_DIR / "mission-control-discover.json")))
DISCOVER_TTL = int(os.environ.get("MC_DISCOVER_TTL", "43200"))
# Befehl, der zum Starten eines Modells in die config.yaml geschrieben wird.
# {model} = Pfad zur GGUF-Datei, {ctx} = Kontextlaenge, ${PORT} bleibt fuer llama-swap stehen.
# WICHTIG: an deinen Container-/llama-server-Aufruf anpassen (siehe README).
_DEFAULT_CMD_TEMPLATE = (
"llama-server -m {model} --host 127.0.0.1 --port ${PORT} "
"-c {ctx} -ngl 999 -fa 1 --no-mmap"
)
CMD_TEMPLATE = os.environ.get("MC_CMD_TEMPLATE", _DEFAULT_CMD_TEMPLATE)
# Schutz: Eine unquotierte systemd `Environment=MC_CMD_TEMPLATE=...`-Zeile wird an
# Leerzeichen gesplittet und kommt als blosses "llama-server" an. Fehlt der {model}-
# Platzhalter, ist die Vorlage unbrauchbar -> sicheren Default verwenden.
if "{model}" not in CMD_TEMPLATE:
CMD_TEMPLATE = _DEFAULT_CMD_TEMPLATE
# Befehl fuer "Container/Toolbox aktualisieren". Standard: kyuz0 refresh-Skript.
UPDATE_CMD = os.environ.get("MC_UPDATE_CMD", "")
DEFAULT_TTL = int(os.environ.get("MC_DEFAULT_TTL", "300"))
TOKEN = os.environ.get("MC_TOKEN", "") # leer = keine Auth (nur LAN!)
# Hermes Agent (Nous-Framework, eigener Dienst auf :8642 — siehe ROADMAP v9).
# Mission Control proxyt nur noch dorthin; Modell-Routing macht Hermes selbst.
HERMES_API_URL = os.environ.get("HERMES_API_URL", "http://127.0.0.1:8642/v1").rstrip("/")
def _read_hermes_api_key() -> str:
"""API-Key des Hermes-Agent-Servers: bevorzugt Env HERMES_API_KEY,
sonst aus ~/.hermes/.env (Schluessel API_SERVER_KEY) — so muss das
Secret nicht dupliziert werden."""
k = os.environ.get("HERMES_API_KEY", "")
if k:
return k
envf = Path(os.path.expanduser("~/.hermes/.env"))
if envf.exists():
try:
for line in envf.read_text(errors="replace").splitlines():
line = line.strip()
if line.startswith("API_SERVER_KEY="):
return line.split("=", 1)[1].strip()
except Exception:
pass
return ""
HERMES_API_KEY = _read_hermes_api_key()
# Control-Plane-Reads (Phase 4): Hermes legt Status/Cron/Skills lokal unter
# ~/.hermes ab; die `hermes`-CLI ist die kanonische Quelle fuer Cron/Skills.
# MC laeuft als hitonabi auf der Box -> direkter Lese-/Exec-Zugriff.
HERMES_HOME = Path(os.path.expanduser(os.environ.get("HERMES_HOME", "~/.hermes")))
HERMES_BIN = os.path.expanduser(os.environ.get("HERMES_BIN", "~/.local/bin/hermes"))
# Hermes-Web-Dashboard (eingebaute UI mit Chat/Tool-Aktivitaet, lokal gebunden).
# MC reverse-proxyt es unter /hermes-ui/ (X-Forwarded-Prefix) und bettet es per
# iframe ein -> eine Oberflaeche, kein zweiter Login (Dashboard self-auth auf Loopback).
HERMES_DASHBOARD_URL = os.environ.get("HERMES_DASHBOARD_URL", "http://127.0.0.1:9119").rstrip("/")
HERMES_WINDOWS_HOST = os.environ.get("HERMES_WINDOWS_HOST", "")
HERMES_WINDOWS_USER = os.environ.get("HERMES_WINDOWS_USER", "TobisPC")
HERMES_SSH_KEY = Path(os.path.expanduser(os.environ.get("HERMES_SSH_KEY", "~/.ssh/id_ed25519_hermes_agent")))
PIPER_BIN = Path(os.environ.get("PIPER_BIN", "/opt/mission-control/piper/piper"))
PIPER_VOICE = Path(os.environ.get("PIPER_VOICE", "/opt/mission-control/piper/voices/de_DE-kerstin-low.onnx"))
WHISPER_MODEL_SIZE = os.environ.get("WHISPER_MODEL", "medium")
# Self-Update ("Mission Control aktualisieren"): Quelle = git-Repo, Prod = laufende Installation.
SOURCE_DIR = os.path.expanduser(os.environ.get("MC_SOURCE_DIR", "~/mission-control"))
PROD_DIR = str(Path(__file__).resolve().parent)
# Env fuer alle HuggingFace-Downloads: XET deaktivieren (mit aktivem XET haengt der
# Download reproduzierbar bei ~6 MB, siehe CLAUDE.md). Token wird je Job ergaenzt.
HF_DOWNLOAD_ENV = {"HF_HUB_DISABLE_XET": "1"}
def hf_bin() -> str:
"""Pfad zur `hf`-CLI fuer Modell-Downloads. 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"
# Gemeinsame YAML-Instanz (preserve_quotes haelt Kommentare/Quotes in config.yaml).
yaml = YAML()
yaml.preserve_quotes = True