web-ui
This commit is contained in:
parent
796a63671b
commit
571652f480
@ -11,6 +11,12 @@ ALLOWED_NETS="127.0.0.0/8 ::1 192.168.178.0/24 100.73.0.0/16"
|
||||
LISTEN_PORT=8800
|
||||
BACKEND_PORT=8080
|
||||
|
||||
# Web-Chat (Open WebUI): Port nach außen (Caddy) und intern (nur 127.0.0.1)
|
||||
WEBUI_PORT=8801
|
||||
WEBUI_BACKEND_PORT=3000
|
||||
# Schlüssel für Login-Sitzungen: openssl rand -hex 32
|
||||
WEBUI_SECRET_KEY=CHANGE_ME
|
||||
|
||||
# Host-Nutzer, dem die Modelldateien in ./models gehören: id -u / id -g
|
||||
HOST_UID=1000
|
||||
HOST_GID=1000
|
||||
@ -33,3 +39,4 @@ LLAMA_SWAP_TAG=v265-rocm-b11610
|
||||
# Quelle für whisper-server/sd-server (gleiche llama-swap-Version wie oben wählen)
|
||||
UNIFIED_TAG=unified-vulkan-265
|
||||
CADDY_TAG=2.10.2-alpine
|
||||
OPEN_WEBUI_TAG=v0.11.4
|
||||
|
||||
72
PLAN-cluster.md
Normal file
72
PLAN-cluster.md
Normal file
@ -0,0 +1,72 @@
|
||||
# Plan: NVIDIA-Windows-Rechner als flexibler Cluster hinter dem bestehenden Gateway
|
||||
|
||||
## Context
|
||||
Mehrere Windows-PCs mit NVIDIA-GPUs sollen zusätzliche Rechenleistung liefern. Der bestehende
|
||||
Linux-PC (AMD, Docker: Caddy + llama-swap) bleibt der **einzige Endpunkt** (:8800, gleicher API-Key).
|
||||
**Jeder Knoten kann jedes Cluster-Modell ausführen** und wechselt bei Bedarf (llama-swap). Linux ist
|
||||
auf den Knoten nicht nötig: llama-swap (`llama-swap_265_windows_amd64.zip`) und llama.cpp
|
||||
(`llama-bXXXXX-bin-win-cuda-{12.4|13.4}-x64.zip` + `cudart-…`) gibt es fertig für Windows.
|
||||
|
||||
Erkenntnisse aus der Recherche:
|
||||
- llama-swap v265 leitet über `peers` pro Modell an **einen** Server weiter (URL-Pfad wird angehängt). Das Load-Balancing übernimmt deshalb Caddy.
|
||||
- llama-swap löst `${env.VAR}` in der Config auf, also kommen keine Secrets ins Git.
|
||||
- Ältere llama.cpp-Builds werden gelöscht (b11610 gibt es schon nicht mehr). Der Installer wählt den Build über die GitHub-API (`latest` oder fest).
|
||||
- Caddy auf dem Host-Netz ist für den Container wegen ufw schwer erreichbar. Deshalb läuft der Loadbalancer als **eigener Container im Compose-Netz**.
|
||||
|
||||
## Architektur
|
||||
```
|
||||
Client ─► Caddy :8800 (IP-Filter, API-Key) ─► llama-swap (AMD, lokal)
|
||||
│ lokale Modelle wie bisher
|
||||
└─ peers: cluster-<m> ─► http://cluster-lb:8090/m/cluster-<m>
|
||||
cluster-lb (Caddy-Container, generiert):
|
||||
/m/cluster-qwen3.5-35b-a3b/* ─► Knoten [A, B, C] (Stammknoten A)
|
||||
/m/cluster-glm-4.7-flash/* ─► Knoten [B, C, A] (Stammknoten B)
|
||||
/m/cluster-qwen3.5-9b/* ─► Knoten [C, A, B] (Stammknoten C)
|
||||
Win-Knoten: llama-swap.exe :8080 mit ALLEN cluster-Modellen, API-Key, Firewall nur Gateway-IP
|
||||
```
|
||||
**Routing ohne unnötige Modellwechsel:** Für jedes Modell enthält der Pool alle Knoten, aber in rotierter Reihenfolge. Dazu kommen `lb_policy first` und `unhealthy_request_count <Slots>`:
|
||||
- Eine Anfrage geht an den Stammknoten des Modells. Dort ist das Modell meist schon geladen und der Prompt-Cache noch vorhanden.
|
||||
- Ist der Stammknoten belegt oder ausgefallen, geht sie an den nächsten Knoten. Der lädt das Modell dann, das ist der flexible Fall.
|
||||
- Sind alle Knoten belegt, wartet Caddy (`lb_try_duration 5m`, `lb_try_interval 1s`), bis einer frei wird.
|
||||
- Health-Checks auf `/health` alle 10 s, ausgefallene Knoten werden übersprungen.
|
||||
- Wenn es mehr Modelle als Knoten gibt, teilen sich Modelle einen Stammknoten (Zuordnung reihum).
|
||||
|
||||
## Änderungen auf dem Gateway (Linux)
|
||||
1. **[docker-compose.yml](docker-compose.yml)**: neuer Service `cluster-lb` (`caddy:${CADDY_TAG}`), nur im Compose-Netz und ohne Port nach außen. Er mountet `caddy/cluster.Caddyfile`. `llama-swap` bekommt `CLUSTER_API_KEY` als Umgebungsvariable.
|
||||
2. **Neu `scripts/cluster-gen.sh`**: liest `CLUSTER_NODES="192.168.178.50:8080 192.168.178.51:8080 …"` und `CLUSTER_SLOTS` (Standard 1) aus `.env`. Die Modellliste kommt aus `cluster/windows/node-config.yaml` (einzige Quelle, Schlüssel `cluster-*`). Daraus erzeugt das Skript `caddy/cluster.Caddyfile` (in `.gitignore`), mit einem `handle_path /m/<modell>/*` und rotierter Upstream-Liste pro Modell. Ohne Knoten entsteht eine gültige Datei, die 503 liefert. [scripts/start.sh](scripts/start.sh) ruft das Skript vor `docker compose up` auf.
|
||||
3. **[llama-swap/config.yaml](llama-swap/config.yaml)**: Block `peers:` mit einem Eintrag pro Cluster-Modell, `proxy: http://cluster-lb:8090/m/<modell>`, `apiKey: ${env.CLUSTER_API_KEY}`, dazu großzügige `timeouts.responseHeader` für Modellwechsel und Download.
|
||||
4. **`.env.example` / `.env`**: `CLUSTER_API_KEY` (start.sh erzeugt ihn wie `LLM_API_KEY`, wenn er fehlt), `CLUSTER_NODES`, `CLUSTER_SLOTS`.
|
||||
5. **Neu `scripts/cluster-status.sh`**: zeigt pro Knoten `/health`, das geladene Modell (`/running`) und die Antwortzeit. start.sh zeigt die Ausgabe im Statusblock, wenn Knoten gesetzt sind.
|
||||
|
||||
## Windows-Knoten-Skripte (`cluster/windows/`)
|
||||
- **`install-node.ps1`** (als Admin; wiederholbar, dient auch zum Update):
|
||||
Parameter `-GatewayIP` und `-ApiKey` (Pflicht), `-Port 8080`, `-InstallDir C:\localai-node`, `-LlamaBuild latest`, `-Cuda auto|12.4|13.4`, `-Preload <modell>` (optional), `-HfToken`, `-KeepSleep`, `-Force`.
|
||||
1. Prüft Admin-Rechte, `nvidia-smi` (GPU, VRAM, Compute Capability, Treiber) und freien Speicherplatz (Warnung unter 60 GB, weil alle Modelle auf jedem Knoten liegen können).
|
||||
2. CUDA-Variante: 13.4, wenn Compute Capability ≥ 7.5 und Treiber ≥ 580, sonst 12.4.
|
||||
3. Lädt llama-swap und llama.cpp CUDA + cudart herunter, entpackt sie nach `bin\` und testet `llama-server.exe --version`.
|
||||
4. Schreibt `config.yaml` aus der Vorlage. Bestehende Dateien bleiben unangetastet, außer bei `-Force`.
|
||||
5. Erzeugt `start-node.cmd`: setzt `LLAMA_CACHE`, `HF_TOKEN` und `NODE_API_KEY`, startet `llama-swap.exe -config … -listen 0.0.0.0:PORT` und schreibt das Log nach `logs\`.
|
||||
6. Firewall-Regel: TCP-Port eingehend nur von `GatewayIP`.
|
||||
7. Geplante Aufgabe `LocalAI-Node`: Start beim Booten, als SYSTEM, ohne Laufzeitlimit, Neustart bei Absturz.
|
||||
8. Standby am Netzteil aus (`powercfg`), außer bei `-KeepSleep`.
|
||||
9. Startet die Aufgabe, wartet auf `/health` und gibt die Zeile für `CLUSTER_NODES` aus.
|
||||
- **`download-models.ps1`** (optional): lädt alle Cluster-Modelle vorab herunter, indem es jedes Modell einmal kurz anfragt. So dauert die erste echte Anfrage nicht wegen eines 20-GB-Downloads.
|
||||
- **`node-config.yaml`** (Vorlage, Quelle der Modellliste): alle Cluster-Chat-Modelle (`cluster-qwen3.5-35b-a3b`, `cluster-glm-4.7-flash`, `cluster-qwen3.5-9b`) in **einer** Swap-Gruppe, Pfade mit `/`, `--fit` mit `--fit-target 1024` (passt sich automatisch an den VRAM des Knotens an), `-np <Slots>`. Dazu `apiKeys: ["${env.NODE_API_KEY}"]`, optional Preload, `healthCheckTimeout: 3600`.
|
||||
- **`install-node.cmd`**: Doppelklick-Wrapper, fordert Admin-Rechte an, setzt `-ExecutionPolicy Bypass` und fragt Gateway-IP und Key ab.
|
||||
- **`status-node.ps1`**: Zustand der Aufgabe, `/health`, `/running`, `nvidia-smi`, letzte Logzeilen.
|
||||
- **`uninstall-node.ps1`**: entfernt Aufgabe und Firewall-Regel und löscht optional das Verzeichnis (`-KeepModels`).
|
||||
|
||||
## Doku
|
||||
- Neu **`docs/cluster.md`**: Architektur und Routing (Stammknoten, Überlauf), Ablauf (Key → Knoten installieren → IP in `CLUSTER_NODES` → `./scripts/start.sh`), Modell hinzufügen (nur in `node-config.yaml` + `peers`, dann Knoten mit `-Force` aktualisieren). Grenzen: Ein Modell, das größer als VRAM+RAM eines Knotens ist, wird nicht über mehrere PCs verteilt. Kleine GPUs lagern per `--fit` in den RAM aus und sind dann langsamer.
|
||||
- **[README.md](README.md)**: Abschnitt „Cluster“ mit Link, Dateiliste ergänzen. Die Client-Doku nennt die `cluster-*`-Modellnamen.
|
||||
|
||||
## Verifikation
|
||||
1. `cluster-gen.sh` mit 0, 1 und 3 Test-Knoten ausführen und jeweils `caddy validate` (Docker, `caddy:2.10.2-alpine`) laufen lassen. Außerdem `docker compose config`.
|
||||
2. Kette ohne Windows testen: Als Fake-Knoten dient eine zweite llama-swap-Linux-Binary bzw. ein Echo-Server im Scratchpad, auf zwei Ports. Prüfen:
|
||||
- `/v1/models` zeigt `cluster-*`.
|
||||
- Die Anfrage läuft über :8800 → peer → cluster-lb → Stammknoten.
|
||||
- Streaming funktioniert.
|
||||
- Bei belegtem Stammknoten geht die Anfrage an den nächsten Knoten.
|
||||
- Ein gestoppter Knoten wird übersprungen.
|
||||
3. PowerShell-Syntax mit `pwsh` parsen, falls verfügbar. Den echten Lauf auf einem Windows-PC macht der Nutzer; der Installer endet mit einem Selbsttest (`/health`).
|
||||
4. Der bestehende Betrieb bleibt unverändert: `./scripts/start.sh` startet weiter erfolgreich, auch ohne Knoten.
|
||||
24
README.md
24
README.md
@ -1,12 +1,13 @@
|
||||
# LocalAI – zentraler LLM-Server für opencode, Continue & Laravel
|
||||
# LocalAI – zentraler LLM-Server für opencode, Continue, Laravel & Web-Chat
|
||||
|
||||
Ein Docker-Compose-Stack, der auf **alegab-pc** (RX 6950 XT 16 GB, Ryzen 9 7900X3D, 62 GB RAM) eine **OpenAI-kompatible API** bereitstellt. Clients im Heim-LAN und über NetBird nutzen sie zum Programmieren, ganz ohne Cloud-KI.
|
||||
|
||||
```
|
||||
Client-PC (opencode / VS Code + Continue) ──LAN / NetBird──┐
|
||||
Client-PC (opencode / VS Code + Continue / Browser) ──LAN / NetBird──┐
|
||||
▼
|
||||
caddy (Host-Netz, :8800) IP-Filter + API-Key
|
||||
└─► llama-swap (nur 127.0.0.1:8080) lädt/wechselt Modelle
|
||||
caddy (Host-Netz) :8800 API: IP-Filter + API-Key :8801 Web-Chat: IP-Filter
|
||||
├─► open-webui (nur 127.0.0.1:3000) Web-Chat mit eigenem Login ──┐
|
||||
└─► llama-swap (nur 127.0.0.1:8080) lädt/wechselt Modelle ◄────┘
|
||||
├─► llama-server (llama.cpp, ROCm) Chat, Autocomplete, Embeddings
|
||||
├─► whisper-server (whisper.cpp, Vulkan) Transkription
|
||||
└─► sd-server (stable-diffusion.cpp, Vulkan) Bilder
|
||||
@ -31,11 +32,11 @@ cp .env.example .env
|
||||
sed -i "s/^LLM_API_KEY=.*/LLM_API_KEY=$(openssl rand -hex 32)/" .env
|
||||
chmod 600 .env
|
||||
$EDITOR .env # ALLOWED_NETS, GIDs (getent group render video), UID/GID prüfen
|
||||
sudo ./scripts/firewall-ufw.sh # einmalig: Port 8800 für LAN + wt0 in ufw öffnen
|
||||
sudo ./scripts/firewall-ufw.sh # einmalig: Ports 8800 + 8801 für LAN + wt0 in ufw öffnen
|
||||
./scripts/start.sh # prüfen, starten, Modelle vorladen, Verbindungsdaten zeigen
|
||||
```
|
||||
|
||||
`scripts/start.sh` legt eine fehlende `.env` beim ersten Start auch selbst an: mit neuem API-Key, UID/GID und den GIDs von render/video. Weitere Optionen:
|
||||
`scripts/start.sh` legt eine fehlende `.env` beim ersten Start auch selbst an: mit neuem API-Key, WebUI-Schlüssel, UID/GID und den GIDs von render/video. Weitere Optionen:
|
||||
|
||||
| Aufruf | Wirkung |
|
||||
|---|---|
|
||||
@ -66,8 +67,8 @@ Vulkan zählt die iGPU des Ryzen mit. `VK_VISIBLE_DEVICES=1` in `.env` wählt di
|
||||
|
||||
### Warum Caddy im Host-Netz?
|
||||
Ports, die Docker veröffentlicht, **umgehen ufw**. Deshalb ist llama-swap nur an `127.0.0.1` gebunden. Caddy läuft mit `network_mode: host`, dort greift ufw, und Caddy sieht die echte Quell-IP. Daraus ergeben sich zwei Schutzschichten:
|
||||
1. **ufw** lässt Port 8800 nur aus dem LAN und über `wt0` zu.
|
||||
2. **Caddy** prüft zusätzlich die Quell-IP (`ALLOWED_NETS`) und den Bearer-Token (`LLM_API_KEY`).
|
||||
1. **ufw** lässt die Ports 8800 (API) und 8801 (Web-Chat) nur aus dem LAN und über `wt0` zu.
|
||||
2. **Caddy** prüft zusätzlich die Quell-IP (`ALLOWED_NETS`), bei der API außerdem den Bearer-Token (`LLM_API_KEY`). Beim Web-Chat übernimmt der Login von Open WebUI diese Rolle.
|
||||
|
||||
Die API läuft über plain HTTP. Über NetBird ist das unkritisch (WireGuard verschlüsselt den Tunnel), im Heim-LAN ist es akzeptabel.
|
||||
|
||||
@ -76,6 +77,7 @@ Die API läuft über plain HTTP. Über NetBird ist das unkritisch (WireGuard ver
|
||||
- **opencode** (Terminal-Agent): [docs/opencode.md](docs/opencode.md)
|
||||
- **Continue** (VS-Code-Plugin): [docs/continue.md](docs/continue.md)
|
||||
- **Laravel** (laravel/ai – Agent, Spracheingabe, Bilder): [docs/laravel.md](docs/laravel.md)
|
||||
- **Web-Chat** (Open WebUI im Browser, `http://<server>:8801`): [docs/webchat.md](docs/webchat.md)
|
||||
|
||||
Schnelltest von jedem Client:
|
||||
```bash
|
||||
@ -88,6 +90,7 @@ curl -H "Authorization: Bearer <KEY>" http://<server>:8800/v1/models
|
||||
docker compose ps # Status
|
||||
docker compose logs -f llama-swap # Modell-Laden, Requests
|
||||
docker compose logs -f caddy # Zugriffe
|
||||
docker compose logs -f open-webui # Web-Chat
|
||||
docker compose restart # nach Änderungen an .env / Caddyfile
|
||||
docker compose up -d --build # nach Änderungen an llama-swap/Dockerfile oder den Image-Tags
|
||||
```
|
||||
@ -127,11 +130,12 @@ GPU-Layer und MoE-Auslagerung in den RAM verteilt llama.cpp automatisch (`--fit`
|
||||
|
||||
| Datei | Inhalt |
|
||||
|---|---|
|
||||
| `docker-compose.yml` | Dienste llama-swap + caddy |
|
||||
| `docker-compose.yml` | Dienste llama-swap, open-webui, caddy |
|
||||
| `llama-swap/Dockerfile` | ROCm-Image + whisper-server/sd-server |
|
||||
| `.env` / `.env.example` | Secrets, Netze, Ports, GIDs, Image-Tags |
|
||||
| `llama-swap/config.yaml` | Modelle, Startparameter, Gruppen |
|
||||
| `caddy/Caddyfile` | IP-Filter, API-Key, Reverse Proxy |
|
||||
| `caddy/Caddyfile` | IP-Filter, API-Key, Reverse Proxy (API + Web-Chat) |
|
||||
| `docs/webchat.md` | Web-Chat (Open WebUI) |
|
||||
| `client/opencode.json` | Vorlage für opencode-Clients |
|
||||
| `client/continue/` | Vorlage für Continue-Clients |
|
||||
| `docs/laravel.md` | Anbindung einer Laravel-App (laravel/ai) |
|
||||
|
||||
@ -20,3 +20,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Web-Chat (Open WebUI): nur erlaubte Netze, Login übernimmt Open WebUI selbst
|
||||
:{$WEBUI_PORT} {
|
||||
@blocked not remote_ip {$ALLOWED_NETS}
|
||||
respond @blocked "forbidden" 403
|
||||
|
||||
reverse_proxy 127.0.0.1:{$WEBUI_BACKEND_PORT} {
|
||||
flush_interval -1
|
||||
transport http {
|
||||
read_timeout 30m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
# Lokaler LLM-Server: llama-swap (llama.cpp/ROCm + whisper/sd-server) hinter Caddy (IP-Filter + API-Key)
|
||||
# Web-Chat: Open WebUI, ebenfalls hinter Caddy (IP-Filter + eigener Login)
|
||||
# Start: docker compose up -d --build Logs: docker compose logs -f llama-swap
|
||||
name: localai
|
||||
|
||||
@ -51,7 +52,57 @@ services:
|
||||
ALLOWED_NETS: "${ALLOWED_NETS:-127.0.0.0/8 ::1}"
|
||||
LISTEN_PORT: "${LISTEN_PORT:-8800}"
|
||||
BACKEND_PORT: "${BACKEND_PORT:-8080}"
|
||||
WEBUI_PORT: "${WEBUI_PORT:-8801}"
|
||||
WEBUI_BACKEND_PORT: "${WEBUI_BACKEND_PORT:-3000}"
|
||||
volumes:
|
||||
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
depends_on:
|
||||
- llama-swap
|
||||
- open-webui
|
||||
|
||||
# Web-Chat im Browser. Spricht intern direkt mit llama-swap (Docker-Netz, ohne Caddy).
|
||||
# Die Werte unten gelten nur beim ersten Start – danach im Admin-Bereich ändern.
|
||||
open-webui:
|
||||
image: ghcr.io/open-webui/open-webui:${OPEN_WEBUI_TAG:-v0.11.4}
|
||||
restart: unless-stopped
|
||||
# nur lokal erreichbar – von außen ausschließlich über Caddy
|
||||
ports:
|
||||
- "127.0.0.1:${WEBUI_BACKEND_PORT:-3000}:8080"
|
||||
environment:
|
||||
WEBUI_SECRET_KEY: "${WEBUI_SECRET_KEY:?WEBUI_SECRET_KEY in .env setzen}"
|
||||
# erster Account wird Admin, weitere müssen freigeschaltet werden
|
||||
DEFAULT_USER_ROLE: pending
|
||||
ENABLE_VERSION_UPDATE_CHECK: "false"
|
||||
ENABLE_OLLAMA_API: "false"
|
||||
OPENAI_API_BASE_URL: http://llama-swap:8080/v1
|
||||
OPENAI_API_KEY: none
|
||||
# nur eine Chat-Sitzung gleichzeitig (-np 1): keine Zusatz-Requests beim Tippen
|
||||
ENABLE_AUTOCOMPLETE_GENERATION: "false"
|
||||
ENABLE_FOLLOW_UP_GENERATION: "false"
|
||||
ENABLE_EVALUATION_ARENA_MODELS: "false"
|
||||
# Dokumente/RAG: Embeddings von nomic-embed statt eines lokalen Downloads
|
||||
RAG_EMBEDDING_ENGINE: openai
|
||||
RAG_EMBEDDING_MODEL: nomic-embed
|
||||
RAG_OPENAI_API_BASE_URL: http://llama-swap:8080/v1
|
||||
RAG_OPENAI_API_KEY: none
|
||||
RAG_EMBEDDING_MODEL_AUTO_UPDATE: "false"
|
||||
# Spracheingabe über whisper
|
||||
AUDIO_STT_ENGINE: openai
|
||||
AUDIO_STT_MODEL: whisper
|
||||
AUDIO_STT_OPENAI_API_BASE_URL: http://llama-swap:8080/v1
|
||||
AUDIO_STT_OPENAI_API_KEY: none
|
||||
WHISPER_MODEL_AUTO_UPDATE: "false"
|
||||
# Bilder über z-image (verdrängt das Chat-Modell, siehe config.yaml)
|
||||
ENABLE_IMAGE_GENERATION: "true"
|
||||
IMAGE_GENERATION_ENGINE: openai
|
||||
IMAGE_GENERATION_MODEL: z-image
|
||||
IMAGE_SIZE: 1024x1024
|
||||
IMAGES_OPENAI_API_BASE_URL: http://llama-swap:8080/v1
|
||||
IMAGES_OPENAI_API_KEY: none
|
||||
volumes:
|
||||
- open-webui:/app/backend/data
|
||||
depends_on:
|
||||
- llama-swap
|
||||
|
||||
volumes:
|
||||
open-webui:
|
||||
|
||||
49
docs/webchat.md
Normal file
49
docs/webchat.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Web-Chat (Open WebUI)
|
||||
|
||||
Chat im Browser, ohne Client-Installation. Open WebUI läuft im selben Stack und spricht intern direkt mit llama-swap. Nach außen liegt es hinter Caddy auf Port **8801** (`WEBUI_PORT`). Dort greifen derselbe IP-Filter (`ALLOWED_NETS`) und ufw wie bei der API. Einen API-Key braucht der Browser nicht, Open WebUI hat einen eigenen Login.
|
||||
|
||||
| Zugang | URL |
|
||||
|---|---|
|
||||
| lokal | http://127.0.0.1:8801 |
|
||||
| LAN | http://192.168.178.72:8801 |
|
||||
| NetBird | http://alegab-pc.vpn.local:8801 |
|
||||
|
||||
`./scripts/start.sh` zeigt die aktuellen Adressen an.
|
||||
|
||||
## Erster Start
|
||||
|
||||
1. Einmalig den Port in der Firewall öffnen: `sudo ./scripts/firewall-ufw.sh`
|
||||
2. Die URL öffnen und **sofort einen Account anlegen**. Der erste Account wird Admin.
|
||||
3. Weitere Accounts landen als „ausstehend“ und müssen unter *Admin-Bereich → Benutzer* freigeschaltet werden. Wer niemanden sonst zulassen will: *Admin-Bereich → Einstellungen → Allgemein → Registrierung* abschalten.
|
||||
|
||||
## Was vorkonfiguriert ist
|
||||
|
||||
| Funktion | Modell | Hinweis |
|
||||
|---|---|---|
|
||||
| Chat | `qwen3.5-35b-a3b`, `glm-4.7-flash`, `qwen3.5-9b` | Modellwechsel lädt das Modell in llama-swap um (dauert einige Sekunden) |
|
||||
| Dokumente / RAG (Datei in den Chat ziehen) | `nomic-embed` | |
|
||||
| Spracheingabe (Mikrofon) | `whisper` | nur über HTTPS oder `localhost`, siehe unten |
|
||||
| Bilder (Bild-Knopf im Chat) | `z-image` | verdrängt das Chat-Modell; der nächste Chat lädt es neu |
|
||||
|
||||
Die Auswahlliste zeigt auch `qwen2.5-coder-1.5b`, `nomic-embed`, `whisper` und `z-image`. Diese Modelle taugen nicht zum Chatten. Ausblenden kann man sie unter *Admin-Bereich → Einstellungen → Modelle*.
|
||||
|
||||
Die Chat-Modelle laufen mit `-np 1`, also mit einer Sitzung gleichzeitig. Wenn opencode gerade arbeitet, wartet der Web-Chat (und umgekehrt). Titel-Generierung nutzt dasselbe Modell und hängt sich hinten an. Autocomplete- und Folgefragen-Vorschläge sind deshalb abgeschaltet.
|
||||
|
||||
## Einstellungen ändern
|
||||
|
||||
Die Umgebungsvariablen in `docker-compose.yml` gelten **nur beim allerersten Start**. Danach speichert Open WebUI alles in seiner Datenbank. Spätere Änderungen macht man im *Admin-Bereich → Einstellungen* (Verbindungen, Dokumente, Audio, Bilder).
|
||||
|
||||
Die Daten (Accounts, Chats, Dokumente) liegen im Docker-Volume `localai_open-webui`.
|
||||
- Sichern: `docker run --rm -v localai_open-webui:/d -v "$PWD":/b alpine tar czf /b/open-webui-backup.tgz -C /d .`
|
||||
- Komplett zurücksetzen: `docker compose down && docker volume rm localai_open-webui`
|
||||
|
||||
## Spracheingabe von anderen Rechnern
|
||||
|
||||
Browser geben das Mikrofon nur auf HTTPS-Seiten oder `localhost` frei. Über `http://<server>:8801` bleibt der Mikrofon-Knopf daher wirkungslos. Für Chrome/Chromium gibt es eine Ausnahme pro Adresse:
|
||||
`chrome://flags/#unsafely-treat-insecure-origin-as-secure` → `http://alegab-pc.vpn.local:8801` eintragen, neu starten.
|
||||
|
||||
Dateien hochladen (Audio-Datei transkribieren) funktioniert auch ohne HTTPS.
|
||||
|
||||
## Update
|
||||
|
||||
`OPEN_WEBUI_TAG` in `.env` anheben (Releases: https://github.com/open-webui/open-webui/releases), dann `docker compose up -d`. Open WebUI migriert die Datenbank selbst. Vorher sichern.
|
||||
@ -1,12 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# Einmalig ausführen: sudo ./scripts/firewall-ufw.sh
|
||||
# Öffnet den Caddy-Port nur für LAN und NetBird (wt0). Caddy filtert zusätzlich selbst.
|
||||
# Öffnet die Caddy-Ports (API + Web-Chat) nur für LAN und NetBird (wt0). Caddy filtert zusätzlich selbst.
|
||||
set -euo pipefail
|
||||
|
||||
PORT="${LISTEN_PORT:-8800}"
|
||||
WEBUI_PORT="${WEBUI_PORT:-8801}"
|
||||
LAN="${LAN_NET:-192.168.178.0/24}"
|
||||
VPN_IF="${VPN_IF:-wt0}"
|
||||
|
||||
ufw allow from "$LAN" to any port "$PORT" proto tcp comment 'localai LAN'
|
||||
ufw allow in on "$VPN_IF" to any port "$PORT" proto tcp comment 'localai NetBird'
|
||||
ufw status numbered | grep -E "$PORT" || true
|
||||
ufw allow from "$LAN" to any port "$WEBUI_PORT" proto tcp comment 'localai webchat LAN'
|
||||
ufw allow in on "$VPN_IF" to any port "$WEBUI_PORT" proto tcp comment 'localai webchat NetBird'
|
||||
ufw status numbered | grep -E "$PORT|$WEBUI_PORT" || true
|
||||
|
||||
@ -47,6 +47,7 @@ if [[ ! -f .env ]]; then
|
||||
warn ".env fehlt – wird aus .env.example erzeugt"
|
||||
cp .env.example .env
|
||||
sed -i "s/^LLM_API_KEY=.*/LLM_API_KEY=$(openssl rand -hex 32)/" .env
|
||||
sed -i "s/^WEBUI_SECRET_KEY=.*/WEBUI_SECRET_KEY=$(openssl rand -hex 32)/" .env
|
||||
sed -i "s/^HOST_UID=.*/HOST_UID=$(id -u)/; s/^HOST_GID=.*/HOST_GID=$(id -g)/" .env
|
||||
rg=$(getent group render | cut -d: -f3 || true); vg=$(getent group video | cut -d: -f3 || true)
|
||||
[[ -n "$rg" ]] && sed -i "s/^RENDER_GID=.*/RENDER_GID=$rg/" .env
|
||||
@ -55,21 +56,30 @@ if [[ ! -f .env ]]; then
|
||||
warn "Bitte ALLOWED_NETS in .env prüfen (LAN-/NetBird-Netz)"
|
||||
fi
|
||||
|
||||
# ältere .env ohne Web-Chat-Einträge ergänzen
|
||||
if ! grep -q '^WEBUI_SECRET_KEY=' .env; then
|
||||
printf '\nWEBUI_PORT=8801\nWEBUI_BACKEND_PORT=3000\nWEBUI_SECRET_KEY=%s\n' "$(openssl rand -hex 32)" >> .env
|
||||
warn "Web-Chat-Einträge (WEBUI_*) in .env ergänzt"
|
||||
fi
|
||||
|
||||
set -a; . ./.env; set +a
|
||||
LISTEN_PORT="${LISTEN_PORT:-8800}"
|
||||
BACKEND_PORT="${BACKEND_PORT:-8080}"
|
||||
WEBUI_PORT="${WEBUI_PORT:-8801}"
|
||||
WEBUI_BACKEND_PORT="${WEBUI_BACKEND_PORT:-3000}"
|
||||
[[ -n "${LLM_API_KEY:-}" && "$LLM_API_KEY" != "CHANGE_ME" ]] || die "LLM_API_KEY in .env setzen (openssl rand -hex 32)"
|
||||
[[ -n "${WEBUI_SECRET_KEY:-}" && "$WEBUI_SECRET_KEY" != "CHANGE_ME" ]] || die "WEBUI_SECRET_KEY in .env setzen (openssl rand -hex 32)"
|
||||
ok ".env geladen"
|
||||
|
||||
# Ports dürfen nur von unserem eigenen Stack belegt sein
|
||||
running=$(docker compose ps --status running -q 2>/dev/null | wc -l)
|
||||
if [[ "$running" -eq 0 ]]; then
|
||||
for p in "$LISTEN_PORT" "$BACKEND_PORT"; do
|
||||
for p in "$LISTEN_PORT" "$BACKEND_PORT" "$WEBUI_PORT" "$WEBUI_BACKEND_PORT"; do
|
||||
if ss -Htln "sport = :$p" | grep -q .; then
|
||||
die "Port $p ist bereits belegt: $(ss -Htlnp "sport = :$p" | awk '{print $NF}' | head -1) – in .env ändern"
|
||||
fi
|
||||
done
|
||||
ok "Ports $LISTEN_PORT und $BACKEND_PORT frei"
|
||||
ok "Ports $LISTEN_PORT, $BACKEND_PORT, $WEBUI_PORT und $WEBUI_BACKEND_PORT frei"
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet ollama 2>/dev/null; then
|
||||
@ -77,9 +87,11 @@ if systemctl is-active --quiet ollama 2>/dev/null; then
|
||||
fi
|
||||
|
||||
if command -v ufw >/dev/null && systemctl is-active --quiet ufw 2>/dev/null; then
|
||||
if [[ -r /etc/ufw/user.rules ]] && ! grep -q "dport $LISTEN_PORT" /etc/ufw/user.rules; then
|
||||
warn "Keine ufw-Freigabe für Port $LISTEN_PORT gefunden → sudo ./scripts/firewall-ufw.sh"
|
||||
for p in "$LISTEN_PORT" "$WEBUI_PORT"; do
|
||||
if [[ -r /etc/ufw/user.rules ]] && ! grep -q "dport $p" /etc/ufw/user.rules; then
|
||||
warn "Keine ufw-Freigabe für Port $p gefunden → sudo ./scripts/firewall-ufw.sh"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
mkdir -p models
|
||||
@ -113,6 +125,14 @@ code=$(curl -s -o /dev/null -m 5 -w '%{http_code}' -H "Authorization: Bearer $LL
|
||||
[[ "$code" == "200" ]] || die "API mit Key liefert HTTP $code"
|
||||
ok "Caddy bereit (Auth aktiv)"
|
||||
|
||||
# Open WebUI braucht beim ersten Start etwas länger (Datenbank anlegen)
|
||||
for i in $(seq 1 120); do
|
||||
curl -fsS -m 2 "http://127.0.0.1:$WEBUI_PORT/health" >/dev/null 2>&1 && break
|
||||
(( i == 120 )) && die "Web-Chat antwortet nicht – docker compose logs open-webui"
|
||||
sleep 1
|
||||
done
|
||||
ok "Web-Chat bereit"
|
||||
|
||||
# ---------------------------------------------------------------- Preload
|
||||
if (( PRELOAD )); then
|
||||
step "Lade Modelle vor (erster Start lädt ggf. von Hugging Face herunter)"
|
||||
@ -149,14 +169,17 @@ step "Verbindung"
|
||||
lan_ip=$(ip -4 -o addr show scope global | awk '$2 !~ /^(wt|docker|virbr|br-|veth)/ {split($4,a,"/"); print a[1]; exit}')
|
||||
nb_ip=$(ip -4 -o addr show wt0 2>/dev/null | awk '{split($4,a,"/"); print a[1]}')
|
||||
nb_fqdn=$(netbird status 2>/dev/null | awk '/^FQDN:/{print $2}' || true)
|
||||
echo " lokal: http://127.0.0.1:$LISTEN_PORT/v1"
|
||||
[[ -n "$lan_ip" ]] && echo " LAN: http://$lan_ip:$LISTEN_PORT/v1"
|
||||
[[ -n "$nb_ip" ]] && echo " NetBird: http://$nb_ip:$LISTEN_PORT/v1${nb_fqdn:+ (http://$nb_fqdn:$LISTEN_PORT/v1)}"
|
||||
echo " API lokal: http://127.0.0.1:$LISTEN_PORT/v1"
|
||||
[[ -n "$lan_ip" ]] && echo " API LAN: http://$lan_ip:$LISTEN_PORT/v1"
|
||||
[[ -n "$nb_ip" ]] && echo " API NetBird: http://$nb_ip:$LISTEN_PORT/v1${nb_fqdn:+ (http://$nb_fqdn:$LISTEN_PORT/v1)}"
|
||||
echo " Chat lokal: http://127.0.0.1:$WEBUI_PORT"
|
||||
[[ -n "$lan_ip" ]] && echo " Chat LAN: http://$lan_ip:$WEBUI_PORT"
|
||||
[[ -n "$nb_ip" ]] && echo " Chat NetBird: http://$nb_ip:$WEBUI_PORT${nb_fqdn:+ (http://$nb_fqdn:$WEBUI_PORT)}"
|
||||
if (( SHOW_KEY )); then
|
||||
echo " API-Key: $LLM_API_KEY"
|
||||
else
|
||||
echo " API-Key: ${LLM_API_KEY:0:6}… (vollständig: --show-key)"
|
||||
fi
|
||||
echo " Web-UI: http://127.0.0.1:$BACKEND_PORT/ui (nur lokal)"
|
||||
echo " llama-swap: http://127.0.0.1:$BACKEND_PORT/ui (nur lokal)"
|
||||
echo
|
||||
echo " Clients einrichten: docs/opencode.md, docs/continue.md"
|
||||
echo " Clients einrichten: docs/opencode.md, docs/continue.md, docs/webchat.md"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user