# llamacppctl Ein einziges, sicherheitsgehärtetes CLI-Werkzeug zum Starten, Prüfen, Stoppen und Wechseln von `llama.cpp`-Server-Instanzen, die als Docker-Container laufen — inklusive einer SSRF-gehärteten Eingabeschicht für System- und User-Prompts aus Text, Datei oder Remote-URL. `llamacppctl` ersetzt eine Sammlung einzelner Shell-Skripte (`start-llm-server.sh`, `status-llm-server.sh`, `switch-llm.sh`, …) durch ein einziges Python-Paket mit konsistenter Konfiguration, Locking und Fehlerbehandlung. ## Installation ```bash tar xzf llamacppctl-installable.tar.gz cd llamacppctl python3 -m venv .venv source .venv/bin/activate pip install . ``` Danach steht der Befehl `llamacppctl` innerhalb der aktivierten virtuellen Umgebung zur Verfügung. Für eine systemweite Installation (ohne venv): ```bash pip install --user . ``` Voraussetzungen auf dem Zielsystem: - Python >= 3.10 - Docker (CLI + laufender Daemon), für `--start`/--check`/--stop`/--change` - Netzwerkzugriff auf den Container-Host-Port für `--chat`/--check` `--print-effective-config` und `--dry-run` benötigen keinen laufenden Docker-Daemon für die reine Konfigurationsprüfung; `--start`/--check`/--stop`/--change` erfordern einen erreichbaren Docker-Daemon. ## Konfiguration Kopiere `llama.cpp.config.example` nach `llama.cpp.config` und passe es an: ```bash cp llama.cpp.config.example llama.cpp.config ``` Abschnittstypen: - `[default]` — globale Defaults, überschreiben die eingebauten Fallback-Werte - `[model.]` — ein Modell-Profil, ausgewählt über `--profile ` - `[prompt.]` — ein System-Prompt-Profil, ausgewählt über `--system-prompt-profile ` (liefert nur einen *Fallback*-Systemprompt; ein explizites `-s/--system-file/--system-url` gewinnt immer) **`container_name` ist Pflicht** und muss für jedes Profil, das parallel laufen soll, eindeutig sein — er ist der einzige Identitätsanker für Docker-Namensgebung, Locking (`--start`/`--change`) sowie `--stop`/`--check`-Ziel. `hf_home` unterstützt Environment-Variablen und `~`, z. B. `hf_home = ${HF_HOME}`. Das Chat-Antwortbudget ist ebenfalls konfigurierbar (wirkt auf `--chat` und die Antwort nach `--start`, nicht auf den Container): `max_tokens` (Default 2048 — für Reasoning-Modelle / lange Texte hochsetzen, sonst bricht die Antwort mitten im „Denken" ab und `content` bleibt leer) und `chat_temperature` (leer = Server-`temp` gilt). CLI-Overrides: `--max-tokens`, `--chat-temp`. Auflösungsreihenfolge (niedrigste zu höchster Priorität): 1. Eingebaute Defaults 2. `[default]`-Sektion 3. `[model.]`-Sektion (falls `--profile` gesetzt) 4. CLI-Overrides (`--image`, `--host-port`, …) ## Verwendung ```bash # Server starten (nutzt die [default]-Sektion der Config) llamacppctl --start --config llama.cpp.config # Nur den geplanten docker-run-Befehl anzeigen, nichts ausführen llamacppctl --start --config llama.cpp.config --dry-run # Effektive Konfiguration als JSON ausgeben llamacppctl --print-effective-config --config llama.cpp.config --start # Status prüfen llamacppctl --check --config llama.cpp.config # Server stoppen und Container entfernen llamacppctl --stop --config llama.cpp.config # Ein bestimmtes Modell-Profil starten (siehe Hinweis unter dem Block) llamacppctl --start --config llama.cpp.config --profile qwopus # Modell wechseln (stop, neu konfigurieren, restart) unter Lock-Schutz llamacppctl --change --config llama.cpp.config --profile carnice # Direkten Chat-Request an einen laufenden Server senden llamacppctl --chat --config llama.cpp.config \ -s "Du antwortest kurz." -p "Was ist 2+2?" # Chat mit Live-Streaming und großem Antwortbudget (lange Texte) llamacppctl --chat --config llama.cpp.config \ --stream --max-tokens 8000 -p "Schreibe eine kurze Rede zum Jahreswechsel." # System-Prompt aus einer lokalen Datei, User-Prompt als Literal llamacppctl --start --config llama.cpp.config \ --system-file ./prompts/coding.md -p "Refaktoriere diese Funktion." # System-Prompt von einer HTTPS-URL (öffentliches, nicht-privates Ziel) llamacppctl --chat --config llama.cpp.config \ --system-url https://example.com/prompts/system.txt -p "Hallo" ``` Ohne `--profile` gilt die `[default]`-Sektion. Mit `--profile ` wählst du ein `[model.]`-Profil; in `llama.cpp.config` sind das z. B. `carnice`, `qwen27b`, `qwopus`, in der Vorlage `llama.cpp.config.example` `qwen35b` und `deepseek`. Die Ziffern (z. B. `35b`) meinen die Modellgröße in Milliarden Parametern, nicht die Qwen-Version. Vollständige Optionsliste: `llamacppctl --help` oder die Manpage (`man/llamacppctl.1`, siehe unten). ## Sicherheitsmodell (Kurzfassung) Für Details siehe [`docs/SECURITY_AND_OPERATIONS.md`](docs/SECURITY_AND_OPERATIONS.md). - Alle Docker-Aufrufe laufen über `subprocess.run()` mit Argumentlisten — niemals über Shell-Strings, somit keine Shell-Injection-Fläche. - `--system-file`/--prompt-file` lesen ausschließlich reguläre Dateien, Größenlimit (`--max-input-bytes`, Default 1 MiB), UTF-8-Text erzwungen, Symlinks standardmäßig abgelehnt, per `--allow-symlinks` freischaltbar. - `--system-url`/--prompt-url` sind standardmäßig HTTPS-only, blockieren IP-Literale, private/loopback/link-local/reserved Adressbereiche sowie die Cloud-Metadata-IP `169.254.169.254`, verbieten eingebettete Zugangsdaten in der URL, erlauben standardmäßig keine Redirects (jeder Hop wird bei Aktivierung erneut validiert), erzwingen eine Content-Type-Allowlist und ein hartes Größenlimit sowohl über `Content-Length` als auch beim Streaming. - Der Port wird standardmäßig **nur auf `127.0.0.1`** veröffentlicht — der ungeauthentifizierte OpenAI-Endpoint ist also nicht im LAN erreichbar. `--expose`/`expose = true` bindet auf alle Interfaces; dann sollte per `--api-key`/`api_key` ein Schlüssel gesetzt werden (Server verlangt ihn, das Tool sendet ihn als Bearer-Token). - `container_name` ist verpflichtend und muss pro parallel betriebenem Profil eindeutig sein, um Docker-Namenskollisionen und ungewollte Container-Übernahmen zu verhindern. - `--start`/--change` nutzen echte Chat-Completions zum Readiness-Check (nicht nur einen offenen Port), um sicherzustellen, dass das Modell tatsächlich geladen und inferenzfähig ist. - `--change` verwendet eine exklusive, nicht-blockierende Dateisperre (`/tmp/llamacppctl..lock`), um parallele Änderungen am selben Container zu verhindern. ## Entwicklung / Tests ```bash pip install -e ".[dev]" pytest -q ``` Die Test-Suite deckt die Prompt-Eingabeschicht (Datei- und URL-Quellen inkl. SSRF-Abwehr mit gemockter DNS-Auflösung und DNS-Pinning), die Konfigurationsauflösung (inkl. `${ENV}`-Expansion), die CLI-Validierung, die Action-Orchestrierung (`do_start`/`do_change`/`do_check`/`do_chat`, Locking, dry-run) sowie Docker-/HTTP-Operationen (mit gemocktem `subprocess` bzw. `requests`) ab. Für einen End-to-End-Test gegen einen **echten** Docker + llama.cpp-Server (u. a. `--api-key`-Round-Trip, Streaming, `--check`-Exit-Codes) gibt es einen opt-in Rauchtest — er startet einen separaten Container und räumt danach auf: ```bash SMOKE_GPU=1 scripts/smoke.sh ``` ## Lizenz MIT