2026-07-06 16:36:27 +02:00
# llamacppctl — Sicherheits- und Betriebsdokumentation
Diese Datei beschreibt die Architektur, das Konfigurationsmodell, das
Sicherheitsmodell der Prompt-Eingabeschicht sowie den empfohlenen
Betriebsmodus auf einem dedizierten GPU-Host.
## 1. Architektur / Modulübersicht
```
src/llamacppctl/
cli.py Argumentparsing (argparse) + semantische Cross-Field-Validierung
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>
2026-07-06 17:32:46 +02:00
schema.py Dataclasses: ServerConfig, PromptConfig, ChatReply, CheckResult
2026-07-06 16:36:27 +02:00
config.py INI-Konfiguration laden/mergen -> ServerConfig/PromptConfig
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>
2026-07-06 17:32:46 +02:00
(inkl. ${ENV}- und ~-Expansion in hf_home)
2026-07-06 16:36:27 +02:00
prompt_io.py Sicherheitsgehärtete Eingabeschicht für System-/User-Prompts
docker_ops.py Alle Docker-Interaktionen (subprocess mit Argumentlisten)
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>
2026-07-06 17:32:46 +02:00
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
2026-07-06 16:36:27 +02:00
actions.py Orchestrierung: do_start, do_check, do_stop, do_change, do_chat
main.py Dünner Einstiegspunkt: parse -> validate -> resolve -> dispatch
```
Verantwortungstrennung: `prompt_io.py` kennt kein argparse und arbeitet nur
mit `PromptSource` + `InputPolicy` . `config.py` kennt keine Docker- oder
HTTP-Details. `docker_ops.py` /`http_ops.py` kennen keine CLI-Semantik. Diese
Trennung hält die sicherheitsrelevante Logik (Eingabevalidierung) unabhängig
testbar von der Orchestrierung.
2026-07-07 11:08:33 +02:00
### Repo-Inventur (über den Code hinaus)
```
tests/ Test-Suite (pytest; gemocktes subprocess/requests, DNS-Pinning)
man/llamacppctl.1 Unix-Manpage: vollständige Optionsreferenz
llama.cpp.config.example Konfigurationsvorlage -> nach llama.cpp.config kopieren
pyproject.toml Paket-/Build-Metadaten, dev-Extras, ruff-/mypy-Config
requirements.txt Laufzeit-Install (Zeiger auf das Paket; pyproject ist Quelle)
requirements-dev.txt Entwickler-Install (`-e .[dev]` )
build_archive.py Reproduzierbarer .tar.gz-Builder mit Selbstverifikation
scripts/check.sh Lokales CI-Gate: ruff + mypy + pytest
scripts/smoke.sh Opt-in End-to-End-Rauchtest gegen echten Docker + GPU
.githooks/pre-push Pre-Push-Hook (ruft scripts/check.sh) — aktivieren via
`git config core.hooksPath .githooks`
.forgejo/workflows/ci.yml Forgejo-Actions-CI (läuft, sobald ein Runner registriert ist)
README.md Schnelleinstieg + Doku-Index
BEDIENUNGSANLEITUNG.md Vollständiges Benutzerhandbuch
INSTALL_FROM_ARCHIVE.md Installation aus dem Archiv + Archiv bauen/verifizieren
CHANGELOG.md / LICENSE Versionshistorie / MIT-Lizenz
```
2026-07-06 16:36:27 +02:00
## 2. Konfigurationsmodell
`llama.cpp.config` ist eine Standard-INI-Datei (`configparser` ). Auflösung
(niedrigste zu höchster Priorität):
1. **Eingebaute Defaults ** (`config.builtin_defaults()` ) — spiegeln die
Standardwerte der ursprünglichen Shell-Skripte wider (Image
2026-07-07 10:28:23 +02:00
`ghcr.io/ggml-org/llama.cpp` per Digest gepinnt für Reproduzierbarkeit,
`host_port=8001` ,
2026-07-06 16:36:27 +02:00
`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` ,
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>
2026-07-06 17:32:46 +02:00
`timeout=300` , `poll_interval=2` , `expose=false` , `api_key=` ,
`max_tokens=2048` , `chat_temperature=` , …).
2026-07-06 16:36:27 +02:00
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.
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>
2026-07-06 17:32:46 +02:00
`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.
2026-07-06 16:36:27 +02:00
`[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`
gewinnt in jedem Fall.
### `container_name` ist verpflichtend
`config.build_server_config()` wirft einen `ConfigError` , wenn
`container_name` nach vollständiger Auflösung leer oder nur aus Whitespace
besteht. Grund: `container_name` ist der **einzige Identitätsanker ** für:
- Docker-Container-Benennung (`docker run --name <container_name>` )
- Ziel für `--stop` /--check`
- Ableitung des Lock-Pfads (`/tmp/llamacppctl.<container_name>.lock` ),
sofern `--lock-file` /`lock_file` nicht explizit gesetzt ist
Wer mehrere `[model.<name>]` -Profile definiert, die **gleichzeitig ** als
separate Container laufen sollen (z. B. zwei Modelle auf zwei GPUs), **muss **
jedem Profil einen eigenen, eindeutigen `container_name` geben. Andernfalls
würde ein zweiter `--start` den ersten Container unbeabsichtigt ersetzen
(`start_llama_container()` entfernt einen vorhandenen Container gleichen
Namens, bevor es einen neuen startet).
Eine leere `--container-name` -Angabe auf der Kommandozeile wird bereits von
`cli.validate_args()` zurückgewiesen (Exit-Code 2, bevor überhaupt eine
Konfigurationsdatei gelesen wird). Eine leere Angabe **in der Config-Datei **
selbst (z. B. `container_name =` ohne Wert) wird erst bei der
Konfigurationsauflösung als `ConfigError` erkannt.
## 3. Sicherheitsmodell der Prompt-Eingabeschicht (`prompt_io.py`)
Jedes der beiden Eingabefelder (System-Prompt, User-Prompt) akzeptiert genau
**eine** von drei Quellenarten, gegenseitig ausschließend über
argparse-Mutually-Exclusive-Groups **und ** eine zweite Verteidigungslinie in
`resolve_source()` :
- **Literal** (`-s` /`-p` ): Text direkt auf der Kommandozeile.
- **Datei** (`--system-file` /--prompt-file`): lokaler Dateipfad.
- **URL** (`--system-url` /--prompt-url`): entfernte HTTPS(S)-Ressource.
### 3.1 Datei-Quellen
`load_text_file()` :
- Nur reguläre Dateien (`path.is_file()` ); Verzeichnisse etc. werden
abgelehnt.
- Symlinks werden standardmäßig abgelehnt und sind nur über `--allow-symlinks`
freischaltbar (`InputPolicy.allow_symlinks` , Default `False` ).
- Größenlimit `--max-input-bytes` (Default 1 MiB) wird **vor ** dem Lesen
anhand von `stat().st_size` und **nach ** der Normalisierung anhand der
UTF-8-kodierten Zeichenlänge geprüft.
- Binärinhalte (NUL-Byte in den Rohbytes) werden abgelehnt.
- Dekodierung: UTF-8, mit Fallback auf `utf-8-sig` (BOM); ungültige
UTF-8-Daten führen zu einem `PromptSourceError` .
- Zeilenenden werden normalisiert (`\r\n` /`\r` -> `\n` ), ein führendes BOM
wird entfernt.
### 3.2 URL-Quellen (SSRF-Härtung)
`validate_url_target()` wird vor **jedem ** HTTP-Request ausgeführt —
einschließlich jedem einzelnen Redirect-Hop, falls Redirects aktiviert sind:
1. Nur `https://` standardmäßig erlaubt; `http://` erfordert explizit
`--allow-insecure-http` .
2. Kein Hostname -> Ablehnung.
3. Eingebettete Zugangsdaten in der URL (`user:pass@host` ) -> Ablehnung.
4. Optionale Host-Allowlist (`--url-allow-host` , wiederholbar): falls
gesetzt, muss der Hostname exakt (case-insensitiv) enthalten sein.
5. `localhost` als Hostname wird immer abgelehnt.
6. IP-Literale als Hostname (`https://127.0.0.1/...` ) werden standardmäßig
abgelehnt; `--allow-ip-host` erlaubt sie explizit.
7. Alle DNS-Antworten für den Hostnamen werden aufgelöst
(`socket.getaddrinfo` ); **jede ** zurückgegebene Adresse wird klassifiziert
(`ipaddress` -Modul: `is_private` , `is_loopback` , `is_link_local` ,
`is_multicast` , `is_reserved` , `is_unspecified` ). Trifft **eine ** dieser
Eigenschaften zu, wird die Anfrage abgelehnt (Rebinding-Schutz: es reicht,
dass irgendeine aufgelöste Adresse privat ist).
8. Die Cloud-Metadata-Adresse `169.254.169.254` wird explizit zusätzlich
geprüft und immer blockiert, sofern nicht `--allow-private-url` gesetzt
ist.
9. `--allow-private-url` deaktiviert die IP-Klassifikationsprüfungen 7 und 8
komplett (bewusste Eskalation für vertrauenswürdige interne Ziele) und
kann **nicht ** gleichzeitig mit `--url-allow-host` verwendet werden
(widersprüchliche Sicherheitsmodelle: „alles erlauben“ vs. „nur
bestimmte Hosts“).
Nach erfolgreicher Zielvalidierung (`load_text_url()` ):
- Anfrage mit `stream=True` , `allow_redirects=False` (Redirects werden
manuell behandelt, damit jeder Hop erneut validiert wird), separaten
Connect-/Read-Timeouts (`--connect-timeout` , `--read-timeout` ).
- Redirect-Statuscodes (3xx) werden nur verfolgt, wenn `--follow-redirects`
gesetzt ist; sonst Ablehnung. Die maximale Anzahl Hops ist begrenzt
(`InputPolicy.max_redirects` , Default 3).
- Content-Type-Allowlist: nur Text-artige Typen
(`text/plain` , `text/markdown` , `text/csv` , `application/json` ,
`application/xml` , `application/yaml` , …); `text/html` nur mit
`--allow-html-input` .
- Größenlimit wird sowohl über den `Content-Length` -Header **als auch **
während des Streamens (`iter_content` ) durchgesetzt, sodass ein Server, der
einen falschen (zu niedrigen) `Content-Length` -Header sendet, keinen
Speicher-Erschöpfungsangriff durchführen kann.
- Antwort muss gültiges UTF-8 sein.
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>
2026-07-06 17:32:46 +02:00
- **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.
2026-07-06 16:36:27 +02:00
### 3.3 Warum das wichtig ist
Diese Härtung verhindert, dass `--system-url` /--prompt-url` als
SSRF-Vektor missbraucht werden kann, um interne Dienste (z. B.
Docker-Socket-Proxies, interne Admin-APIs, Cloud-Metadata-Endpunkte) vom
GPU-Host aus zu erreichen, falls `llamacppctl` jemals in einem Kontext
läuft, in dem die übergebene URL nicht vollständig vertrauenswürdig ist
(z. B. durch ein Webhook, ein anderes Automatisierungs-Skript oder einen
mehrbenutzerfähigen Wrapper um `llamacppctl` ).
## 4. Docker- / Host-Vertrauensmodell
- `docker_ops.py` ruft ausschließlich `subprocess.run(["docker", ...])` mit
**Argumentlisten ** auf, niemals mit `shell=True` oder zusammengesetzten
Shell-Strings. Es gibt daher keine Shell-Injection-Fläche über
Konfigurationswerte oder CLI-Argumente.
- `llamacppctl` vertraut dem lokalen Docker-Daemon vollständig (wie jedes
Docker-CLI-Tool) — es ist kein Sandboxing gegenüber Docker selbst
vorgesehen. Wer `llamacppctl` einsetzt, muss dem Nutzerkonto, das den
Befehl ausführt, den gleichen Vertrauensgrad einräumen wie direktem
Docker-CLI-Zugriff (i. d. R. Mitgliedschaft in der `docker` -Gruppe).
- `HF_HOME` wird **read-only ** (`:ro` ) in den Container gemountet — der
Container kann Modelldateien lesen, aber nicht verändern oder löschen.
- GPU-Zuweisung erfolgt über `--gpus device=<gpu_device>` , sodass mehrere
Profile gezielt auf unterschiedliche GPUs (z. B. RTX 3090 #1/ #2 ) gepinnt
werden können.
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>
2026-07-06 17:32:46 +02:00
### 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`)
2026-07-06 16:36:27 +02:00
`lock_ops.FileLock` verwendet `fcntl.flock(LOCK_EX | LOCK_NB)` auf einer
Lock-Datei unter `/tmp/llamacppctl.<container_name>.lock` (Default, ableitbar
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>
2026-07-06 17:32:46 +02:00
ü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.
2026-07-06 16:36:27 +02:00
## 6. `--dry-run` und `--print-effective-config` als Sicherheitswerkzeuge
- `--print-effective-config` gibt die vollständig aufgelöste Konfiguration
(Server- und Prompt-Konfiguration) als JSON aus, **bevor ** irgendeine
Docker- oder HTTP-Aktion ausgeführt wird. Damit lässt sich prüfen, welche
Werte aus welcher Quelle (Defaults/[default]/[model.*]/CLI) tatsächlich
gewonnen haben, ohne einen Container anzufassen.
- `--dry-run` (in Kombination mit `--start` /--change`) zeigt den vollständig
zusammengesetzten `docker run` -Befehl (Shell-quotiert zur Anzeige) an,
**ohne ** ihn auszuführen. Empfohlen vor jeder Änderung an einer
Produktionskonfiguration, insbesondere nach Anpassungen an
`llama.cpp.config` .
Beide Flags erfordern **keinen ** erreichbaren Docker-Daemon für ihre reine
Ausgabe — `main.run()` prüft `docker_available()` derzeit vor der
Konfigurationsauflösung; auf einem Host ganz ohne Docker (z. B. zur reinen
Konfigurationsvalidierung) schlägt der Aufruf entsprechend mit einer
expliziten Fehlermeldung fehl statt still falsche Annahmen zu treffen.
## 7. Betriebsmodell auf einem dedizierten GPU-Host
Empfohlenes Setup (passend zu einer Zwei-GPU-Umgebung, z. B. 2× RTX 3090):
```ini
[default]
hf_home = /srv/models
gpu_device = 0
container_name = llama_cpp_server
host_port = 8001
2026-07-07 09:53:43 +02:00
[model.qwen35b]
container_name = llama_cpp_qwen35b
2026-07-06 16:36:27 +02:00
gpu_device = 1
host_port = 8002
[model.deepseek]
container_name = llama_cpp_deepseek
gpu_device = 1
host_port = 8003
ctx_size = 131072
```
- Jedes Profil, das parallel laufen soll, bekommt eine eigene
`container_name` /`host_port` -Kombination.
- `--profile` wählt bei jedem Aufruf explizit aus, welches Profil
angesprochen wird — es gibt keinen impliziten "aktuellen" Zustand außer
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
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>
2026-07-06 17:32:46 +02:00
(`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).
2026-07-06 16:36:27 +02:00
## 8. Installation, Nutzung, Fehlersuche
Siehe [`README.md` ](../README.md ) für Installationsschritte und
Nutzungsbeispiele.
Häufige Fehlerbilder:
| Symptom | Ursache | Lösung |
|---|---|---|
| `docker is not available on PATH` | Docker-CLI fehlt oder Daemon nicht erreichbar | Docker installieren/starten, Nutzer zur `docker` -Gruppe hinzufügen |
| `container_name is mandatory and must not be empty` | Kein `container_name` in `[default]` /`[model.*]` /CLI gesetzt | `container_name` in der Config oder via `--container-name` setzen |
| `model profile not found: <name>` | `--profile <name>` verweist auf nicht existierende `[model.<name>]` -Sektion | Sektionsname prüfen/anlegen |
| `http requires --allow-insecure-http` | `--system-url` /--prompt-url` nutzt ` http://` | Auf ` https://` wechseln oder bewusst ` --allow-insecure-http` setzen |
| `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 |
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>
2026-07-06 17:32:46 +02:00
| `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 |