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.
  • Python 94.4%
  • Shell 5.6%
Find a file
dschlueter b8680cc049 docs: move BEDIENUNGSANLEITUNG and INSTALL_FROM_ARCHIVE into docs/
Keep only README, LICENSE, and CHANGELOG as prose at the repo root
(tooling/convention) and move the remaining manuals under docs/ next to
SECURITY_AND_OPERATIONS.md.

- git mv the two files into docs/ (history preserved).
- Update all cross-references: README doc-index links, the internal
  SECURITY_AND_OPERATIONS link and the §12 pointer list in
  BEDIENUNGSANLEITUNG, and the repo inventory in SECURITY_AND_OPERATIONS §1.
- build_archive.py: point REQUIRED_FILES at docs/ and drop the now-redundant
  INCLUDE_FILES entries (the docs/ dir is included wholesale).
- build_archive.py: drop the obsolete requirements.txt<->pyproject dependency
  mirror check, which broke once requirements.txt was reduced to `.`
  (pyproject is the single source of truth). Verified by building and
  re-opening the archive.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 11:17:41 +02:00
.forgejo/workflows ci: add local pre-push gate (ruff + mypy + pytest) 2026-07-07 10:48:57 +02:00
.githooks ci: add local pre-push gate (ruff + mypy + pytest) 2026-07-07 10:48:57 +02:00
docs docs: move BEDIENUNGSANLEITUNG and INSTALL_FROM_ARCHIVE into docs/ 2026-07-07 11:17:41 +02:00
man docs(config): clarify profile names and make examples copy-paste safe 2026-07-07 09:53:43 +02:00
scripts ci: add local pre-push gate (ruff + mypy + pytest) 2026-07-07 10:48:57 +02:00
src/llamacppctl chore(ci): add ruff and mypy, wire them into CI 2026-07-07 10:29:07 +02:00
tests chore(ci): add ruff and mypy, wire them into CI 2026-07-07 10:29:07 +02:00
.gitignore chore(repo): add LICENSE, CI workflow, and untrack local config 2026-07-07 10:23:05 +02:00
build_archive.py docs: move BEDIENUNGSANLEITUNG and INSTALL_FROM_ARCHIVE into docs/ 2026-07-07 11:17:41 +02:00
CHANGELOG.md docs: add CHANGELOG and ship LICENSE/CHANGELOG in the archive 2026-07-07 10:36:14 +02:00
LICENSE chore(repo): add LICENSE, CI workflow, and untrack local config 2026-07-07 10:23:05 +02:00
llama.cpp.config.example build(docker): pin the llama.cpp image by digest 2026-07-07 10:28:23 +02:00
pyproject.toml chore(ci): add ruff and mypy, wire them into CI 2026-07-07 10:29:07 +02:00
README.md docs: move BEDIENUNGSANLEITUNG and INSTALL_FROM_ARCHIVE into docs/ 2026-07-07 11:17:41 +02:00
requirements-dev.txt build: make pyproject the single source of dependency truth 2026-07-07 10:36:00 +02:00
requirements.txt build: make pyproject the single source of dependency truth 2026-07-07 10:36:00 +02:00

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.

Dokumentation

Dokument Für wen / wofür
README (diese Datei) Schnelleinstieg: Installation, Konfiguration, Verwendung
docs/BEDIENUNGSANLEITUNG.md Vollständiges Benutzerhandbuch (alle Aktionen, Optionen, Fehlersuche)
docs/INSTALL_FROM_ARCHIVE.md Installation aus dem .tar.gz-Archiv + Archiv selbst bauen/verifizieren
docs/SECURITY_AND_OPERATIONS.md Architektur, Modul-/Repo-Übersicht, Sicherheits- & Betriebsmodell
man/llamacppctl.1 Vollständige Optionsreferenz (man llamacppctl bzw. man ./man/llamacppctl.1)
CHANGELOG.md Versionshistorie

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-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.<profile>]-Sektion (falls --profile gesetzt)
  4. 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).

Migration von den Shell-Skripten

llamacppctl ersetzt die einzelnen Shell-Skripte durch Actions eines Werkzeugs (--profile ist optional; ohne greift die [default]-Sektion):

Altes Skript Neuer Aufruf
start-llm-server.sh llamacppctl --start --config llama.cpp.config [--profile <name>]
status-llm-server.sh llamacppctl --check --config llama.cpp.config [--profile <name>]
Stop-Skript llamacppctl --stop --config llama.cpp.config [--profile <name>]
switch-llm.sh (Modellwechsel) llamacppctl --change --config llama.cpp.config [--profile <name>]
(neu) direkter Chat-Test llamacppctl --chat --config llama.cpp.config -p "..."

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-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-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 ü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.<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