Forgejo Actions is not enabled on the instance, so run the same checks locally before pushing: - scripts/check.sh runs ruff, mypy, and pytest (mirrors the CI workflow). - .githooks/pre-push invokes it; enable per clone with `git config core.hooksPath .githooks`. Bypass with `git push --no-verify`. - Fix the pytest invocation in the CI workflow (and document it): use `python -m pytest` so the repo root is on sys.path, otherwise the test modules fail to `import tests.*`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.6 KiB
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
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):
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:
cp llama.cpp.config.example llama.cpp.config
Abschnittstypen:
[default]— globale Defaults, überschreiben die eingebauten Fallback-Werte[model.<name>]— ein Modell-Profil, ausgewählt über--profile <name>[prompt.<name>]— ein System-Prompt-Profil, ausgewählt über--system-prompt-profile <name>(liefert nur einen Fallback-Systemprompt; ein explizites-s/--system-file/--system-urlgewinnt 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):
- Eingebaute Defaults
[default]-Sektion[model.<profile>]-Sektion (falls--profilegesetzt)- CLI-Overrides (
--image,--host-port, …)
Verwendung
# 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 <name> wählst du
ein [model.<name>]-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.
- Alle Docker-Aufrufe laufen über
subprocess.run()mit Argumentlisten — niemals über Shell-Strings, somit keine Shell-Injection-Fläche. --system-file/--prompt-filelesen 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-urlsind standardmäßig HTTPS-only, blockieren IP-Literale, private/loopback/link-local/reserved Adressbereiche sowie die Cloud-Metadata-IP169.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 überContent-Length` als auch beim Streaming.- Der Port wird standardmäßig nur auf
127.0.0.1veröffentlicht — der ungeauthentifizierte OpenAI-Endpoint ist also nicht im LAN erreichbar.--expose/expose = truebindet auf alle Interfaces; dann sollte per--api-key/api_keyein Schlüssel gesetzt werden (Server verlangt ihn, das Tool sendet ihn als Bearer-Token). container_nameist 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.--changeverwendet eine exklusive, nicht-blockierende Dateisperre (/tmp/llamacppctl.<container_name>.lock), um parallele Änderungen am selben Container zu verhindern.
Entwicklung / Tests
pip install -e ".[dev]"
python -m pytest -q
python -m pytest (statt des pytest-Skripts) legt das Repo-Root auf
sys.path, damit die Testmodule import tests.* auflösen.
Lokales CI-Gate: scripts/check.sh bündelt ruff + mypy + pytest
(spiegelt .forgejo/workflows/ci.yml). Als Pre-Push-Hook aktivieren — er
blockt einen Push bei Fehlern:
git config core.hooksPath .githooks # einmalig pro Clone
Einen einzelnen Push im Notfall umgehen: git push --no-verify.
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:
SMOKE_GPU=1 scripts/smoke.sh
Lizenz
MIT License Copyright (c) 2026 Dieter Schlueter