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>
This commit is contained in:
Dieter Schlüter 2026-07-07 11:17:41 +02:00
commit b8680cc049
5 changed files with 16 additions and 46 deletions

View file

@ -15,8 +15,8 @@ Fehlerbehandlung.
| Dokument | Für wen / wofür |
|---|---|
| README (diese Datei) | Schnelleinstieg: Installation, Konfiguration, Verwendung |
| [`BEDIENUNGSANLEITUNG.md`](BEDIENUNGSANLEITUNG.md) | Vollständiges Benutzerhandbuch (alle Aktionen, Optionen, Fehlersuche) |
| [`INSTALL_FROM_ARCHIVE.md`](INSTALL_FROM_ARCHIVE.md) | Installation aus dem `.tar.gz`-Archiv + Archiv selbst bauen/verifizieren |
| [`docs/BEDIENUNGSANLEITUNG.md`](docs/BEDIENUNGSANLEITUNG.md) | Vollständiges Benutzerhandbuch (alle Aktionen, Optionen, Fehlersuche) |
| [`docs/INSTALL_FROM_ARCHIVE.md`](docs/INSTALL_FROM_ARCHIVE.md) | Installation aus dem `.tar.gz`-Archiv + Archiv selbst bauen/verifizieren |
| [`docs/SECURITY_AND_OPERATIONS.md`](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`](CHANGELOG.md) | Versionshistorie |

View file

@ -73,8 +73,8 @@ REQUIRED_FILES = [
"tests/test_lock_ops.py",
"tests/test_actions.py",
"scripts/smoke.sh",
"BEDIENUNGSANLEITUNG.md",
"INSTALL_FROM_ARCHIVE.md",
"docs/BEDIENUNGSANLEITUNG.md",
"docs/INSTALL_FROM_ARCHIVE.md",
]
# Top-level directories to include wholesale (in addition to REQUIRED_FILES),
@ -90,8 +90,6 @@ INCLUDE_FILES = [
"requirements-dev.txt",
"llama.cpp.config.example",
"build_archive.py",
"BEDIENUNGSANLEITUNG.md",
"INSTALL_FROM_ARCHIVE.md",
]
EXCLUDE_DIR_NAMES = {"__pycache__", ".pytest_cache", ".venv", "venv", ".git", "*.egg-info"}
@ -124,14 +122,15 @@ def verify_required_files(project_dir: Path) -> None:
def verify_dependencies_declared(project_dir: Path) -> list:
"""Parses pyproject.toml and cross-checks it against requirements.txt.
"""Parses pyproject.toml and returns the declared runtime dependency
specifiers.
Returns the list of runtime dependency specifiers declared in
pyproject.toml. Raises BuildError on any mismatch.
pyproject.toml is the single source of truth for dependencies
(requirements.txt merely installs the package), so there is no requirements
mirror to cross-check. Raises BuildError if none are declared.
"""
log("Verifying declared dependencies are consistent...")
log("Reading declared runtime dependencies from pyproject.toml...")
pyproject_path = project_dir / "pyproject.toml"
requirements_path = project_dir / "requirements.txt"
try:
import tomllib # Python 3.11+
@ -171,37 +170,7 @@ def verify_dependencies_declared(project_dir: Path) -> list:
if not deps:
raise BuildError("No runtime dependencies found in pyproject.toml [project.dependencies]")
req_text = requirements_path.read_text(encoding="utf-8")
req_names = set()
for line in req_text.splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
# crude package-name extraction, e.g. "requests>=2.31,<3" -> "requests"
name = line
for sep in (">=", "<=", "==", "!=", ">", "<", "~="):
if sep in name:
name = name.split(sep, 1)[0]
req_names.add(name.strip().lower())
missing_from_requirements = []
for dep in deps:
name = dep
for sep in (">=", "<=", "==", "!=", ">", "<", "~="):
if sep in name:
name = name.split(sep, 1)[0]
name = name.strip().lower()
if name not in req_names:
missing_from_requirements.append(dep)
if missing_from_requirements:
raise BuildError(
"pyproject.toml declares dependencies not mirrored in requirements.txt:\n "
+ "\n ".join(missing_from_requirements)
)
log(f" pyproject.toml dependencies: {deps}")
log(" requirements.txt is consistent with pyproject.toml.")
return deps

283
docs/BEDIENUNGSANLEITUNG.md Normal file
View file

@ -0,0 +1,283 @@
# Bedienungsanleitung — llamacppctl
`llamacppctl` startet, prüft, stoppt, wechselt und promptet einen
**llama.cpp-Server, der als Docker-Container** läuft — über **eine** CLI statt
mehrerer Shell-Skripte. Die OpenAI-kompatible API des Servers ist standardmäßig
nur auf `127.0.0.1` erreichbar.
Diese Anleitung deckt **Installation** und **Benutzung** vollständig ab. Zur
Installation aus dem fertigen Archiv siehe zusätzlich
[`INSTALL_FROM_ARCHIVE.md`](INSTALL_FROM_ARCHIVE.md). Details zum Sicherheits-
und Betriebsmodell stehen in [`SECURITY_AND_OPERATIONS.md`](SECURITY_AND_OPERATIONS.md);
die vollständige Optionsreferenz in der Manpage (`man/llamacppctl.1`).
---
## 1. Voraussetzungen
- **Docker** (Daemon läuft, Nutzer in der `docker`-Gruppe): `docker version` muss funktionieren.
- **NVIDIA-GPU** + Treiber + **NVIDIA Container Toolkit** (damit `--gpus` im Container greift): `nvidia-smi` funktioniert, und `docker run --rm --gpus all <cuda-image> nvidia-smi` zeigt die GPU.
- **Python ≥ 3.10**.
- Ein **GGUF-Modell** auf dem Host, das read-only in den Container gemountet wird.
- Das Docker-Image, z. B. `ghcr.io/ggml-org/llama.cpp:server-cuda` (wird beim ersten Start automatisch gezogen).
---
## 2. Installation
### 2.1 Aus dem Quellcode (empfohlen für Entwicklung)
```bash
cd llamacppctl
python3 -m venv .venv
source .venv/bin/activate
pip install -e . # editable: CLI == Quellcode in src/
# optional für Tests:
pip install -e ".[dev]"
```
> **Wichtig:** Bei `pip install .` (ohne `-e`) läuft das CLI aus einer Kopie in
> `site-packages` — Änderungen an `src/` wirken dann NICHT. Für Entwicklung immer
> `-e` verwenden. Kontrolle: `python -c "import llamacppctl.actions as a; print(a.__file__)"`
> muss auf `src/` zeigen.
### 2.2 Aus dem Archiv
Siehe [`INSTALL_FROM_ARCHIVE.md`](INSTALL_FROM_ARCHIVE.md).
### 2.3 Installation prüfen (ohne Docker)
```bash
llamacppctl --help
llamacppctl --print-effective-config --config llama.cpp.config --start --dry-run
```
---
## 3. Konfiguration
### 3.1 Datei anlegen
```bash
cp llama.cpp.config.example llama.cpp.config
# llama.cpp.config an den Host anpassen (Pfade, GPU, Ports)
```
Der Standard-Pfad ist `llama.cpp.config` im aktuellen Verzeichnis, überschreibbar
mit `--config <pfad>`.
### 3.2 Aufbau (INI)
- `[default]` — globale Werte, überschreiben die eingebauten Defaults.
- `[model.<name>]` — ein Modell-Profil, gewählt mit `--profile <name>`.
- `[prompt.<name>]` — ein *Fallback*-Systemprompt, gewählt mit `--system-prompt-profile <name>` (ein explizites `-s`/`--system-file`/`--system-url` gewinnt immer).
**Auflösungsreihenfolge** (niedrig → hoch): eingebaute Defaults → `[default]` →
`[model.<profile>]` → CLI-Overrides.
### 3.3 Wichtige Felder
| Feld / CLI | Bedeutung |
|---|---|
| `image` / `--image` | Docker-Image (z. B. `ghcr.io/ggml-org/llama.cpp:server-cuda`) |
| `hf_home` / `--hf-home` | Host-Verzeichnis, read-only gemountet. **Env-Variablen erlaubt**, z. B. `hf_home = ${HF_HOME}` |
| `model_path` / `--model-path` | Modellpfad, relativ zu `hf_home` (oder absolut) |
| `container_name` / `--container-name` | **Pflicht**, eindeutig pro parallel laufendem Profil (Identitätsanker für Naming/Lock/Stop/Check) |
| `host_port` / `--host-port` | Host-Port (1–65535) |
| `container_port` / `--container-port` | Container-interner Port |
| `model_alias` / `--model-alias` | Modell-Alias in der API |
| `gpu_device` / `--gpu` | GPU-Device für `--gpus device=<id>` (auch mehrere: `1,2`) |
| `ctx_size` / `--ctx-size` | Kontextfenster (KV-Cache wächst damit) |
| `n_predict` / `--n-predict` | Server-seitige max. Vorhersage-Tokens |
| `temp`, `top_p`, `top_k`, `min_p`, `repeat_penalty` | Sampling (beim Start in den Container gebacken) |
| `expose` / `--expose` / `--no-expose` | Port auf allen Interfaces (LAN) statt nur `127.0.0.1`. **Default: aus** |
| `api_key` / `--api-key` | API-Key: Server verlangt ihn, Tool sendet ihn als Bearer-Token |
| `max_tokens` / `--max-tokens` | Antwortbudget für `--chat`/`--start`-Antwort (Default 2048) |
| `chat_temperature` / `--chat-temp` | Temperatur des Chat-Requests (leer = Server-`temp`) |
| `timeout` / `--timeout` | Readiness-Timeout in Sekunden |
| `reasoning` / `--reasoning {on,off}` | Reasoning-Modus |
Boolean-Schalter mit Paar-Form: `--jinja/--no-jinja`, `--fa/--no-fa`,
`--kv-unified/--no-kv-unified`, `--cont-batching/--no-cont-batching`,
`--no-context-shift/--context-shift`.
### 3.4 Beispiel
```ini
[default]
image = ghcr.io/ggml-org/llama.cpp:server-cuda
hf_home = ${HF_HOME}
model_path = models/qwen3/mein-modell-Q4_K_M.gguf
model_alias = mein_llm
container_name = llama_cpp_server
host_port = 8001
gpu_device = 1
ctx_size = 65536
max_tokens = 8192
[prompt.knapp]
system_prompt = Antworte kurz, präzise und technisch.
[model.zweitmodell]
model_path = models/qwen3/anderes-modell.gguf
model_alias = zweit_llm
container_name = llama_cpp_zweit
host_port = 8002
gpu_device = 2
```
---
## 4. Die fünf Aktionen
Genau **eine** Aktion pro Aufruf ist erforderlich.
### `--start` — Server starten
Entfernt einen vorhandenen Container gleichen Namens, startet neu, wartet per
echter Chat-Completion auf Readiness (nicht nur offener Port). Läuft unter Lock.
```bash
llamacppctl --start
llamacppctl --start --profile zweitmodell
llamacppctl --start -p "Sag kurz Hallo." # + Testantwort nach dem Start
llamacppctl --start --dry-run # nur das docker-run-Kommando zeigen
```
### `--check` — Status prüfen
Zeigt Container-/HTTP-Status; mit User-Prompt zusätzlich eine echte Chat-Probe.
**Scriptbar:** Exit 0 = läuft & erreichbar, sonst 5.
```bash
llamacppctl --check
llamacppctl --check -p "ping" && echo "OK" || echo "nicht bereit"
```
### `--stop` — Container stoppen und entfernen
```bash
llamacppctl --stop
llamacppctl --stop --force # auch bei Inkonsistenzen entfernen
```
### `--change` — Modell/Config wechseln
Stoppt, konfiguriert neu, startet — unter Lock. **Validiert den Modellpfad,
bevor** der laufende Container entfernt wird (kein unnötiger Ausfall).
```bash
llamacppctl --change --profile zweitmodell
llamacppctl --change --model-path models/qwen3/neues-modell.gguf --ctx-size 32768
```
### `--chat` — an laufenden Server prompten
```bash
llamacppctl --chat -p "Was ist 2+2?"
llamacppctl --chat -s "Du antwortest nur mit Zahlen." -p "Wie viel ist 6*7?"
llamacppctl --chat --stream -p "Schreibe eine kurze Geschichte."
```
---
## 5. Prompts (System & User)
Pro Feld genau **eine** Quelle:
| System-Prompt | User-Prompt | Quelle |
|---|---|---|
| `-s`, `--system` | `-p`, `--prompt` | Literaler Text |
| `--system-file` | `--prompt-file` | Lokale Datei (nur reguläre Dateien, UTF-8, Größenlimit) |
| `--system-url` | `--prompt-url` | HTTPS-URL (SSRF-gehärtet) |
```bash
llamacppctl --chat --system-file ./prompts/reviewer.md --prompt-file ./frage.txt
llamacppctl --start --system-prompt-profile knapp -p "Erkläre Backups."
```
URL-Quellen sind standardmäßig HTTPS-only, blockieren private/loopback/metadata-
IPs und Redirects; Details in `docs/SECURITY_AND_OPERATIONS.md`.
---
## 6. Antwortsteuerung & Reasoning
- `--max-tokens N` — Budget der Antwort (Default 2048).
- `--chat-temp F` — Temperatur des Requests (sonst gilt die Server-`temp`).
- `--stream` — Tokens live auf stdout, während sie generiert werden.
> **Reasoning-Modelle** (z. B. Qwen3.x mit `reasoning = on`) „denken" erst
> intern (oft mehrere tausend Tokens) und geben **dann** die eigentliche
> Antwort. Ist `--max-tokens` zu klein, endet die Generierung im Denken und der
> sichtbare `content` bleibt **leer** — das Tool warnt dann
> (`finish_reason=length`). Für lange Texte großzügig setzen (z. B. `--max-tokens 8000`).
---
## 7. Netzwerk & Sicherheit (Kurz)
- Der Port wird per Default nur auf **`127.0.0.1`** veröffentlicht — die
unauthentifizierte API ist **nicht** im LAN erreichbar.
- Für LAN-Zugriff: `--expose`/`expose = true`. **Dann unbedingt** `--api-key`/`api_key` setzen.
- Mit Key schützt llama.cpp `/v1/chat/completions` (ohne Key → 401); `/health`
und `/v1/models` bleiben bewusst offen (für Health-Checks).
- `hf_home` wird **read-only** gemountet; alle Docker-Aufrufe nutzen
Argumentlisten (keine Shell-Injection).
---
## 8. GPU & Kontextgröße
- Ein Modell muss samt KV-Cache in den GPU-Speicher passen. Der KV-Cache wächst
linear mit `ctx_size` und wird beim Laden komplett allokiert.
- Mehrere GPUs: `gpu_device = 1,2` (bzw. `--gpu 1,2`) verteilt das Modell.
- Vor einer Änderung mit `--dry-run` das Kommando prüfen, mit
`--print-effective-config` die aufgelösten Werte kontrollieren.
---
## 9. Exit-Codes
| Code | Bedeutung |
|---|---|
| 0 | Erfolg (bzw. `--check`: läuft & erreichbar) |
| 1 | Allgemeiner Fehler / leere Antwort trotz Budget |
| 2 | CLI-/Argument-Fehler |
| 3 | Modelldatei nicht gefunden |
| 4 | Docker-Fehler |
| 5 | HTTP/Readiness nicht bereit (auch `--check`, wenn nicht erreichbar) |
| 6 | Lock belegt (anderer `--start`/`--change` läuft; ggf. `--force`) |
---
## 10. Fehlersuche
| Symptom | Ursache | Lösung |
|---|---|---|
| `docker is not available on PATH` | Docker-CLI/-Daemon fehlt | Docker installieren/starten, Nutzer in `docker`-Gruppe |
| `model file not found: …` | `model_path`/`hf_home` falsch | Pfade prüfen; `${HF_HOME}` gesetzt? |
| `Server did not become ready within <n>s` | Modell lädt langsam / OOM | `--timeout` erhöhen, `--logs` für Log-Tail, ggf. `ctx_size` senken |
| Leere Antwort + Hinweis `finish_reason=length` | Reasoning verbraucht das Budget | `--max-tokens` erhöhen |
| `HTTP 401: Invalid API Key` | Server verlangt Key | `--api-key`/`api_key` passend setzen |
| `lock busy: …` | Paralleler `--start`/`--change` | warten oder `--force` (hängender Lock) |
| `--check` zeigt `health=unhealthy`, Server antwortet aber | bildeigener Docker-Healthcheck ≠ HTTP-Probe | unkritisch: `HTTP: OK` + Exit 0 zählt |
---
## 11. Tests
```bash
pip install -e ".[dev]"
pytest -q # Unit-Tests (kein Docker nötig)
```
End-to-End gegen einen echten Server (opt-in, braucht Docker + GPU + Modell):
```bash
SMOKE_GPU=1 scripts/smoke.sh
```
---
## 12. Weiterführend
- `man/llamacppctl.1` — vollständige Optionsreferenz (`man ./man/llamacppctl.1`).
- `docs/SECURITY_AND_OPERATIONS.md` — Architektur, Sicherheits- und Betriebsmodell.
- `README.md` — Kurzüberblick.
- `docs/INSTALL_FROM_ARCHIVE.md` — Installation aus dem `.tar.gz`-Archiv.

View file

@ -0,0 +1,114 @@
# Installation aus dem Archiv — llamacppctl
Diese Anleitung beschreibt, wie du `llamacppctl` aus dem fertigen Archiv
`llamacppctl-installable.tar.gz` installierst. Für die vollständige Bedienung
siehe [`BEDIENUNGSANLEITUNG.md`](BEDIENUNGSANLEITUNG.md).
---
## 1. Voraussetzungen auf der Zielmaschine
- **Python ≥ 3.10** (`python3 --version`)
- **Docker** mit erreichbarem Daemon (`docker version`)
- **NVIDIA-GPU** + Treiber + **NVIDIA Container Toolkit** (für `--gpus`)
- Ein **GGUF-Modell** auf dem Host
> Für einen reinen Konfigurations-/Trockenlauf (`--print-effective-config`,
> `--dry-run`) reicht Python; Docker/GPU/Modell werden erst für echte
> `--start`/`--check`/`--chat`-Aufrufe gebraucht.
---
## 2. Integrität prüfen (optional, empfohlen)
```bash
gzip -t llamacppctl-installable.tar.gz && echo "Archiv OK"
tar tzf llamacppctl-installable.tar.gz | head # Inhalt ansehen
```
Das Archiv enthält ausschließlich Quellcode, Tests, Doku, Manpage,
`llama.cpp.config.example` und `scripts/smoke.sh` — **keine** persönliche
`llama.cpp.config`, keine `.venv`, keine `.git`-Historie und keine Geheimnisse.
---
## 3. Entpacken und installieren
```bash
tar xzf llamacppctl-installable.tar.gz
cd llamacppctl
python3 -m venv .venv
source .venv/bin/activate
pip install .
```
Danach steht das Konsolen-Kommando `llamacppctl` zur Verfügung:
```bash
llamacppctl --help
```
> Zum Entwickeln stattdessen `pip install -e ".[dev]"` (editable + Tests).
---
## 4. Konfiguration anlegen
```bash
cp llama.cpp.config.example llama.cpp.config
# llama.cpp.config an den Host anpassen: hf_home (z. B. ${HF_HOME}),
# model_path, gpu_device, host_port, container_name …
```
Auflösung prüfen, ohne etwas zu starten:
```bash
llamacppctl --print-effective-config --config llama.cpp.config --start --dry-run
```
Das zeigt die vollständig aufgelöste Konfiguration als JSON **und** das
`docker run`-Kommando, das `--start` ausführen würde.
---
## 5. Erststart
```bash
llamacppctl --start --config llama.cpp.config
llamacppctl --check --config llama.cpp.config
llamacppctl --chat --config llama.cpp.config -p "Sag kurz Hallo."
```
Weitere Aktionen, Optionen und Fehlersuche: [`BEDIENUNGSANLEITUNG.md`](BEDIENUNGSANLEITUNG.md).
---
## 6. Tests (optional)
```bash
pip install -e ".[dev]"
python -m pytest -q
```
---
## 7. Archiv selbst neu bauen / verifizieren
Das Archiv wird reproduzierbar von `build_archive.py` erzeugt. Es
1. prüft, ob alle erforderlichen Projektdateien vorhanden sind,
2. gleicht die deklarierten Abhängigkeiten in einer frischen venv ab,
3. lässt die komplette Test-Suite laufen,
4. baut das `.tar.gz` (nur Allowlist: `src/ tests/ docs/ man/ scripts/` + `pyproject.toml`, `README.md`, `LICENSE`, `CHANGELOG.md`, `requirements*.txt`, `llama.cpp.config.example`, `build_archive.py`),
5. öffnet das Archiv erneut und verifiziert die enthaltenen Dateien,
6. installiert das Paket aus dem Archiv in einer weiteren frischen venv und testet das Konsolenskript.
```bash
python3 build_archive.py # -> ../llamacppctl-installable.tar.gz
python3 build_archive.py --output /pfad/x.tar.gz
python3 build_archive.py --skip-pip-check --skip-smoke-test # ohne Netz-Schritte
```
Weil `build_archive.py` selbst im Archiv liegt, lässt sich das Archiv jederzeit
unabhängig neu bauen und verifizieren.

View file

@ -43,9 +43,10 @@ scripts/smoke.sh Opt-in End-to-End-Rauchtest gegen echten Docker + GPU
`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
docs/BEDIENUNGSANLEITUNG.md Vollständiges Benutzerhandbuch
docs/INSTALL_FROM_ARCHIVE.md Installation aus dem Archiv + Archiv bauen/verifizieren
docs/SECURITY_AND_OPERATIONS.md Dieses Dokument (Architektur/Sicherheit/Betrieb)
```
## 2. Konfigurationsmodell