llamacppctl/README.md

208 lines
8.3 KiB
Markdown
Raw Normal View History

# 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.<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
```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 <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`](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.<container_name>.lock`), um parallele Änderungen am
selben Container zu verhindern.
## Entwicklung / Tests
```bash
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:
```bash
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:
```bash
SMOKE_GPU=1 scripts/smoke.sh
```
## Lizenz
MIT License
Copyright (c) 2026 Dieter Schlueter