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:
parent
aa1a9fc232
commit
600c128fed
4 changed files with 85 additions and 19 deletions
|
|
@ -4,13 +4,13 @@ Was gebaut wurde
|
|||
|
||||
Statt manuell zu tarballen, gibt es jetzt ein eigenständiges Python-Skript build_archive.py, das:
|
||||
|
||||
Prüft, ob alle 27 erforderlichen Projektdateien vorhanden sind (Quellcode, Tests, Doku, Manpage, Config-Beispiel)
|
||||
Prüft, ob alle erforderlichen Projektdateien vorhanden sind (Quellcode, Tests, Doku, Manpage, Config-Beispiel, scripts/smoke.sh)
|
||||
|
||||
pyproject.toml-Abhängigkeiten gegen requirements.txt abgleicht
|
||||
|
||||
Die deklarierte Abhängigkeit (requests>=2.31,<3) in einer frischen, isolierten virtuellen Umgebung installiert, um echte Installierbarkeit zu bestätigen (nicht nur einen Trockenlauf)
|
||||
|
||||
Die vollständige Test-Suite (93 Tests, alle grün) laufen lässt
|
||||
Die vollständige Test-Suite (aktuell 130 Tests, alle grün) laufen lässt
|
||||
|
||||
Das .tar.gz über Pythons tarfile-Modul baut (nicht rohes Shell-tar)
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ Enthaltene Projektstruktur
|
|||
|
||||
src/llamacppctl/ — die vollständige Implementierung: cli.py, config.py, prompt_io.py (SSRF-gehärtete Eingabeschicht), docker_ops.py, http_ops.py, lock_ops.py, actions.py, main.py
|
||||
|
||||
tests/ — 93 Tests (Prompt-Quellen, Datei-/URL-Sicherheit mit gemockter DNS-Auflösung, Konfigurationsauflösung, CLI-Validierung, Docker-/HTTP-Operationen mit gemocktem subprocess/requests, File-Locking)
|
||||
tests/ — 130 Tests (Prompt-Quellen, Datei-/URL-Sicherheit mit gemockter DNS-Auflösung, Konfigurationsauflösung, CLI-Validierung, Docker-/HTTP-Operationen mit gemocktem subprocess/requests, File-Locking)
|
||||
|
||||
docs/SECURITY_AND_OPERATIONS.md — vollständige Architektur-, Konfigurations- und Sicherheitsdokumentation
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,16 @@ Besonderheiten gegenüber den ursprünglichen Shell-Skripten:
|
|||
|
||||
- `--change` nutzt eine exklusive Dateisperre (`/tmp/llamacppctl.<container_name>.lock`), damit zwei gleichzeitige Modellwechsel für denselben Container sich nicht in die Quere kommen.
|
||||
- Die Start-Bereitschaftsprüfung (`--start`/`--change`) wartet auf eine echte Chat-Completion-Antwort, nicht nur auf einen offenen Port — das ist zuverlässiger als ein reiner Port-Check.
|
||||
- `--dry-run` zeigt dir den vollständigen `docker run`-Befehl an, ohne ihn auszuführen — praktisch, um vor einer echten Änderung zu prüfen, was passieren würde.
|
||||
- `--dry-run` zeigt dir den vollständigen `docker run`-Befehl an, ohne ihn auszuführen (reine Vorschau: kein Lock, kein Container-Abriss).
|
||||
- `--change` validiert den Modellpfad **vor** dem Entfernen des laufenden Containers und läuft — wie `--start` — unter Lock; `--force` umgeht einen hängenden Lock.
|
||||
|
||||
Die vollständige Referenz zu allen Optionen steht in der Manpage (`man/llamacppctl.1`) und in `README.md`/`docs/SECURITY_AND_OPERATIONS.md` im Archiv.
|
||||
Neuere Optionen (Details in Manpage/README):
|
||||
|
||||
- **Antwortsteuerung:** `--max-tokens` (Reasoning-Modelle brauchen viel Budget, sonst leere Antwort), `--chat-temp`, und `--stream` für token-weise Live-Ausgabe bei `--chat`.
|
||||
- **Netzwerk/Auth:** Port wird per Default nur auf `127.0.0.1` veröffentlicht; `--expose` bindet auf alle Interfaces, `--api-key` schützt die Chat-API (Bearer-Token).
|
||||
- **Scriptbar:** `--check` liefert Exit-Code 0 (läuft/erreichbar) bzw. 5 — geeignet für Monitoring/Cron.
|
||||
- **`hf_home`** darf Env-Variablen enthalten, z. B. `hf_home = ${HF_HOME}`.
|
||||
- **End-to-End-Test:** `scripts/smoke.sh` (opt-in) gegen einen echten Server.
|
||||
|
||||
Die vollständige Referenz zu allen Optionen steht in der Manpage (`man/llamacppctl.1`) und in `README.md`/`docs/SECURITY_AND_OPERATIONS.md`.
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue