docs: bring docs/ and build_archive.py up to date

Die docs/ waren gegenüber den neuen Features veraltet.

- SECURITY_AND_OPERATIONS.md: Netzwerk-Exposition (Loopback-Default,
  --expose/--api-key inkl. 401-Verhalten der Endpunkte), DNS-Pinning,
  --start/--change unter Lock + --force, --change validate-before-remove,
  Chat-Parameter (max_tokens/chat_temperature/--stream), ${ENV}-Expansion,
  --check-Exit-Codes, smoke.sh, ChatReply; Fehler-Tabelle erweitert
  (401, Trunkierung, Docker-health vs. HTTP-OK).
- How_to_use.md: Abschnitt zu den neuen Optionen.
- Archiv-Report: Testzahl (130) und Dateiliste aktualisiert.
- build_archive.py: scripts/ in INCLUDE_DIRS (sonst fehlt smoke.sh im Archiv),
  test_actions.py/How_to_use.md/smoke.sh in REQUIRED_FILES aufgenommen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-07-06 17:32:46 +02:00
commit 600c128fed
4 changed files with 85 additions and 19 deletions

View file

@ -9,13 +9,14 @@ Betriebsmodus auf einem dedizierten GPU-Host.
```
src/llamacppctl/
cli.py Argumentparsing (argparse) + semantische Cross-Field-Validierung
schema.py Dataclasses: ServerConfig, PromptConfig, CheckResult
schema.py Dataclasses: ServerConfig, PromptConfig, ChatReply, CheckResult
config.py INI-Konfiguration laden/mergen -> ServerConfig/PromptConfig
(inkl. ${ENV}- und ~-Expansion in hf_home)
prompt_io.py Sicherheitsgehärtete Eingabeschicht für System-/User-Prompts
docker_ops.py Alle Docker-Interaktionen (subprocess mit Argumentlisten)
http_ops.py HTTP-Health-Checks und Chat-Completions gegen die
OpenAI-kompatible llama.cpp-API
lock_ops.py Exklusives, nicht-blockierendes File-Locking für --change
http_ops.py HTTP-Health-Checks, Chat-Completions und SSE-Streaming gegen
die OpenAI-kompatible llama.cpp-API
lock_ops.py Exklusives, nicht-blockierendes File-Locking für --start/--change
actions.py Orchestrierung: do_start, do_check, do_stop, do_change, do_chat
main.py Dünner Einstiegspunkt: parse -> validate -> resolve -> dispatch
```
@ -37,13 +38,26 @@ testbar von der Orchestrierung.
`container_name=va_llm`, `gpu_device=1`, `jinja/fa/kv_unified/
cont_batching/no_context_shift=true`, `reasoning=on`,
`cache_type_k/v=q4_0`, `batch_size=1024`, `ubatch_size=512`,
`timeout=300`, `poll_interval=2`, …).
`timeout=300`, `poll_interval=2`, `expose=false`, `api_key=`,
`max_tokens=2048`, `chat_temperature=`, …).
2. **`[default]`**-Sektion — überschreibt die eingebauten Defaults global.
3. **`[model.<profile>]`**-Sektion — ausgewählt über `--profile <name>`,
überschreibt `[default]` selektiv (nur gesetzte Schlüssel).
4. **CLI-Overrides** — z. B. `--image`, `--host-port`, `--container-name`,
`--ctx-size`, … überschreiben alles Vorherige.
`hf_home` unterstützt Environment-Variablen und `~` (z. B. `hf_home = ${HF_HOME}`);
die Expansion erfolgt in `build_server_config()` über `os.path.expandvars` +
`expanduser`.
Neben den Container-/Server-Parametern liefert die Auflösung auch die
**Chat-Request-Parameter** (`PromptConfig`, wirken nur auf `--chat` und die
Antwort nach `--start`, nicht auf den Container): `max_tokens` (Default 2048;
Reasoning-Modelle brauchen viel Budget, sonst bricht die Antwort mitten im
„Denken“ ab und `content` ist leer) und `chat_temperature` (leer = die
Server-`temp` gilt). CLI-Overrides: `--max-tokens`, `--chat-temp`. Die Ausgabe
kann mit `--stream` token-weise (SSE) erfolgen.
`[prompt.<name>]`-Sektionen werden **separat** aufgelöst und liefern
ausschließlich einen *Fallback*-Systemprompt (`system_prompt = ...`). Ein
explizit übergebener `-s/--system`, `--system-file` oder `--system-url`
@ -147,6 +161,13 @@ Nach erfolgreicher Zielvalidierung (`load_text_url()`):
einen falschen (zu niedrigen) `Content-Length`-Header sendet, keinen
Speicher-Erschöpfungsangriff durchführen kann.
- Antwort muss gültiges UTF-8 sein.
- **DNS-Pinning gegen Rebinding:** `validate_url_target()` löst den Hostnamen
auf und klassifiziert die IPs; der eigentliche `requests.get` würde jedoch
**erneut** auflösen (TOCTOU-Fenster). `_pin_dns()` beschränkt `socket.getaddrinfo`
für die Dauer des Requests auf genau die bereits validierten IPs — löst der
Name zwischenzeitlich auf eine andere (z. B. private) Adresse um, wird der
Connect mit `gaierror` abgebrochen. TLS/SNI und Zertifikatsprüfung bleiben
intakt, weil die URL weiterhin den Hostnamen trägt.
### 3.3 Warum das wichtig ist
@ -175,16 +196,42 @@ mehrbenutzerfähigen Wrapper um `llamacppctl`).
Profile gezielt auf unterschiedliche GPUs (z. B. RTX 3090 #1/#2) gepinnt
werden können.
## 5. Locking (`--change`)
### 4.1 Netzwerk-Exposition und API-Key
Die llama.cpp-OpenAI-API ist standardmäßig **nicht** authentifiziert. Deshalb:
- Der Port wird per Default nur auf **`127.0.0.1`** veröffentlicht
(`_port_publish()` -> `-p 127.0.0.1:<host_port>:<container_port>`), d. h. der
Endpunkt ist **nicht** aus dem LAN erreichbar. `--expose`/`expose = true`
bindet auf alle Interfaces (`-p <host_port>:<container_port>`).
- Wird exponiert, sollte per `--api-key`/`api_key` ein Schlüssel gesetzt werden.
Er wird dem Server übergeben (`--api-key`) und vom Tool als
`Authorization: Bearer <key>` auf jeder Anfrage mitgesendet.
- **Wichtig:** llama.cpp schützt damit `/v1/chat/completions` (ohne Key -> 401),
lässt aber `/health` und `/v1/models` bewusst offen (für Health-Checks). Die
Reachability-Checks des Tools (`check_health`/`check_models`) funktionieren
daher auch ohne Key; nur die Chat-Endpunkte verlangen ihn.
## 5. Locking (`--start` / `--change`)
`lock_ops.FileLock` verwendet `fcntl.flock(LOCK_EX | LOCK_NB)` auf einer
Lock-Datei unter `/tmp/llamacppctl.<container_name>.lock` (Default, ableitbar
über `--lock-file`). `--change` hält den Lock über die gesamte
Stop-Reconfigure-Restart-Sequenz. Ein zweiter, gleichzeitiger `--change`-Aufruf
für **denselben** `container_name` schlägt sofort mit `LockError` (Exit-Code
6) fehl, statt zu blockieren oder Race-Conditions am Container zu riskieren.
Da der Lock-Pfad an `container_name` gekoppelt ist, blockieren sich zwei
Profile mit unterschiedlichem `container_name` gegenseitig nicht.
über `--lock-file`). **Sowohl `--start` als auch `--change`** halten den Lock
über die gesamte (Stop-)Reconfigure-Restart-Sequenz. Ein zweiter, gleichzeitiger
Aufruf für **denselben** `container_name` schlägt sofort mit `LockError`
(Exit-Code 6) fehl, statt zu blockieren oder Race-Conditions am Container zu
riskieren. Da der Lock-Pfad an `container_name` gekoppelt ist, blockieren sich
zwei Profile mit unterschiedlichem `container_name` gegenseitig nicht.
`--force` umgeht einen belegten Lock bewusst (mit Warnung auf stderr) — gedacht
für hängende/verwaiste Locks. Zusätzlich lässt `--force` `--stop`
Inkonsistenzen (z. B. halb erstellte Container) schlucken, statt mit
Docker-Fehler abzubrechen.
`--dry-run` ist eine reine Vorschau und nimmt **weder** den Lock **noch**
entfernt es den Container; `--change` validiert außerdem den Modellpfad
**vor** dem Entfernen des laufenden Containers, sodass eine Fehlkonfiguration
keinen vermeidbaren Ausfall verursacht.
## 6. `--dry-run` und `--print-effective-config` als Sicherheitswerkzeuge
@ -235,7 +282,11 @@ ctx_size = 131072
dem, was tatsächlich in Docker läuft.
- `--check --profile <name>` ist der empfohlene Weg für Monitoring/Cronjobs,
um sowohl Container-Status als auch tatsächliche Inferenzfähigkeit
(`chat_ok`) zu prüfen.
(`chat_ok`) zu prüfen. `--check` ist **scriptbar**: Exit-Code 0, wenn der
Container läuft und HTTP erreichbar ist, sonst 5.
- Für einen End-to-End-Test gegen einen echten Docker + llama.cpp-Server
(`--api-key`-Round-Trip, Streaming, Exit-Codes) gibt es das opt-in Skript
`scripts/smoke.sh` (eigener Container/Port, Cleanup am Ende).
## 8. Installation, Nutzung, Fehlersuche
@ -253,3 +304,6 @@ Häufige Fehlerbilder:
| `resolved to blocked IP ...` | URL löst auf eine private/loopback/reserved Adresse auf | Ziel korrigieren oder bewusst `--allow-private-url` setzen (nur für vertrauenswürdige interne Ziele) |
| `lock busy: /tmp/llamacppctl.<name>.lock` | Ein anderer `--change`-Lauf für denselben `container_name` läuft bereits | Warten, bis der andere Lauf beendet ist, oder Ursache des hängenden Laufs prüfen |
| `Server did not become ready within <n>s` | Modell braucht länger zum Laden als `timeout`, oder Startfehler | `--timeout` erhöhen, `--logs` für Container-Log-Tail nutzen |
| `chat completion returned HTTP 401: Invalid API Key` | Server verlangt `api_key`, das Tool sendet keinen/falschen | `--api-key`/`api_key` passend setzen |
| Leere Antwort + `Hinweis: ... abgeschnitten (finish_reason=length)` | Reasoning-Modell hat das `max_tokens`-Budget im „Denken“ verbraucht | `--max-tokens`/`max_tokens` erhöhen |
| `--check` meldet `health=unhealthy`, obwohl der Server antwortet | bildeigener Docker-Healthcheck vs. eigene HTTP-Probe des Tools | unkritisch: `HTTP: OK` + Exit 0 zählt; Docker-Health ist separat |