ecf974d9c0
Macht das "geschenkte v9" sichtbar. Read-only Control-Plane-Reads:
- hermes_control.py: gateway_state.json + cron-Heartbeat (Status),
`hermes cron list` + `hermes skills list` via COLUMNS=400 (kein
Rich-Truncation) als kanonische Quelle, .usage.json fuer Nutzung.
TTL-Cache (10s) gegen Polling-Last.
- routers/hermes.py: GET /api/hermes/{agent,cron,skills}.
- HermesPanel: aufklappbares "Cockpit" — Status-Badges, Cron-Jobs,
Skills (sortiert nach Nutzung, aktiv/inaktiv).
- config.py: HERMES_HOME, HERMES_BIN.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
104 lines
5.3 KiB
Python
104 lines
5.3 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_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
|