Initial import: bare-metal stacks, Server UI, GPU fan, tutorials.
Infrastructure configs for GMKtec K11 (Docker, vLLM, LocalAI, ComfyUI, control-plane, gpu-fan agent, Server UI with CLI/file explorer/GPU fan curve). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
# ADR-001: Host Agent + Control Plane UI
|
||||
|
||||
**Status:** Zaakceptowany (research 2026-07-04)
|
||||
**Kontekst:** Uniwersalny panel serwera (Server UI) + gpu-fan na gmktec-k11
|
||||
**Pełna analiza:** [`SERVER-UI-ARCHITECTURE-RESEARCH.md`](SERVER-UI-ARCHITECTURE-RESEARCH.md)
|
||||
|
||||
---
|
||||
|
||||
## Kontekst i problem
|
||||
|
||||
Na hoście działają:
|
||||
- **gpu-fan** (systemd, root, NVML) — port 8090, własny Web UI
|
||||
- **server-ui** (systemd) — port 8091, zarządzanie Docker compose
|
||||
- **Workloady AI** (Docker) — ComfyUI, LocalAI, vLLM
|
||||
|
||||
Użytkownik chce:
|
||||
1. Jeden panel zamiast wielu portów (8090, 8091, …)
|
||||
2. UI serwerowe w Dockerze
|
||||
3. Operacje wymagające root (wentylatory GPU) nadal na hoście
|
||||
|
||||
Pytanie: jak to pogodzić bez pakowania NVML do kontenera?
|
||||
|
||||
---
|
||||
|
||||
## Decyzja
|
||||
|
||||
Przyjmujemy architekturę **dwuwarstwową**:
|
||||
|
||||
1. **Host Agents** — procesy systemd jako root, API tylko na `127.0.0.1`, bez publicznego UI:
|
||||
- `gpu-fan-daemon` (:18090) — NVML fan loop + REST API
|
||||
- (przyszłość) `host-agent` — agregacja systemd, disk health
|
||||
|
||||
2. **Control Plane UI** — Server UI (docelowo kontener Docker):
|
||||
- Jeden dashboard, jeden `API_KEY` dla użytkownika
|
||||
- Mount `/var/run/docker.sock` — orchestracja stacków
|
||||
- HTTP proxy `/api/gpu-fan/*` → host agent
|
||||
|
||||
**Strategia implementacji:** C z planu research (split gpu-fan → integracja UI → dockerize server-ui).
|
||||
|
||||
---
|
||||
|
||||
## Uzasadnienie
|
||||
|
||||
| Alternatywa | Dlaczego odrzucona |
|
||||
|-------------|-------------------|
|
||||
| gpu-fan w Dockerze | NVML write na GeForce wymaga root na hoście; kruche, nieutrzymywane |
|
||||
| Status quo (dwa panele) | Rozproszony UX, dwa klucze |
|
||||
| Tylko Cockpit | Brak whitelist AI stacks, polityki GPU, custom NVML |
|
||||
| Monolit root z UI na 0.0.0.0 | Obecny gpu-fan — do refaktoru ze względów bezpieczeństwa |
|
||||
|
||||
Wzorzec zgodny z **Portainer** (UI + docker.sock) i **Proxmox** (privileged daemons na hoście).
|
||||
|
||||
---
|
||||
|
||||
## Konsekwencje
|
||||
|
||||
### Pozytywne
|
||||
- Jeden port publiczny dla panelu (:8091, potem opcjonalnie NPMPlus :443)
|
||||
- Daemon fan niezależny od UI — bezpieczeństwo termiczne przy padzie panelu
|
||||
- Możliwość rozszerzenia o `host-agent` bez kolejnych portów LAN
|
||||
|
||||
### Negatywne / koszt
|
||||
- Refactor gpu-fan (rozdzielenie UI i API)
|
||||
- Dockerize server-ui (Dockerfile, mounty, testy `host.docker.internal`)
|
||||
- Dwa miejsca deploy: systemd (agenty) + compose (UI)
|
||||
|
||||
### Neutralne
|
||||
- ComfyUI/LocalAI UI pozostają osobnymi aplikacjami (linki z dashboardu)
|
||||
- npmplus bez zmian (`network_mode: host`)
|
||||
|
||||
---
|
||||
|
||||
## Szczegóły techniczne (skrót)
|
||||
|
||||
```
|
||||
Browser → server-ui:8091 (Docker)
|
||||
├─ docker.sock → compose stacks
|
||||
└─ proxy → 127.0.0.1:18090 (gpu-fan-daemon, systemd root)
|
||||
```
|
||||
|
||||
Env agenta:
|
||||
- `GPU_FAN_API_HOST=127.0.0.1`
|
||||
- `GPU_FAN_API_PORT=18090`
|
||||
|
||||
Env server-ui:
|
||||
- `GPU_FAN_AGENT_URL=http://host.docker.internal:18090` (w Dockerze)
|
||||
|
||||
---
|
||||
|
||||
## Kiedy przejrzeć tę decyzję
|
||||
|
||||
- Multi-node cluster (więcej niż jeden host) — wtedy centralny panel + agenty na każdym nodzie
|
||||
- Migracja na Cockpit/Kubernetes — osobna decyzja ADR
|
||||
- NVIDIA udostępni oficjalny fan API w kontenerach bez root — mało prawdopodobne na GeForce
|
||||
|
||||
---
|
||||
|
||||
## Powiązane dokumenty
|
||||
|
||||
- [`SERVER-UI-ARCHITECTURE-RESEARCH.md`](SERVER-UI-ARCHITECTURE-RESEARCH.md)
|
||||
- [`../stacks/gpu-fan/coding-agent/DOCKER-VS-HOST-REPORT.md`](../stacks/gpu-fan/coding-agent/DOCKER-VS-HOST-REPORT.md)
|
||||
- [`DOCKER-UI-DEPLOYMENT.md`](DOCKER-UI-DEPLOYMENT.md)
|
||||
@@ -0,0 +1,94 @@
|
||||
# Backlog
|
||||
|
||||
Priorytety dla kolejnego agenta. Aktualizuj po ukończeniu zadań.
|
||||
|
||||
## P0 — naprawy / dokończenie bieżącego
|
||||
|
||||
- [ ] **Przekazać `LOCALAI_API_KEY` do kontenera**
|
||||
Dodać do `stacks/localai/docker-compose.yml` w sekcji `environment:`:
|
||||
```yaml
|
||||
- LOCALAI_API_KEY=${LOCALAI_API_KEY:-}
|
||||
```
|
||||
Dodać komentarz / placeholder do `stacks/localai/.env.example` (bez prawdziwego klucza).
|
||||
Restart: `docker compose --profile localai restart localai`
|
||||
|
||||
- [ ] **Zweryfikować API auth po restarcie**
|
||||
```bash
|
||||
curl -s http://localhost:8070/v1/models \
|
||||
-H "Authorization: Bearer <klucz z .env>"
|
||||
```
|
||||
Bez klucza powinno być 401.
|
||||
|
||||
- [ ] **Zsynchronizować port 8070**
|
||||
Albo zaktualizować `05-localai-stack.md`, `stacks/localai/README.md`, root README (port 8070), albo zapytać użytkownika o powrót do 8080.
|
||||
|
||||
- [ ] **Dokończyć test modelu LocalAI**
|
||||
Po pobraniu modelu: chat w UI, `curl /v1/chat/completions`, `nvidia-smi` (VRAM).
|
||||
|
||||
## P1 — produkcja / dostęp zewnętrzny
|
||||
|
||||
- [x] ~~SwarmUI~~ — **usunięte 2026-07-04** ([`SWARMUI-REMOVAL.md`](SWARMUI-REMOVAL.md))
|
||||
|
||||
- [ ] Reverse proxy LocalAI — **częściowo** (NPMPlus działa dla `llm.rtx1.mobile.agency-ai.dev`)
|
||||
- [ ] `LOCALAI_BASE_URL=https://llm.rtx1.mobile.agency-ai.dev` w dokumentacji klienta
|
||||
|
||||
- [ ] Bind LocalAI tylko na localhost po proxy:
|
||||
```yaml
|
||||
ports:
|
||||
- "127.0.0.1:${LOCALAI_PORT:-8080}:8080"
|
||||
```
|
||||
|
||||
- [ ] Rozdział **08 — firewall i hardening** (ufw, ograniczenie portów 8070/8090/**8091**)
|
||||
|
||||
- [ ] Opcjonalnie: `LOCALAI_AUTH=true` + rejestracja invite (multi-user) zamiast samego legacy key
|
||||
|
||||
## P2 — integracja modeli i GPU
|
||||
|
||||
- [x] **BGE-Reranker-v2-m3** — GGUF + YAML + `download-reranker.sh` (2026-07-01). Raport: [`RERANKER-DEPLOYMENT.md`](RERANKER-DEPLOYMENT.md)
|
||||
|
||||
- [ ] Połączyć `stacks/vllm/models.catalog.yaml` z LocalAI
|
||||
GGUF z `/data/apps/gguf/` → symlink lub kopia do `/data/apps/localai/models/` + YAML
|
||||
|
||||
- [ ] Skrypt pobierania modelu pod LocalAI (obok vLLM `download-model.sh`)
|
||||
|
||||
- [ ] vLLM opcjonalnie: `./scripts/download-model.sh qwen3.6-27b-awq-vllm` + `switch-model.sh`
|
||||
Tylko gdy użytkownik chce AWQ zamiast GGUF w LocalAI
|
||||
|
||||
- [x] Dokumentacja: jeden aktywny duży model na GPU — LocalAI ↔ ComfyUI, procedura w Server UI — [`DOCKER-UI-DEPLOYMENT.md`](DOCKER-UI-DEPLOYMENT.md), [`stacks/comfyui/README.md`](../stacks/comfyui/README.md)
|
||||
|
||||
- [x] ~~Dockge~~ — **usunięty 2026-07-04** ([`DOCKGE-REMOVAL.md`](DOCKGE-REMOVAL.md))
|
||||
- [x] **Server UI** — `stacks/server-ui/`, port **8091** (2026-07-04)
|
||||
- [ ] **Produkcja server-ui** — `sudo ./scripts/install.sh` na hoście (systemd)
|
||||
- [ ] **Architektura Server UI + gpu-fan** — research: [`SERVER-UI-ARCHITECTURE-RESEARCH.md`](SERVER-UI-ARCHITECTURE-RESEARCH.md), ADR: [`ADR-001-host-agent-control-plane.md`](ADR-001-host-agent-control-plane.md)
|
||||
- Faza 1: gpu-fan API-only `127.0.0.1:18090`
|
||||
- Faza 2: UI gpu-fan w server-ui (proxy)
|
||||
- Faza 3: server-ui w Dockerze (`docker.sock` + mount repo)
|
||||
|
||||
## P3 — roadmapa tutoriala
|
||||
|
||||
- [x] Rozdział **07 — ComfyUI stack** (Docker, `/data/apps/comfyui/`) — stack + tutorial 2026-07-04. Research SM: [`STABILITYMATRIX-RESEARCH.md`](STABILITYMATRIX-RESEARCH.md)
|
||||
- [ ] **Uruchomić ComfyUI na hoście** — `cd stacks/comfyui && ./scripts/start.sh` (użytkownik)
|
||||
|
||||
- [ ] Pełny Docker llama.cpp — **niski priorytet** jeśli LocalAI wystarczy dla GGUF
|
||||
|
||||
- [ ] Open WebUI dla vLLM — opcjonalnie, nie planowane na teraz
|
||||
|
||||
## Czego nie robić bez prośby użytkownika
|
||||
|
||||
- `git commit` / `git push`
|
||||
- Pobieranie modeli LLM przy `docker compose pull`
|
||||
- Build LocalAI lub vLLM ze źródeł GitHub
|
||||
- Edycja plików w `.cursor/plans/`
|
||||
- Zapisywanie sekretów w `coding-agent/` lub w commitach
|
||||
|
||||
## Ukończone w tej sesji (referencja)
|
||||
|
||||
- [x] Tutorial 00–03b, 04 (dysk + vLLM), 05 (LocalAI)
|
||||
- [x] `stacks/vllm/` — katalog modeli, skrypty, elastyczny compose
|
||||
- [x] `stacks/localai/` — stack GPU cuda-13, skrypty, README
|
||||
- [x] `stacks/llamacpp/README.md` — placeholder
|
||||
- [x] `stacks/server-ui/` — własny Docker UI, port 8091 (2026-07-04)
|
||||
- [x] `scripts/setup-data-disk.sh` — katalogi aplikacji na `/data`
|
||||
- [x] Root README — roadmapa 05/06/07
|
||||
- [x] `stacks/comfyui/` — ComfyUI Docker, port 8188 (2026-07-04); research SM → [`STABILITYMATRIX-RESEARCH.md`](STABILITYMATRIX-RESEARCH.md)
|
||||
- [x] `manual-tutorial/07-comfyui-stack.md` — tutorial ComfyUI
|
||||
@@ -0,0 +1,119 @@
|
||||
# Wdrożenie ComfyUI stack (Docker)
|
||||
|
||||
Data: 2026-07-04
|
||||
Host: `gmktec-k11` — RTX 3090 Ti 24 GB, `/data` zamontowany
|
||||
|
||||
## Kontekst
|
||||
|
||||
Po researchu [Stability Matrix](STABILITYMATRIX-RESEARCH.md) — **nie instalujemy SM** (wymaga GUI). Zamiast tego ComfyUI w Dockerze, wzorowany na `stacks/localai/`.
|
||||
|
||||
SwarmUI usunięte 2026-07-04 — [`SWARMUI-REMOVAL.md`](SWARMUI-REMOVAL.md).
|
||||
|
||||
## Podsumowanie stacku
|
||||
|
||||
| Element | Wartość |
|
||||
|---------|---------|
|
||||
| Stack | [`stacks/comfyui/`](../stacks/comfyui/) |
|
||||
| Kontener | `comfyui` |
|
||||
| Obraz | `yanwk/comfyui-boot:cu126-slim` |
|
||||
| Port | **8188** |
|
||||
| Profil compose | `comfyui` |
|
||||
| Dane | `/data/apps/comfyui/` |
|
||||
|
||||
## Architektura
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph host [gmktec-k11 headless]
|
||||
localai[localai :8070]
|
||||
comfyui[comfyui :8188]
|
||||
dockge[server-ui :8091]
|
||||
end
|
||||
data["/data/apps/comfyui/"]
|
||||
comfyui --> data
|
||||
note["GPU: localai LUB comfyui — nie oba z dużym modelem"]
|
||||
```
|
||||
|
||||
## Pliki w repo
|
||||
|
||||
| Plik | Rola |
|
||||
|------|------|
|
||||
| `stacks/comfyui/docker-compose.yml` | GPU, port 8188, bind mounty |
|
||||
| `stacks/comfyui/.env.example` | `DATA_ROOT`, `COMFYUI_PORT`, obraz |
|
||||
| `stacks/comfyui/scripts/start.sh` | ensure-dirs, pull, up, ostrzeżenie o LocalAI |
|
||||
| `stacks/comfyui/scripts/ensure-dirs.sh` | katalogi na `/data` |
|
||||
| `stacks/comfyui/scripts/pull.sh` | tylko `docker compose pull` |
|
||||
| `stacks/comfyui/README.md` | dokumentacja stacku |
|
||||
|
||||
## Katalogi na hoście
|
||||
|
||||
```
|
||||
/data/apps/comfyui/
|
||||
├── storage/ # kopia ComfyUI (pierwszy start obrazu yanwk)
|
||||
├── models/
|
||||
├── cache/hf-hub/
|
||||
├── cache/torch-hub/
|
||||
├── input/
|
||||
├── output/
|
||||
├── custom_nodes/
|
||||
└── workflows/
|
||||
```
|
||||
|
||||
Istniejące katalogi z `setup-data-disk.sh` (models, input, output, custom_nodes) są rozszerzone przez `ensure-dirs.sh`.
|
||||
|
||||
## Procedura wdrożenia
|
||||
|
||||
```bash
|
||||
cd ~/cursor/ubuntu-bare-metal/stacks/comfyui
|
||||
cp .env.example .env
|
||||
|
||||
# opcjonalnie: zatrzymaj LocalAI przed dużym modelem SD
|
||||
cd ../localai && docker compose --profile localai stop localai
|
||||
cd ../comfyui
|
||||
|
||||
./scripts/start.sh
|
||||
```
|
||||
|
||||
Weryfikacja:
|
||||
|
||||
```bash
|
||||
docker compose --profile comfyui ps
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8188/
|
||||
nvidia-smi
|
||||
```
|
||||
|
||||
Oczekiwany HTTP: **200** (po `start_period` healthcheck, ~3 min pierwszy start).
|
||||
|
||||
## Polityka GPU
|
||||
|
||||
| Scenariusz | Akcja |
|
||||
|------------|-------|
|
||||
| Chat LLM (LocalAI) | `comfyui` stopped lub bez modelu w VRAM |
|
||||
| Generowanie obrazów (ComfyUI) | `localai` stopped |
|
||||
| Portainer | Stop/Start kontenerów według potrzeb |
|
||||
|
||||
## Modele
|
||||
|
||||
- Brak auto-download w `compose pull`
|
||||
- Użytkownik: ComfyUI-Manager w UI lub ręcznie do `/data/apps/comfyui/models/`
|
||||
- Szacunki VRAM: SDXL ~8–12 GB; Flux ~12–20 GB (mieści się na 24 GB przy zatrzymanym LocalAI)
|
||||
|
||||
## Dostęp sieciowy
|
||||
|
||||
| Sieć | URL |
|
||||
|------|-----|
|
||||
| localhost | `http://127.0.0.1:8188` |
|
||||
| LAN (po statycznym IP) | `http://192.168.100.90:8188` (enp3s0) |
|
||||
| Reverse proxy | opcjonalnie później przez NPMPlus (jak LocalAI) |
|
||||
|
||||
## Szacowany rozmiar
|
||||
|
||||
- Obraz Docker: ~5–15 GB (zależnie od wariantu yanwk)
|
||||
- Modele SD/Flux: dziesiątki GB — użytkownik pobiera sam
|
||||
|
||||
## Powiązane dokumenty
|
||||
|
||||
- [`COMFYUI-HANDOFF.md`](COMFYUI-HANDOFF.md)
|
||||
- [`STABILITYMATRIX-RESEARCH.md`](STABILITYMATRIX-RESEARCH.md)
|
||||
- [`manual-tutorial/07-comfyui-stack.md`](../manual-tutorial/07-comfyui-stack.md)
|
||||
- [`DOCKER-UI-DEPLOYMENT.md`](DOCKER-UI-DEPLOYMENT.md) — polityka GPU w Portainer
|
||||
@@ -0,0 +1,82 @@
|
||||
# Handoff: ComfyUI stack
|
||||
|
||||
Data: 2026-07-04
|
||||
Dla kolejnego agenta / sesji Cursor
|
||||
|
||||
## Co zrobiono
|
||||
|
||||
1. Research Stability Matrix → decyzja **nie instalować** ([`STABILITYMATRIX-RESEARCH.md`](STABILITYMATRIX-RESEARCH.md))
|
||||
2. Utworzono pełny stack [`stacks/comfyui/`](../stacks/comfyui/) — wzorzec `localai`
|
||||
3. Dokumentacja wdrożenia i tutorial rozdział 07
|
||||
4. Zaktualizowano BACKLOG, Portainer README, politykę GPU LocalAI ↔ ComfyUI
|
||||
|
||||
## Co użytkownik musi zrobić na hoście
|
||||
|
||||
```bash
|
||||
cd ~/cursor/ubuntu-bare-metal/stacks/comfyui
|
||||
cp .env.example .env
|
||||
./scripts/start.sh
|
||||
```
|
||||
|
||||
Pierwszy start: kopia ComfyUI do `/data/apps/comfyui/storage/` — kilka minut, duży pull obrazu.
|
||||
|
||||
Przed generowaniem obrazów z dużym modelem:
|
||||
|
||||
```bash
|
||||
cd ../localai && docker compose --profile localai stop localai
|
||||
```
|
||||
|
||||
## Weryfikacja po wdrożeniu
|
||||
|
||||
```bash
|
||||
docker ps --filter name=comfyui
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8188/
|
||||
nvidia-smi
|
||||
```
|
||||
|
||||
UI: `http://192.168.100.90:8188` (LAN, enp3s0) lub localhost.
|
||||
|
||||
## Otwarte / następne kroki
|
||||
|
||||
| Priorytet | Zadanie |
|
||||
|-----------|---------|
|
||||
| P1 | Uruchomić stack na hoście (`./scripts/start.sh`) — **nie wykonane w repo-only** |
|
||||
| P2 | Pobrać pierwszy checkpoint SD/SDXL do `models/` |
|
||||
| P2 | NPMPlus reverse proxy dla ComfyUI (opcjonalnie, jak LocalAI) |
|
||||
| P3 | Bind `127.0.0.1:8188` po proxy (bezpieczeństwo) |
|
||||
|
||||
## Polityka GPU (przypomnienie)
|
||||
|
||||
RTX 3090 Ti 24 GB — **jeden** duży workload GPU:
|
||||
|
||||
- LocalAI (LLM) **lub** ComfyUI (SD/Flux) z modelem w VRAM
|
||||
- W Portainer: Stop/Start `localai` / `comfyui`
|
||||
|
||||
## Pliki kluczowe
|
||||
|
||||
```
|
||||
stacks/comfyui/
|
||||
├── docker-compose.yml # profile comfyui, port 8188, gpus: all
|
||||
├── .env.example
|
||||
└── scripts/start.sh # ostrzeżenie gdy localai running
|
||||
|
||||
coding-agent/
|
||||
├── STABILITYMATRIX-RESEARCH.md
|
||||
├── COMFYUI-DEPLOYMENT.md
|
||||
└── COMFYUI-HANDOFF.md # ten plik
|
||||
|
||||
manual-tutorial/07-comfyui-stack.md
|
||||
```
|
||||
|
||||
## Czego nie robić bez prośby
|
||||
|
||||
- `git commit` / `git push`
|
||||
- Pobieranie modeli SD przy `docker compose pull`
|
||||
- Instalacja Stability Matrix AppImage
|
||||
- Edycja `.cursor/plans/`
|
||||
|
||||
## Powiązane
|
||||
|
||||
- [`BACKLOG.md`](BACKLOG.md)
|
||||
- [`STATE.md`](STATE.md) — zaktualizuj po uruchomieniu na hoście
|
||||
- [`SWARMUI-REMOVAL.md`](SWARMUI-REMOVAL.md)
|
||||
@@ -0,0 +1,149 @@
|
||||
# Control plane — jeden plik `.env`
|
||||
|
||||
**Data:** 2026-07-05
|
||||
**Status:** wdrożone
|
||||
|
||||
gpu-fan (agent NVML) i Server UI (panel stacków) używają **jednego** pliku credentials.
|
||||
|
||||
## Kanoniczne ścieżki
|
||||
|
||||
| Środowisko | Plik |
|
||||
|------------|------|
|
||||
| Produkcja (systemd) | `/opt/control-plane/.env` |
|
||||
| Dev w repo | `stacks/control-plane/.env` |
|
||||
| Szablon | `stacks/control-plane/.env.example` |
|
||||
|
||||
Oba serwisy systemd:
|
||||
|
||||
```ini
|
||||
EnvironmentFile=-/opt/control-plane/.env
|
||||
```
|
||||
|
||||
- `server-ui.service`
|
||||
- `gpu-fan.service`
|
||||
|
||||
## Zmienne
|
||||
|
||||
| Zmienna | Opis |
|
||||
|---------|------|
|
||||
| `API_KEY` | **Wspólny** klucz: auth panelu Server UI (`X-API-Key`) + auth agenta gpu-fan (proxy `/api/gpu-fan/*`) |
|
||||
| `SERVER_UI_HOST`, `SERVER_UI_PORT` | Bind Server UI (domyślnie `0.0.0.0:8091`) |
|
||||
| `REPO_ROOT` | Ścieżka do `ubuntu-bare-metal` (wymagane dla Docker Server UI) |
|
||||
| `DOCKER_GID` | GID grupy docker (Docker install) |
|
||||
| `GPU_FAN_AGENT_URL` | URL agenta z perspektywy Server UI (`http://127.0.0.1:18090` native, `http://host.docker.internal:18090` Docker) |
|
||||
| `GPU_FAN_API_HOST`, `GPU_FAN_API_PORT` | Bind agenta gpu-fan (domyślnie `127.0.0.1:18090`) |
|
||||
| `CURVE_PATH`, `POLL_INTERVAL`, `GPU_INDEX` | Konfiguracja NVML / krzywej wentylatorów |
|
||||
|
||||
**Usunięte:** `GPU_FAN_AGENT_KEY` — duplikat `API_KEY`.
|
||||
|
||||
## Ładowanie w Pythonie
|
||||
|
||||
[`stacks/control-plane/env_loader.py`](../stacks/control-plane/env_loader.py) — `load_control_plane_env(stack_dir)`:
|
||||
|
||||
**Produkcja** (`stack_dir` pod `/opt/`): tylko `/opt/control-plane/.env` + `os.environ` (systemd).
|
||||
|
||||
**Dev** (repo `stacks/*`): tylko `stacks/control-plane/.env` + `os.environ`.
|
||||
|
||||
**Nie ładowane:** `stacks/server-ui/.env`, `/opt/server-ui/.env` (legacy — `API_KEY` usuwany przez `setup-control-plane-env.sh`).
|
||||
|
||||
Kolejność priorytetu: pliki env → **`os.environ` wygrywa** (systemd `EnvironmentFile`).
|
||||
|
||||
`api_key_source(stack_dir, values)` — log przy starcie Server UI (bez ujawniania sekretu).
|
||||
|
||||
Produkcja: `env_loader.py` kopiowany do `/opt/control-plane/env_loader.py` przez `setup-control-plane-env.sh`.
|
||||
|
||||
## Instalacja / migracja
|
||||
|
||||
```bash
|
||||
sudo bash stacks/server-ui/scripts/setup-control-plane-env.sh
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart gpu-fan server-ui
|
||||
```
|
||||
|
||||
Skrypt:
|
||||
|
||||
- tworzy `/opt/control-plane/.env` z example jeśli brak
|
||||
- **migruje** z `/opt/server-ui/.env` i `/opt/gpu-fan/.env` (backup `.env.bak.<timestamp>`)
|
||||
- generuje `API_KEY` jeśli brak lub `change-me`
|
||||
- **sync** `stacks/control-plane/.env` z produkcją (ten sam `API_KEY`)
|
||||
- **usuwa** `API_KEY` / `GPU_FAN_AGENT_KEY` z legacy `stacks/server-ui/.env`
|
||||
- wypisuje gotowy URL z kluczem (`print-api-key-instructions.sh`)
|
||||
|
||||
Instrukcja dla użytkownika po instalacji:
|
||||
|
||||
```bash
|
||||
bash stacks/server-ui/scripts/show-api-key.sh
|
||||
```
|
||||
|
||||
Pełna instalacja:
|
||||
|
||||
```bash
|
||||
sudo bash stacks/server-ui/scripts/install-control-plane.sh
|
||||
```
|
||||
|
||||
## Weryfikacja
|
||||
|
||||
```bash
|
||||
bash stacks/server-ui/scripts/show-api-key.sh
|
||||
|
||||
API_KEY=$(sudo grep ^API_KEY= /opt/control-plane/.env | cut -d= -f2)
|
||||
|
||||
# Test klucza w panelu
|
||||
curl -s -X POST http://127.0.0.1:8091/api/auth/verify \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"api_key\":\"${API_KEY}\"}"
|
||||
|
||||
# Agent bezpośrednio
|
||||
curl -s http://127.0.0.1:18090/api/status -H "X-API-Key: ${API_KEY}"
|
||||
|
||||
# Proxy przez Server UI
|
||||
curl -s http://127.0.0.1:8091/api/gpu-fan/health -H "X-API-Key: ${API_KEY}"
|
||||
```
|
||||
|
||||
Oczekiwane: JSON z danymi / `"ok": true`.
|
||||
|
||||
## Docker Server UI
|
||||
|
||||
[`stacks/server-ui/docker-compose.yml`](../stacks/server-ui/docker-compose.yml):
|
||||
|
||||
```yaml
|
||||
env_file:
|
||||
- ../control-plane/.env
|
||||
```
|
||||
|
||||
## Auth flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Browser
|
||||
participant ServerUI
|
||||
participant GpuFanAgent
|
||||
Browser->>ServerUI: X-API-Key: API_KEY
|
||||
ServerUI->>GpuFanAgent: X-API-Key: API_KEY (ten sam)
|
||||
GpuFanAgent-->>ServerUI: status JSON
|
||||
ServerUI-->>Browser: proxy response
|
||||
```
|
||||
|
||||
## Poza scope
|
||||
|
||||
Sekrety workloadów Docker (`LOCALAI_API_KEY`, `HF_TOKEN`, `INITIAL_ADMIN_PASSWORD`, …) pozostają w `stacks/<nazwa>/.env` — edycja portów przez Server UI bez zmian.
|
||||
|
||||
## Pliki zmienione (implementacja)
|
||||
|
||||
- `stacks/control-plane/` — `.env.example`, `env_loader.py`
|
||||
- `stacks/server-ui/scripts/setup-control-plane-env.sh`
|
||||
- `server-ui.service`, `gpu-fan.service`
|
||||
- `server-ui/app.py`, `gpu_fan_proxy.py`, `gpu-fan/fan_daemon.py`, `gpu-fan/app.py`
|
||||
- Instalatory: `install-control-plane.sh`, `install.sh`, `install-docker.sh`, `gpu-fan/install.sh`
|
||||
- Skrypty dev: `start.sh`, `deploy-gpu-fan-fix.sh`, `restart-stack.sh`, `enable-lan.sh`, `status.sh`
|
||||
|
||||
## Troubleshooting — Invalid API key
|
||||
|
||||
1. Klucz tylko z `/opt/control-plane/.env` (`sudo grep ^API_KEY= …`)
|
||||
2. UI: **Zapisz** → **Sprawdź klucz**
|
||||
3. Sync: `sudo bash stacks/server-ui/scripts/setup-control-plane-env.sh`
|
||||
4. Tutorial: [`manual-tutorial/04a-api-key.md`](../manual-tutorial/04a-api-key.md)
|
||||
|
||||
## Legacy
|
||||
|
||||
Stare pliki `/opt/server-ui/.env` i `/opt/gpu-fan/.env` nie są już czytane przez systemd. Po migracji można je usunąć (backup w `.env.bak.*`).
|
||||
@@ -0,0 +1,91 @@
|
||||
# Konwencje projektu
|
||||
|
||||
Zasady spójne dla wszystkich stacków w `ubuntu-bare-metal`.
|
||||
|
||||
## Stack Docker
|
||||
|
||||
Każdy stack w `stacks/<nazwa>/`:
|
||||
|
||||
```
|
||||
stacks/<nazwa>/
|
||||
├── README.md
|
||||
├── docker-compose.yml
|
||||
├── .env.example # szablon bez sekretów
|
||||
├── .env # lokalny, w .gitignore
|
||||
├── .gitignore
|
||||
└── scripts/
|
||||
├── start.sh # walidacja + pull + up
|
||||
└── pull.sh # opcjonalnie — tylko obraz
|
||||
```
|
||||
|
||||
- **Profile compose:** `profiles: [<nazwa>]` — start jawny: `docker compose --profile <nazwa> up -d`
|
||||
- **GPU:** `gpus: all` + `CUDA_VISIBLE_DEVICES=0` (tylko RTX 3090 Ti)
|
||||
- **Dane:** bind mounty pod `${DATA_ROOT}/apps/<nazwa>/`, nie named volumes (backup na `/data`)
|
||||
- **Restart:** `restart: unless-stopped` na produkcji
|
||||
|
||||
## Zmienne środowiskowe
|
||||
|
||||
| Zmienna | Typowy zakres |
|
||||
|---------|----------------|
|
||||
| `DATA_ROOT` | `/data` |
|
||||
| `CUDA_VISIBLE_DEVICES` | `0` |
|
||||
| `HF_TOKEN` | vLLM — tylko gated models |
|
||||
| `LOCALAI_API_KEY` | LocalAI — legacy API auth |
|
||||
| `LOCALAI_PORT` | domyślnie 8080 (użytkownik może zmienić, np. 8070) |
|
||||
| `COMFYUI_PORT` | domyślnie 8188 |
|
||||
| `VLLM_PORT` | domyślnie 8000 |
|
||||
|
||||
W **Server UI** porty edytowalnych stacków są zapisywane w `stacks/<nazwa>/.env`. Metadane (`port_env`, `port_default`, `port_editable`) w `stacks/server-ui/stacks.yaml`.
|
||||
|
||||
Sekrety **tylko** w `.env` na serwerze. W `.env.example` — puste lub komentarz.
|
||||
|
||||
### Control plane (gpu-fan + Server UI)
|
||||
|
||||
Jeden plik credentials — **nie** osobne `/opt/server-ui/.env` i `/opt/gpu-fan/.env`:
|
||||
|
||||
| Środowisko | Plik |
|
||||
|------------|------|
|
||||
| Produkcja | `/opt/control-plane/.env` |
|
||||
| Dev | `stacks/control-plane/.env` |
|
||||
|
||||
Wspólny `API_KEY` dla panelu i proxy gpu-fan. Szczegóły: [`CONTROL-PLANE-ENV.md`](CONTROL-PLANE-ENV.md).
|
||||
|
||||
## Tutorial (`manual-tutorial/`)
|
||||
|
||||
- Numeracja: `00`, `01`, … `03b`, `04`, `05`, …
|
||||
- Język: polski; komendy i nazwy pakietów po angielsku
|
||||
- Każdy rozdział: cel, wymagania, komendy, weryfikacja, troubleshooting
|
||||
- Użytkownik potwierdza ukończenie („krok X gotowy”) przed kolejnym rozdziałem
|
||||
|
||||
## Modele LLM
|
||||
|
||||
- **Brak auto-download** przy `docker compose pull`
|
||||
- Katalog centralny vLLM: `stacks/vllm/models.catalog.yaml`
|
||||
- GGUF → LocalAI lub llama.cpp; AWQ/HF → vLLM
|
||||
- Kilka modeli na dysku, **jeden aktywny w VRAM**
|
||||
|
||||
## Git i bezpieczeństwo
|
||||
|
||||
- Nie commituj: `.env`, `upstream/`, kluczy API, tokenów HF
|
||||
- `coding-agent/` — bez wartości sekretów
|
||||
- Commity tylko na wyraźną prośbę użytkownika
|
||||
- Nie `git push --force` na main/master
|
||||
|
||||
## Ścieżki bezwzględne na serwerze
|
||||
|
||||
Domyślny root repo:
|
||||
|
||||
```
|
||||
/home/tomasz-syn-grzegorza/cursor/ubuntu-bare-metal
|
||||
```
|
||||
|
||||
W tutorialach używaj tej pełnej ścieżki w przykładach `cd`.
|
||||
|
||||
## GMKtec K11 — numeracja NVMe
|
||||
|
||||
| Urządzenie | Rozmiar | Rola |
|
||||
|------------|---------|------|
|
||||
| `nvme1n1` | ~128 GB | System `/` |
|
||||
| `nvme0n1` | ~1 TB | Dane `/data` |
|
||||
|
||||
Zawsze weryfikuj po `SIZE` i `MOUNTPOINT`, nie po numerze urządzenia.
|
||||
@@ -0,0 +1,90 @@
|
||||
# Wdrożenie UI zarządzania Dockerem (Server UI)
|
||||
|
||||
Data: 2026-07-05 (aktualizacja: instalator control plane)
|
||||
Host: `gmktec-k11` (`192.168.100.90` — enp3s0)
|
||||
|
||||
## Kontekst
|
||||
|
||||
- Portainer usunięty 2026-07-04 — [`PORTAINER-REMOVAL.md`](PORTAINER-REMOVAL.md)
|
||||
- Dockge usunięty 2026-07-04 — [`DOCKGE-REMOVAL.md`](DOCKGE-REMOVAL.md)
|
||||
- **Własny panel:** [`stacks/server-ui/`](../stacks/server-ui/) — port **8091**
|
||||
- **gpu-fan:** host agent :18090 (tylko systemd, bez Docker)
|
||||
|
||||
---
|
||||
|
||||
## Podsumowanie
|
||||
|
||||
| Element | Wartość |
|
||||
|---------|---------|
|
||||
| Stack | [`stacks/server-ui/`](../stacks/server-ui/) |
|
||||
| Runtime native | systemd (`server-ui.service`) → `/opt/server-ui` |
|
||||
| Runtime Docker | `docker compose --profile server-ui` (opcjonalnie) |
|
||||
| HTTP | **http://192.168.100.90:8091** |
|
||||
| Auth mutacji | `X-API-Key` — `/opt/control-plane/.env` (native) lub `stacks/control-plane/.env` (docker) |
|
||||
| Stacki | `localai`, `vllm`, `comfyui`, `npmplus` |
|
||||
|
||||
**Server UI nie pojawia się w `docker ps` przy instalacji native** — to usługa systemd, nie kontener.
|
||||
|
||||
---
|
||||
|
||||
## Instalacja (zalecana)
|
||||
|
||||
```bash
|
||||
cd ~/cursor/ubuntu-bare-metal/stacks/server-ui
|
||||
sudo ./scripts/install-control-plane.sh
|
||||
```
|
||||
|
||||
Menu: gpu-fan native (Y/n) + Server UI native (1) / Docker (2) / skip (3).
|
||||
|
||||
```bash
|
||||
# Bez pytań: gpu-fan + server-ui native
|
||||
sudo ./scripts/install-control-plane.sh -y
|
||||
|
||||
# Tylko Server UI Docker
|
||||
sudo ./scripts/install-control-plane.sh --gpu-fan=no --server-ui=docker
|
||||
```
|
||||
|
||||
### Tylko Server UI
|
||||
|
||||
| Tryb | Komenda |
|
||||
|------|---------|
|
||||
| Native | `sudo ./scripts/install.sh` |
|
||||
| Docker | `sudo ./scripts/install-docker.sh` |
|
||||
|
||||
Klucze:
|
||||
```bash
|
||||
grep ^API_KEY= /opt/control-plane/.env
|
||||
```
|
||||
|
||||
Tutorial: [`manual-tutorial/08-server-ui-install.md`](../manual-tutorial/08-server-ui-install.md)
|
||||
|
||||
---
|
||||
|
||||
## Pierwsze użycie
|
||||
|
||||
1. Otwórz **http://192.168.100.90:8091/?api_key=KLUCZ**
|
||||
2. Karty stacków: Start/Stop, logi, edycja portów, linki (8070, 8188, …)
|
||||
3. Zakładka **GPU Fan** — wymaga działającego `gpu-fan.service`
|
||||
|
||||
## Polityka GPU
|
||||
|
||||
RTX 3090 Ti 24 GB — jeden duży workload GPU naraz. API zwraca **409** przy konflikcie.
|
||||
|
||||
## Weryfikacja
|
||||
|
||||
```bash
|
||||
# Native
|
||||
systemctl status server-ui gpu-fan
|
||||
curl -s http://127.0.0.1:8091/api/health
|
||||
|
||||
# Docker
|
||||
docker compose --profile server-ui ps
|
||||
curl -s http://127.0.0.1:8091/api/stacks
|
||||
```
|
||||
|
||||
## Powiązane
|
||||
|
||||
- [`SERVER-UI-INSTALL-OPTIONS.md`](SERVER-UI-INSTALL-OPTIONS.md)
|
||||
- [`SERVER-UI-PORT-CONFIG.md`](SERVER-UI-PORT-CONFIG.md)
|
||||
- [`DOCKER-UI-HANDOFF.md`](DOCKER-UI-HANDOFF.md)
|
||||
- [`stacks/server-ui/README.md`](../stacks/server-ui/README.md)
|
||||
@@ -0,0 +1,55 @@
|
||||
# Handoff — Server UI (Docker stack manager)
|
||||
|
||||
## Stan: kod gotowy (2026-07-04)
|
||||
|
||||
- [x] `stacks/server-ui/` — app, API, dashboard, systemd unit
|
||||
- [x] Dockge usunięty z Dockera
|
||||
- [ ] **Produkcja:** `sudo ./scripts/install.sh` (wymaga hasła sudo na hoście)
|
||||
- [x] Dev mode zweryfikowany: `:8091` HTTP 200, `/api/gpu`, `/api/stacks`
|
||||
|
||||
Raport: [`DOCKER-UI-DEPLOYMENT.md`](DOCKER-UI-DEPLOYMENT.md)
|
||||
|
||||
---
|
||||
|
||||
## URL
|
||||
|
||||
```
|
||||
http://192.168.100.90:8091
|
||||
```
|
||||
|
||||
API Key po `sudo ./scripts/install.sh`:
|
||||
|
||||
```bash
|
||||
grep ^API_KEY= /opt/control-plane/.env
|
||||
```
|
||||
|
||||
Dev mode (z repo):
|
||||
|
||||
```bash
|
||||
cp stacks/control-plane/.env.example stacks/control-plane/.env
|
||||
cd stacks/server-ui && ./scripts/start.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zarządzanie GPU
|
||||
|
||||
Stop/Start stacków `localai` / `comfyui` / `vllm` w UI — jeden aktywny GPU workload naraz.
|
||||
|
||||
---
|
||||
|
||||
## Czego nie robić
|
||||
|
||||
- Nie przywracać Portainera/Dockge bez uzgodnienia
|
||||
- Nie commitować `.env` z API_KEY
|
||||
- Nie `git commit` bez prośby użytkownika
|
||||
|
||||
---
|
||||
|
||||
## Diagnostyka
|
||||
|
||||
```bash
|
||||
systemctl status server-ui
|
||||
journalctl -u server-ui -f
|
||||
curl -s http://127.0.0.1:8091/api/stacks
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
# Usunięcie Dockge
|
||||
|
||||
Data: 2026-07-04
|
||||
Host: `gmktec-k11`
|
||||
|
||||
## Powód
|
||||
|
||||
Dockge wdrożony tymczasowo po usunięciu Portainera. Zastąpiony **własnym Server UI** ([`stacks/server-ui/`](../stacks/server-ui/)) — skupiony panel pod znane stacki, bez zewnętrznej zależności.
|
||||
|
||||
## Co usunięto
|
||||
|
||||
| Element | Status |
|
||||
|---------|--------|
|
||||
| Kontener `dockge` | usunięty |
|
||||
| Obraz `louislam/dockge:1` | usunięty |
|
||||
| Stack Docker | `docker compose --profile dockge down` |
|
||||
|
||||
## Co pozostało (opcjonalnie do ręcznego cleanup)
|
||||
|
||||
```
|
||||
/data/apps/dockge/data/ # dane Dockge (nieużywane)
|
||||
/data/apps/stacks/ # symlinki do repo (nieużywane przez Server UI)
|
||||
stacks/dockge/ # deprecated w repo
|
||||
```
|
||||
|
||||
## Zamiennik
|
||||
|
||||
**Server UI** — port **8091**, systemd na hoście:
|
||||
|
||||
- [`stacks/server-ui/README.md`](../stacks/server-ui/README.md)
|
||||
- [`DOCKER-UI-DEPLOYMENT.md`](DOCKER-UI-DEPLOYMENT.md)
|
||||
|
||||
## Powiązane
|
||||
|
||||
- [`PORTAINER-REMOVAL.md`](PORTAINER-REMOVAL.md)
|
||||
- [`DOCKER-UI-HANDOFF.md`](DOCKER-UI-HANDOFF.md)
|
||||
@@ -0,0 +1,113 @@
|
||||
# Przygotowanie repozytorium pod Gitea
|
||||
|
||||
Raport z sesji przygotowującej pierwszy commit `ubuntu-bare-metal`.
|
||||
|
||||
## Analiza stanu wyjściowego
|
||||
|
||||
| Aspekt | Wynik |
|
||||
|--------|--------|
|
||||
| Git | Katalog **nie był** repozytorium — wykonano `git init` |
|
||||
| Gitea remote | **Nie skonfigurowany** — URL do podania przez użytkownika |
|
||||
| Pliki `.env` | 7 na dysku (sekrety) — **wykluczone** przez root `.gitignore` |
|
||||
| `.venv` | `stacks/server-ui/.venv`, `stacks/gpu-fan/.venv` (~54 MB każdy) — **wykluczone** |
|
||||
| Bloker bezpieczeństwa | Prawdziwy `API_KEY` w `manual-tutorial/04a-api-key.md` — **zredagowany** na placeholder |
|
||||
|
||||
## Zmiany przygotowawcze (ta sesja)
|
||||
|
||||
1. **Root [`.gitignore`](../.gitignore)** — `.env`, `.venv`, `__pycache__`, `upstream/`, `.cursor/`
|
||||
2. **Redakcja** [`manual-tutorial/04a-api-key.md`](../manual-tutorial/04a-api-key.md) — przykład `API_KEY` to placeholder `xxxxxxxx…`, nie produkcyjny klucz
|
||||
3. **`git init`** + branch `main`
|
||||
4. **Pierwszy commit** — pełny import repo (bez push)
|
||||
|
||||
## Zakres pierwszego commita
|
||||
|
||||
```
|
||||
ubuntu-bare-metal/
|
||||
├── README.md
|
||||
├── .gitignore
|
||||
├── scripts/
|
||||
├── manual-tutorial/ # 13 rozdziałów (00–10, 04a)
|
||||
├── coding-agent/ # handoff, ADR, deployment notes
|
||||
└── stacks/
|
||||
├── vllm/
|
||||
├── localai/
|
||||
├── comfyui/
|
||||
├── gpu-fan/
|
||||
├── server-ui/ # panel :8091, CLI PTY, Pliki, GPU Fan
|
||||
├── control-plane/ # env_loader, wspólny API_KEY
|
||||
├── npmplus/
|
||||
└── llamacpp/
|
||||
```
|
||||
|
||||
### Nie trafia do gita
|
||||
|
||||
- `**/.env` (produkcja: `/opt/control-plane/.env` poza repo)
|
||||
- `**/.venv/`, `**/__pycache__/`
|
||||
- `stacks/localai/upstream/` (jeśli sklonowany lokalnie)
|
||||
|
||||
## Ostatnie zmiany Server UI (GPU Fan chart)
|
||||
|
||||
W [`stacks/server-ui/static/index.html`](../stacks/server-ui/static/index.html):
|
||||
|
||||
- Siatka i podziałka temperatury (°C) / prędkości (%)
|
||||
- Linijki kreskowe (zamiast kolorowych pasków gradientowych)
|
||||
- Odstęp etykiet Y od lewej linijki (`yLabelX`)
|
||||
- Odstęp etykiet X od dolnej linijki (`xLabelY`)
|
||||
- Responsywne SVG: `aspect-ratio: 600/360`, `height: auto` (bez rozciągania na mobile)
|
||||
- Wyśrodkowanie wykresu w karcie: `margin: 0 auto`
|
||||
|
||||
Powiązane docs: [`manual-tutorial/06-gpu-fan-control.md`](../manual-tutorial/06-gpu-fan-control.md), [`SERVER-UI-CLI.md`](SERVER-UI-CLI.md), [`SERVER-UI-CLI-BUGFIX.md`](SERVER-UI-CLI-BUGFIX.md).
|
||||
|
||||
## Push na Gitea (gdy masz URL)
|
||||
|
||||
```bash
|
||||
cd ~/cursor/ubuntu-bare-metal
|
||||
|
||||
# jednorazowo — podstaw URL z Gitea (SSH lub HTTPS)
|
||||
git remote add origin <URL_GITEA>
|
||||
|
||||
# jeśli repo na Gitea ma już README / initial commit:
|
||||
# git pull origin main --allow-unrelated-histories
|
||||
# (rozwiąż ewentualne konflikty, potem push)
|
||||
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
Przykładowe URL:
|
||||
|
||||
- HTTPS: `https://gitea.example.com/user/ubuntu-bare-metal.git`
|
||||
- SSH: `git@gitea.example.com:user/ubuntu-bare-metal.git`
|
||||
|
||||
## Checklist bezpieczeństwa przed każdym commitem
|
||||
|
||||
```bash
|
||||
cd ~/cursor/ubuntu-bare-metal
|
||||
|
||||
# 1. Brak sekretów w staged plikach
|
||||
git diff --cached | grep -iE 'API_KEY=|HF_TOKEN=|password=|secret=' && echo 'STOP: sekret w diff!' || echo 'OK'
|
||||
|
||||
# 2. .env nie jest śledzony
|
||||
git status --short | grep '\.env' && echo 'STOP: .env w staging!' || echo 'OK'
|
||||
|
||||
# 3. Dry-run — co trafi do commita
|
||||
git add -n .
|
||||
|
||||
# 4. Szukaj przypadkowych kluczy w docs
|
||||
grep -rE '[a-f0-9]{32}' manual-tutorial/ coding-agent/ --include='*.md' | grep -v xxxxxxxx
|
||||
```
|
||||
|
||||
## Zalecenia po redakcji klucza w tutorialu
|
||||
|
||||
Klucz produkcyjny był kiedyś w `04a-api-key.md`. Rozważ **rotację** `API_KEY` w `/opt/control-plane/.env` i restart `server-ui` / `gpu-fan`, jeśli repo będzie publiczne lub współdzielone.
|
||||
|
||||
## Weryfikacja po clone na innym hoście
|
||||
|
||||
```bash
|
||||
git clone <URL_GITEA> ubuntu-bare-metal
|
||||
cd ubuntu-bare-metal/stacks/control-plane
|
||||
cp .env.example .env # uzupełnij API_KEY lokalnie
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Wygenerowano przy pierwszym imporcie git — lipiec 2026.*
|
||||
@@ -0,0 +1,104 @@
|
||||
# Handoff — sesja ubuntu-bare-metal
|
||||
|
||||
Dokument dla agenta kontynuującego pracę. Ostatni znany stan po sesji konfiguracji serwera LLM na GMKtec K11.
|
||||
|
||||
## Kontekst projektu
|
||||
|
||||
| Element | Wartość |
|
||||
|---------|---------|
|
||||
| Repo | `/home/tomasz-syn-grzegorza/cursor/ubuntu-bare-metal` |
|
||||
| Urządzenie | GMKtec K11 |
|
||||
| OS | Ubuntu 26.04 LTS minimized (`resolute`) |
|
||||
| GPU | NVIDIA GeForce RTX 3090 Ti, 24 GB VRAM (`CUDA_VISIBLE_DEVICES=0`) |
|
||||
| RAM | ~29 GiB |
|
||||
| Dysk systemowy | `nvme1n1` 128 GB → `/` |
|
||||
| Dysk danych | `nvme0n1` 1 TB → `/data` |
|
||||
| Docker data-root | `/data/docker` |
|
||||
| Użytkownik | `tomasz-syn-grzegorza` (sudo) |
|
||||
|
||||
**Workflow:** tutorial krok po kroku w [`manual-tutorial/`](../manual-tutorial/). Użytkownik czyta markdown i wykonuje komendy ręcznie. Agent nie może wpisać hasła `sudo` interaktywnie — skrypty wymagające sudo użytkownik uruchamia w swoim terminalu SSH.
|
||||
|
||||
## Ukończone (infrastruktura + repo)
|
||||
|
||||
| Rozdział / obszar | Status |
|
||||
|-------------------|--------|
|
||||
| 01 — Docker CE ~29.x | Ukończony przez użytkownika |
|
||||
| 02 — NVIDIA driver 595 open | Ukończony |
|
||||
| 03 — NVIDIA Container Toolkit 1.19 | Ukończony |
|
||||
| 03b — narzędzia minimized | Dokumentacja gotowa |
|
||||
| 04A — dysk 1 TB, fstab, migracja Docker | Ukończony (`scripts/setup-data-disk.sh`) |
|
||||
| 04B — stack vLLM | Repo gotowe; obraz Docker pobrany; **kontener nie uruchomiony** (brak modelu) |
|
||||
| Katalog modeli vLLM | `stacks/vllm/models.catalog.yaml` + skrypty list/download/switch |
|
||||
| Placeholder llama.cpp | `stacks/llamacpp/README.md` |
|
||||
| 05 — stack LocalAI | Repo gotowe; **kontener uruchomiony przez użytkownika** |
|
||||
| LocalAI API key | Użytkownik dodał `LOCALAI_API_KEY` do `stacks/localai/.env` — **wartość nie dokumentowana tutaj** |
|
||||
|
||||
## Stan runtime (faktyczny na serwerze)
|
||||
|
||||
| Serwis | Stan |
|
||||
|--------|------|
|
||||
| vLLM | Obraz `vllm/vllm-openai:latest` na dysku; kontener **nie działa** (`VLLM_MODEL` puste w `.env`) |
|
||||
| LocalAI | Kontener `localai` **running**; obraz `localai/localai:v4.4.3-gpu-nvidia-cuda-13` |
|
||||
| Model LocalAI | Użytkownik **w trakcie pobierania** modelu (prawdopodobnie przez UI / galerię) |
|
||||
| API key | Ustawiony w `.env` — **wymaga** przekazania do kontenera w `docker-compose.yml` + restart (patrz BACKLOG P0) |
|
||||
|
||||
## Porty
|
||||
|
||||
| Serwis | Port w dokumentacji | Port faktyczny (`.env` użytkownika) |
|
||||
|--------|---------------------|-------------------------------------|
|
||||
| vLLM API | 8000 | 8000 (nieaktywny) |
|
||||
| LocalAI UI + API | 8080 | **8070** |
|
||||
| llama.cpp (plan) | 8001 | nie wdrożony |
|
||||
|
||||
Wewnątrz kontenera LocalAI zawsze nasłuchuje na **8080**; mapowanie hosta ustawia `LOCALAI_PORT` w `.env`.
|
||||
|
||||
## Decyzje techniczne (nie zmieniać bez uzgodnienia)
|
||||
|
||||
1. **GGUF** z lmstudio-community **nie działa** w standardowym `vllm/vllm-openai` — użycie przez LocalAI (llama.cpp backend) lub przyszły host llama.cpp.
|
||||
2. **vLLM interim** dla Q4-odpowiednika: AWQ `Qwen/Qwen3.6-27B-Instruct-AWQ`, profil `qwen3.6-27b-awq-128k`, kontekst 128K (`MAX_MODEL_LEN=131072`), KV `fp8`.
|
||||
3. **LocalAI:** oficjalny obraz Docker (nie build ze źródeł); bez `command: phi-2` (brak auto-pobierania modelu przy starcie).
|
||||
4. **Jeden duży model w VRAM** naraz na 24 GB — nie uruchamiać vLLM + LocalAI z dużymi modelami równolegle.
|
||||
5. **Modele docelowe (katalog):** Qwen3.6-27B Q4_K_M GGUF, Gemma 4 12B Q4_0 GGUF (lmstudio-community).
|
||||
6. **Brak domyślnego modelu** przy instalacji stacków — pobieranie on-demand.
|
||||
|
||||
## Co zrobiono w repo (implementacja agenta)
|
||||
|
||||
### vLLM (`stacks/vllm/`)
|
||||
|
||||
- `models.catalog.yaml` — GGUF (llamacpp) + AWQ (vllm)
|
||||
- Elastyczny `docker-compose.yml` + `vllm-entrypoint.sh` (`QUANTIZATION`, `VLLM_EXTRA_ARGS`)
|
||||
- Skrypty: `list-models.sh`, `download-model.sh`, `switch-model.sh`, `start.sh`
|
||||
- Profile: `qwen3.6-27b-awq-128k.env`, `_template.env`
|
||||
- Tutorial część B w `manual-tutorial/04-vllm-stack.md`
|
||||
|
||||
### LocalAI (`stacks/localai/`)
|
||||
|
||||
- `docker-compose.yml`, `.env.example`, skrypty `pull.sh`, `start.sh`, `clone-upstream.sh`
|
||||
- Bind mounty na `/data/apps/localai/{models,backends,configuration,images,data}`
|
||||
- Tutorial `manual-tutorial/05-localai-stack.md`
|
||||
- Roadmapa root README: 05 LocalAI, 06 ComfyUI, 07 firewall
|
||||
|
||||
### Inne
|
||||
|
||||
- `scripts/setup-data-disk.sh` — rozszerzony o katalogi `gguf/` i `localai/`
|
||||
|
||||
## Ostatnia rozmowa (advisory — nie wdrożone w repo)
|
||||
|
||||
Użytkownik pytał o:
|
||||
|
||||
1. **Wystawienie endpointu przez domenę** — reverse proxy (Caddy/nginx) + DNS + HTTPS; LocalAI za proxy na `127.0.0.1:PORT`.
|
||||
2. **API token** — opcja A: `LOCALAI_API_KEY` (legacy, pełny admin); opcja B: `LOCALAI_AUTH=true` + `LOCALAI_BASE_URL` (konta użytkowników).
|
||||
|
||||
Brak w repo: `Caddyfile`, rozdział proxy/TLS, wpis `LOCALAI_API_KEY` w `docker-compose.yml`.
|
||||
|
||||
## Następny agent — zacznij od
|
||||
|
||||
1. Przeczytaj [BACKLOG.md](BACKLOG.md) — sekcja **P0**.
|
||||
2. Napraw przekazanie `LOCALAI_API_KEY` do kontenera i zweryfikuj auth po restarcie.
|
||||
3. Uzgodnij z użytkownikiem port **8070** vs **8080** w dokumentacji.
|
||||
4. Po zakończeniu pobierania modelu — test chat w UI i `curl /v1/chat/completions`.
|
||||
|
||||
## Transkrypt sesji
|
||||
|
||||
Pełna historia rozmowy (Cursor):
|
||||
`/home/tomasz-syn-grzegorza/.cursor/projects/home-tomasz-syn-grzegorza/agent-transcripts/f1d1befe-e1e0-44e8-8a40-1f175e6df09c/f1d1befe-e1e0-44e8-8a40-1f175e6df09c.jsonl`
|
||||
@@ -0,0 +1,144 @@
|
||||
# Dwa adresy IP na serwerze domowym — poradnik krok po kroku
|
||||
|
||||
*Dla hosta GMKtec K11 (`gmktec-k11`), Ubuntu 26.04, dwie karty Ethernet.*
|
||||
|
||||
---
|
||||
|
||||
## Po co dwa IP?
|
||||
|
||||
Serwer ma **dwie fizyczne karty sieciowe**. Docelowo (gdy oba kable podłączone):
|
||||
|
||||
| Karta | Adres | Do czego |
|
||||
|-------|-------|----------|
|
||||
| **eno1** | `192.168.100.80` | Internet — brama domyślna `192.168.100.1` |
|
||||
| **enp3s0** | `192.168.100.90` | LAN — Portainer, LocalAI |
|
||||
|
||||
Skrypt **sam wykrywa**, które porty mają kabel, i pokazuje menu z sensownymi opcjami. Nie musisz zgadywać, który port „działa”.
|
||||
|
||||
---
|
||||
|
||||
## Zanim zaczniesz
|
||||
|
||||
### 1. Podłącz kabel(e)
|
||||
|
||||
- **Oba porty** — wybierzesz konfigurację dual (`.80` na eno1 + brama, `.90` na enp3s0).
|
||||
- **Jeden port** — skrypt zaproponuje wariant pojedynczy (np. tylko enp3s0 z `.90` i bramą).
|
||||
|
||||
Sprawdź ręcznie:
|
||||
|
||||
```bash
|
||||
ip -br link show eno1 enp3s0
|
||||
```
|
||||
|
||||
Kolumna stanu: `UP` + link = kabel w porcie.
|
||||
|
||||
### 2. Zarezerwuj adresy na routerze
|
||||
|
||||
Wyklucz z DHCP: `192.168.100.80` i `192.168.100.90`.
|
||||
|
||||
### 3. Plan B
|
||||
|
||||
Druga sesja SSH lub konsola fizyczna — po zmianie IP stara sesja może się rozłączyć.
|
||||
|
||||
---
|
||||
|
||||
## Instalacja — skrypt ze skanem i menu
|
||||
|
||||
```bash
|
||||
cd /home/tomasz-syn-grzegorza/cursor/ubuntu-bare-metal/scripts
|
||||
```
|
||||
|
||||
### Podgląd (bez zmian)
|
||||
|
||||
```bash
|
||||
sudo ./configure-static-dual-nic.sh --dry-run
|
||||
```
|
||||
|
||||
Zobaczysz skan interfejsów, domyślną opcję menu i YAML Netplan.
|
||||
|
||||
### Właściwe uruchomienie
|
||||
|
||||
```bash
|
||||
sudo ./configure-static-dual-nic.sh
|
||||
```
|
||||
|
||||
Przykład interakcji:
|
||||
|
||||
```
|
||||
=== Skan interfejsów ===
|
||||
Interfejs Stan Link IPv4
|
||||
eno1 UP NIE —
|
||||
enp3s0 UP TAK 192.168.100.2/24
|
||||
|
||||
=== Wybierz konfigurację ===
|
||||
1) Pojedynczy enp3s0: .90+brama (zalecane — usługi Docker)
|
||||
2) Pojedynczy enp3s0: .80+brama
|
||||
3) Pojedynczy enp3s0: .80+.90+brama (oba IP na jednej karcie)
|
||||
0) Anuluj
|
||||
|
||||
Wybór [1-3, domyślnie 1]:
|
||||
```
|
||||
|
||||
Gdy **oba** porty mają link:
|
||||
|
||||
```
|
||||
1) Dual: eno1=.80+brama, enp3s0=.90 (zalecane)
|
||||
2) Dual odwrócony: enp3s0=.80+brama, eno1=.90
|
||||
```
|
||||
|
||||
Skrypt dalej: backup Netplan → zapis → `netplan apply` → weryfikacja IP i ping do routera.
|
||||
|
||||
---
|
||||
|
||||
## Sprawdzenie, czy działa
|
||||
|
||||
```bash
|
||||
ip -br addr show eno1 enp3s0
|
||||
ip route show default
|
||||
ping -c 2 192.168.100.1
|
||||
```
|
||||
|
||||
Usługi (gdy `.90` na enp3s0 lub w profilu „both” na enp3s0):
|
||||
|
||||
- Server UI: `http://192.168.100.90:8091`
|
||||
- LocalAI: `http://192.168.100.90:8070`
|
||||
|
||||
---
|
||||
|
||||
## Co zrobić, gdy coś poszło nie tak
|
||||
|
||||
```bash
|
||||
sudo ./restore-dhcp-network.sh
|
||||
ip -br addr
|
||||
```
|
||||
|
||||
SSH może wrócić pod adresem DHCP (np. `192.168.100.2`).
|
||||
|
||||
Po rollbacku uruchom ponownie `configure-static-dual-nic.sh` i wybierz inną opcję z menu (np. pojedynczy enp3s0, jeśli eno1 nie współpracuje).
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Miałem kabel w eno1 i nie było IP — co się stało?**
|
||||
Stara wersja skryptu wymuszała bramę na eno1 bez uwzględnienia stanu drugiej karty. Wersja v2 używa poprawnego Netplan (`optional`, `ConfigureWithoutCarrier`) i menu zależnego od kabli.
|
||||
|
||||
**Czy muszę restartować serwer?**
|
||||
Nie — wystarczy `netplan apply` (robi to skrypt).
|
||||
|
||||
**Który adres przy port forward na routerze?**
|
||||
Zwykle **`192.168.100.90`** — sprawdź `ip a`, gdzie faktycznie siedzi `.90`.
|
||||
|
||||
---
|
||||
|
||||
## Pliki
|
||||
|
||||
| Plik | Opis |
|
||||
|------|------|
|
||||
| [`scripts/configure-static-dual-nic.sh`](../scripts/configure-static-dual-nic.sh) | Skan + menu + statyczne IP |
|
||||
| [`scripts/restore-dhcp-network.sh`](../scripts/restore-dhcp-network.sh) | Powrót do DHCP |
|
||||
| [`NETWORK-STATIC-IP-HANDOFF.md`](NETWORK-STATIC-IP-HANDOFF.md) | Notatki dla agenta |
|
||||
|
||||
---
|
||||
|
||||
*Ostatnia aktualizacja: 2026-07-04 (skrypt v2 — skan + menu).*
|
||||
@@ -0,0 +1,94 @@
|
||||
# Raport wdrożenia — statyczne IP dual NIC
|
||||
|
||||
> **Szablon** — uzupełnij po uruchomieniu `configure-static-dual-nic.sh` na hoście.
|
||||
|
||||
## Metadane
|
||||
|
||||
| Pole | Wartość |
|
||||
|------|---------|
|
||||
| Host | `gmktec-k11` |
|
||||
| Data wdrożenia | _do uzupełnienia_ |
|
||||
| Wykonał | _do uzupełnienia_ |
|
||||
| Skrypt | [`scripts/configure-static-dual-nic.sh`](../scripts/configure-static-dual-nic.sh) |
|
||||
| **Wybrany profil** | _np. dual-eno1, single-enp3s0-90_ |
|
||||
| **Opis profilu** | _np. eno1=.80+brama, enp3s0=.90_ |
|
||||
|
||||
---
|
||||
|
||||
## Skan przed apply (z outputu skryptu)
|
||||
|
||||
| Interfejs | Link | IPv4 przed |
|
||||
|-----------|------|------------|
|
||||
| eno1 | _TAK/NIE_ | _…_ |
|
||||
| enp3s0 | _TAK/NIE_ | _…_ |
|
||||
|
||||
---
|
||||
|
||||
## Konfiguracja po apply
|
||||
|
||||
| Interfejs | IP | Brama |
|
||||
|-----------|-----|-------|
|
||||
| eno1 | _…_ | _tak/nie_ |
|
||||
| enp3s0 | _…_ | _tak/nie_ |
|
||||
|
||||
---
|
||||
|
||||
## Pre-flight
|
||||
|
||||
- [ ] Co najmniej jeden interfejs z linkiem
|
||||
- [ ] `.80` i `.90` zarezerwowane na routerze
|
||||
- [ ] Backup Netplan utworzony
|
||||
|
||||
Ścieżka backupu: `_/etc/netplan/00-installer-config.yaml.bak._________`
|
||||
|
||||
---
|
||||
|
||||
## Wyniki weryfikacji (wklej output)
|
||||
|
||||
### `ip -br addr show eno1 enp3s0`
|
||||
|
||||
```
|
||||
_do uzupełnienia_
|
||||
```
|
||||
|
||||
### `ip route show default`
|
||||
|
||||
```
|
||||
_do uzupełnienia_
|
||||
```
|
||||
|
||||
### Ping
|
||||
|
||||
| Test | Wynik |
|
||||
|------|-------|
|
||||
| `ping -I <gw_nic> 192.168.100.1` | _OK / FAIL_ |
|
||||
| `ping 8.8.8.8` | _OK / FAIL_ |
|
||||
|
||||
### Usługi
|
||||
|
||||
| URL | Kod |
|
||||
|-----|-----|
|
||||
| `http://192.168.100.90:8091` (Server UI) | _…_ |
|
||||
| `http://192.168.100.90:8070` (LocalAI) | _…_ |
|
||||
|
||||
---
|
||||
|
||||
## Problemy i rozwiązania
|
||||
|
||||
_do uzupełnienia_
|
||||
|
||||
---
|
||||
|
||||
## Rollback
|
||||
|
||||
- [ ] Nie użyto
|
||||
- [ ] `restore-dhcp-network.sh` — data: _
|
||||
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
- [ ] Wdrożenie zakończone pomyślnie
|
||||
- [ ] Wymaga follow-up
|
||||
|
||||
**Następne kroki:** [`NETWORK-STATIC-IP-HANDOFF.md`](NETWORK-STATIC-IP-HANDOFF.md) — sync IP w dokumentacji.
|
||||
@@ -0,0 +1,109 @@
|
||||
# Handoff — statyczne IP (dual NIC) dla agenta kodującego
|
||||
|
||||
## Stan: skrypt v2 (skan + menu), wdrożenie przez użytkownika
|
||||
|
||||
- [x] [`scripts/configure-static-dual-nic.sh`](../scripts/configure-static-dual-nic.sh) — Netplan **1.2** (`ignore-carrier`, DNS na interfejsie z bramą; bez `networkd:`)
|
||||
- [x] [`scripts/restore-dhcp-network.sh`](../scripts/restore-dhcp-network.sh) — pomija zepsute backupy; `--force-dhcp`
|
||||
- [ ] **Użytkownik uruchamia** skrypt na hoście
|
||||
- [ ] Po apply: uzupełnić [`NETWORK-STATIC-IP-DEPLOYMENT.md`](NETWORK-STATIC-IP-DEPLOYMENT.md) (wybrany profil)
|
||||
- [ ] Zsynchronizować `192.168.100.5` → `192.168.100.90` w pozostałych handoffach
|
||||
|
||||
Artykuł: [`NETWORK-STATIC-IP-BLOG.md`](NETWORK-STATIC-IP-BLOG.md).
|
||||
|
||||
---
|
||||
|
||||
## Jak działa skrypt (v2)
|
||||
|
||||
1. **Skan** `eno1` / `enp3s0` — stan, link (TAK/NIE), obecny IPv4
|
||||
2. **Menu CLI** — tylko opcje pasujące do wykrytych kabli
|
||||
3. **Netplan** — globalne `nameservers`, `optional` / `networkd.ConfigureWithoutCarrier`, brama z `metric: 100`
|
||||
4. **Weryfikacja** — oczekiwane IP, `networkctl reconfigure`, `ping -I <gw_nic> 192.168.100.1`
|
||||
|
||||
### Profile (menu / `--profile`)
|
||||
|
||||
| Profil | eno1 | enp3s0 | Brama |
|
||||
|--------|------|--------|-------|
|
||||
| `dual-eno1` (dual, zalecane) | `.80` | `.90` | eno1 |
|
||||
| `dual-enp3s0` | `.90` | `.80` | enp3s0 |
|
||||
| `single-eno1-80` | `.80` | — | eno1 |
|
||||
| `single-eno1-both` | `.80` + `.90` | — | eno1 |
|
||||
| `single-enp3s0-90` | — | `.90` | enp3s0 |
|
||||
| `single-enp3s0-80` | — | `.80` | enp3s0 |
|
||||
| `single-enp3s0-both` | — | `.80` + `.90` | enp3s0 |
|
||||
|
||||
Menu pokazuje się tylko gdy **co najmniej jeden** interfejs ma link. Żaden link → błąd i `exit 1`.
|
||||
|
||||
---
|
||||
|
||||
## Uruchomienie
|
||||
|
||||
```bash
|
||||
cd /home/tomasz-syn-grzegorza/cursor/ubuntu-bare-metal/scripts
|
||||
|
||||
# Interaktywnie (skan + menu):
|
||||
sudo ./configure-static-dual-nic.sh
|
||||
|
||||
# Podgląd YAML (domyślna opcja 1 z menu po skanie):
|
||||
sudo ./configure-static-dual-nic.sh --dry-run
|
||||
|
||||
# Bez menu (agent / automatyzacja):
|
||||
sudo ./configure-static-dual-nic.sh --non-interactive --profile dual-eno1
|
||||
```
|
||||
|
||||
Rollback (pomija zepsute backupy ze statycznej konfiguracji):
|
||||
|
||||
```bash
|
||||
sudo ./restore-dhcp-network.sh
|
||||
# lub wymuś czysty DHCP:
|
||||
sudo ./restore-dhcp-network.sh --force-dhcp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pre-flight
|
||||
|
||||
1. Zarezerwuj `.80` i `.90` na routerze (DHCP)
|
||||
2. Druga sesja SSH lub konsola fizyczna
|
||||
3. Skrypt tworzy backup: `/etc/netplan/00-installer-config.yaml.bak.<timestamp>`
|
||||
|
||||
**Nie zakładaj** sztywnie kabla w `eno1` — skrypt wykrywa link i proponuje właściwy profil.
|
||||
|
||||
---
|
||||
|
||||
## Weryfikacja po apply
|
||||
|
||||
```bash
|
||||
ip -br addr show eno1 enp3s0
|
||||
ip route show default
|
||||
ping -c 2 -I eno1 192.168.100.1 # lub -I enp3s0, zależnie od profilu
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://192.168.100.90:8091
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Działanie |
|
||||
|---------|-----------|
|
||||
| Brak IPv4 na eno1 mimo kabla (stary bug) | v2: `ConfigureWithoutCarrier` + menu; spróbuj `dual-eno1` lub `single-enp3s0-90` jeśli tylko enp3s0 |
|
||||
| Brak internetu | `ip route`; brama musi być na interfejsie **z kablem** |
|
||||
| SSH urwane | `ip a` z konsoli; rollback `restore-dhcp-network.sh` |
|
||||
| Konflikt IP | Zwolnij `.80`/`.90` na routerze |
|
||||
|
||||
---
|
||||
|
||||
## Follow-up po wdrożeniu
|
||||
|
||||
Pliki z `192.168.100.5` → **`.90`** (gdy usługi na enp3s0):
|
||||
|
||||
- `coding-agent/DOCKER-UI-*.md`, `SWARMUI-*.md`, `PORTAINER-SETUP-TOKEN.md`, `BACKLOG.md`
|
||||
- `stacks/npmplus/README.md`, `manual-tutorial/06-gpu-fan-control.md`
|
||||
|
||||
Gdy wybrano `single-enp3s0-both` lub `single-enp3s0-90` — usługi pod `.90` na enp3s0.
|
||||
|
||||
---
|
||||
|
||||
## Backlog
|
||||
|
||||
- [ ] Wypełnić `NETWORK-STATIC-IP-DEPLOYMENT.md` z wybranym profilem
|
||||
- [ ] Zaktualizować IP w handoffach
|
||||
@@ -0,0 +1,40 @@
|
||||
# Usunięcie Portainer CE
|
||||
|
||||
Data: 2026-07-04
|
||||
Host: `gmktec-k11`
|
||||
|
||||
## Powód
|
||||
|
||||
Portainer 2.43+ wymaga setup tokena i dokończenia konfiguracji w ~5 minut. Na headless serwerze (SSH-only) powodowało to komunikat timeout i blokadę UI.
|
||||
|
||||
## Co usunięto
|
||||
|
||||
| Element | Status |
|
||||
|---------|--------|
|
||||
| Kontener `portainer` | usunięty |
|
||||
| Obraz `portainer/portainer-ce:latest` | usunięty |
|
||||
| Stack w Dockerze | `docker compose --profile portainer down` |
|
||||
|
||||
## Co pozostało na dysku (nie ruszane)
|
||||
|
||||
```
|
||||
/data/apps/portainer/
|
||||
├── data/ # portainer.db, konfiguracja UI
|
||||
└── admin-password # plik hasła (jeśli istniał)
|
||||
```
|
||||
|
||||
Katalog `stacks/portainer/` w repo — usunięty; zastąpiony przez [`stacks/server-ui/`](../stacks/server-ui/).
|
||||
|
||||
## Zamiennik
|
||||
|
||||
**Server UI** — port **8091**, [`DOCKER-UI-DEPLOYMENT.md`](DOCKER-UI-DEPLOYMENT.md)
|
||||
|
||||
- Własny panel FastAPI (whitelist stacków)
|
||||
- Polityka GPU wbudowana w API
|
||||
- Start/Stop: nagłówek `X-API-Key`
|
||||
|
||||
## Powiązane
|
||||
|
||||
- [`PORTAINER-SETUP-TOKEN.md`](PORTAINER-SETUP-TOKEN.md) — archiwum procedur Portainer
|
||||
- [`stacks/server-ui/README.md`](../stacks/server-ui/README.md)
|
||||
- [`DOCKGE-REMOVAL.md`](DOCKGE-REMOVAL.md)
|
||||
@@ -0,0 +1,225 @@
|
||||
# Portainer — setup token (archiwum)
|
||||
|
||||
> **Portainer usunięty 2026-07-04.** Aktualny Docker UI: [Server UI](../stacks/server-ui/README.md) na porcie **8091**.
|
||||
|
||||
Od Portainer **2.43** (w tym `portainer-ce:latest`) przy **pierwszej** konfiguracji panelu potrzebny jest **setup token** — jednorazowy kod z logów kontenera. Chroni to świeżą instalację przed przejęciem przez kogoś innego w sieci LAN.
|
||||
|
||||
**To nie jest** token API z menu użytkownika (ten tworzysz później, po zalogowaniu).
|
||||
|
||||
---
|
||||
|
||||
## Czy mnie to dotyczy?
|
||||
|
||||
| Sytuacja | Co robić |
|
||||
|----------|----------|
|
||||
| Logujesz się już **loginem i hasłem** | Setup token **nie** jest potrzebny |
|
||||
| W przeglądarce widzisz pole **Setup token** | Potrzebujesz kodu z logów (poniżej) |
|
||||
| Plik `/data/apps/portainer/data/portainer.db` **istnieje** | Instancja została już zainicjowana — zwykle logujesz się hasłem, nie tokenem |
|
||||
|
||||
Sprawdzenie na serwerze (SSH):
|
||||
|
||||
```bash
|
||||
ls -la /data/apps/portainer/data/portainer.db
|
||||
```
|
||||
|
||||
Jeśli plik jest — konto admina mogło zostać utworzone. Jeśli nie pamiętasz hasła, patrz sekcja [Reset od zera](#reset-od-zera-ostrożnie).
|
||||
|
||||
---
|
||||
|
||||
## Jak znaleźć setup token (krok po kroku)
|
||||
|
||||
### 1. Upewnij się, że kontener działa
|
||||
|
||||
```bash
|
||||
docker ps --filter name=portainer
|
||||
```
|
||||
|
||||
Powinien być **Up**.
|
||||
|
||||
### 2. Odczytaj token z logów
|
||||
|
||||
Na maszynie z Dockerem (ten sam host co Portainer):
|
||||
|
||||
```bash
|
||||
docker logs portainer 2>&1 | grep setup_token
|
||||
```
|
||||
|
||||
Szukaj linii w stylu:
|
||||
|
||||
```text
|
||||
setup_token=AbCdEf123456...
|
||||
```
|
||||
|
||||
**Kopiujesz tylko część po znaku `=`** (bez `setup_token=`).
|
||||
|
||||
Jeśli `grep` nic nie zwraca:
|
||||
|
||||
```bash
|
||||
docker logs portainer 2>&1 | less
|
||||
```
|
||||
|
||||
W `less` wciśnij `/` i wpisz `setup_token`, Enter.
|
||||
|
||||
### 3. Wpisz token w przeglądarce
|
||||
|
||||
1. Otwórz **http://192.168.100.90:9000**
|
||||
2. Wklej **Setup token**
|
||||
3. Ustaw **hasło administratora** (minimum **12 znaków**)
|
||||
4. Zatwierdź i wybierz środowisko **Local** (Docker na tym komputerze)
|
||||
|
||||
### 4. Ważne ograniczenia czasowe
|
||||
|
||||
- Token jest **jednorazowy** — po udanym setupie nie musisz go zapisywać.
|
||||
- Masz około **5 minut** od startu kontenera na dokończenie konfiguracji. Po tym czasie token może wygasnąć.
|
||||
- Jeśli minęło za dużo czasu: zrestartuj kontener i **od razu** odczytaj nowy token:
|
||||
|
||||
```bash
|
||||
cd /home/tomasz-syn-grzegorza/cursor/ubuntu-bare-metal/stacks/portainer
|
||||
docker compose --profile portainer restart portainer
|
||||
sleep 3
|
||||
docker logs portainer 2>&1 | grep setup_token
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Gdy token „nie działa”
|
||||
|
||||
| Problem | Rozwiązanie |
|
||||
|---------|-------------|
|
||||
| „Invalid setup token” | Ponownie `docker logs portainer \| grep setup_token` — literówka, stary token |
|
||||
| Minęło > 5 min | Restart kontenera (jak wyżej), nowy token z logów |
|
||||
| Panel mówi, że admin już istnieje | **Nie** szukaj tokena — loguj się hasłem ustawionym wcześniej |
|
||||
| Brak linii `setup_token` w logach | Możliwe, że setup już zakończony (`portainer.db` istnieje) lub starsza wersja obrazu |
|
||||
|
||||
---
|
||||
|
||||
## Jak pominąć setup token (tylko zaufana sieć LAN)
|
||||
|
||||
Jeśli Portainer działa **wyłącznie** w domowej sieci (`192.168.100.x`) i **nie** jest wystawiony na internet, możesz wyłączyć wymóg tokena.
|
||||
|
||||
W [`stacks/portainer/docker-compose.yml`](../stacks/portainer/docker-compose.yml) dodaj pod serwisem `portainer`:
|
||||
|
||||
```yaml
|
||||
command: --no-setup-token
|
||||
```
|
||||
|
||||
Potem:
|
||||
|
||||
```bash
|
||||
cd stacks/portainer
|
||||
docker compose --profile portainer up -d
|
||||
```
|
||||
|
||||
**Uwagi:**
|
||||
|
||||
- Działa sensownie przy **nowej** instalacji (brak `portainer.db`). Przy istniejącej bazie ta flaga **nie zmienia** hasła ani konta.
|
||||
- **Nie używaj** `--no-setup-token`, jeśli port 9000 jest dostępny z internetu.
|
||||
- To świadome osłabienie zabezpieczenia przy pierwszym starcie — akceptowalne na izolowanym LAN.
|
||||
|
||||
---
|
||||
|
||||
## Własny token (zamiast losowego z logów)
|
||||
|
||||
Przydatne przy skryptach — **nie commituj** tej wartości do git.
|
||||
|
||||
```yaml
|
||||
command: --setup-token TwojTajnyKodNaSetup
|
||||
```
|
||||
|
||||
Ten sam ciąg wpisujesz w polu **Setup token** w przeglądarce.
|
||||
|
||||
---
|
||||
|
||||
## Hasło admina z pliku (bez tokena)
|
||||
|
||||
Rekomendowane przez Portainer przy instalacjach, gdzie nie masz wygodnego dostępu do logów.
|
||||
|
||||
### 1. Plik z hasłem (poza repozytorium)
|
||||
|
||||
```bash
|
||||
echo -n 'TwojeSilneHasloMin12Znakow' > /data/apps/portainer/admin-password
|
||||
chmod 600 /data/apps/portainer/admin-password
|
||||
```
|
||||
|
||||
Plik musi zawierać **samą treść hasła** — bez pustej linii na końcu (`echo -n`).
|
||||
|
||||
### 2. Zmiany w `docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
command: --admin-password-file /run/portainer/admin-password
|
||||
volumes:
|
||||
# ... istniejące wolumeny ...
|
||||
- ${DATA_ROOT:-/data}/apps/portainer/admin-password:/run/portainer/admin-password:ro
|
||||
```
|
||||
|
||||
### 3. Pierwszy start
|
||||
|
||||
```bash
|
||||
cd stacks/portainer
|
||||
docker compose --profile portainer up -d
|
||||
```
|
||||
|
||||
Portainer tworzy konto **admin** z tym hasłem. **Setup token nie jest potrzebny.**
|
||||
|
||||
**Uwaga:** przy **istniejącym** `portainer.db` ta flaga **nie nadpisuje** hasła.
|
||||
|
||||
---
|
||||
|
||||
## Setup token vs Access token (API)
|
||||
|
||||
| | Setup token | Access token |
|
||||
|---|-------------|--------------|
|
||||
| **Kiedy** | Pierwsza konfiguracja panelu | Po zalogowaniu do UI |
|
||||
| **Gdzie go wziąć** | `docker logs portainer` | UI → ikona użytkownika → **My account** → **Access tokens** |
|
||||
| **W pliku `.env` w repo** | Nie | Nie |
|
||||
| **Do czego** | Jednorazowe utworzenie admina | Wywołania API Portainera (skrypty) |
|
||||
|
||||
W [`stacks/portainer/.env`](../stacks/portainer/.env) **nie ma** żadnego tokena — to normalne.
|
||||
|
||||
---
|
||||
|
||||
## Reset od zera (ostrożnie)
|
||||
|
||||
Gdy utknąłeś: nie znasz hasła, token wygasł, a setup się nie udał.
|
||||
|
||||
```bash
|
||||
cd /home/tomasz-syn-grzegorza/cursor/ubuntu-bare-metal/stacks/portainer
|
||||
docker compose --profile portainer down
|
||||
mv /data/apps/portainer/data /data/apps/portainer/data.bak.$(date +%Y%m%d)
|
||||
./scripts/start.sh
|
||||
docker logs portainer 2>&1 | grep setup_token
|
||||
```
|
||||
|
||||
**Stracisz:** zapisane ustawienia Portainer w UI (endpointy, preferencje). Kontenery Docker (`localai`, `npmplus`, …) **nie są** usuwane — tylko konfiguracja samego Portainera.
|
||||
|
||||
Przy reinstalacji możesz od razu dodać `command: --no-setup-token` lub `--admin-password-file` (sekcje wyżej).
|
||||
|
||||
---
|
||||
|
||||
## Rekomendacja dla RTX1 (tylko LAN, headless)
|
||||
|
||||
| Scenariusz | Najprostsza droga |
|
||||
|------------|-------------------|
|
||||
| **Domyślnie w repo** | `--admin-password-file` + plik na `/data` — **bez tokena, bez limitu 5 min** |
|
||||
| Masz SSH, setup token | `docker logs portainer \| grep setup_token` → przeglądarka (w ciągu 5 min) |
|
||||
| Chcesz bez tokena na LAN | `--no-setup-token` przy **czystej** instalacji (nadal ~5 min na hasło w UI) |
|
||||
|
||||
Stack w repo: [`stacks/portainer/docker-compose.yml`](../stacks/portainer/docker-compose.yml) używa **`--admin-password-file`**.
|
||||
|
||||
---
|
||||
|
||||
## Stan na hoście (2026-07-04)
|
||||
|
||||
- Naprawiono timeout pierwszej konfiguracji przez `--admin-password-file` w compose.
|
||||
- Hasło: `/data/apps/portainer/admin-password` (utworzone przy wdrożeniu, **nie** w git).
|
||||
- Logowanie: użytkownik **admin**, hasło z pliku.
|
||||
- `portainer.db` istnieje — konto admin utworzone przy starcie z pliku hasła.
|
||||
|
||||
---
|
||||
|
||||
## Powiązane dokumenty
|
||||
|
||||
- [`DOCKER-UI-DEPLOYMENT.md`](DOCKER-UI-DEPLOYMENT.md)
|
||||
- [`DOCKER-UI-HANDOFF.md`](DOCKER-UI-HANDOFF.md)
|
||||
- [`stacks/portainer/README.md`](../stacks/portainer/README.md)
|
||||
- Oficjalna dokumentacja: [How do I find, skip, or customize my setup token?](https://docs.portainer.io/) (Portainer 2.43+)
|
||||
@@ -0,0 +1,28 @@
|
||||
# coding-agent — notatki dla agenta kodującego
|
||||
|
||||
Katalog handoff dla kolejnych sesji Cursor pracujących nad [`ubuntu-bare-metal`](../).
|
||||
|
||||
## Kolejność czytania
|
||||
|
||||
1. **[HANDOFF.md](HANDOFF.md)** — podsumowanie sesji, decyzje, stan runtime
|
||||
2. **[STATE.md](STATE.md)** — co działa teraz, ścieżki na dysku, kluczowe pliki repo
|
||||
3. **[CONTROL-PLANE-ENV.md](CONTROL-PLANE-ENV.md)** — jeden `.env` dla gpu-fan + Server UI
|
||||
4. **[BACKLOG.md](BACKLOG.md)** — priorytetyzowane zadania (P0–P3)
|
||||
5. **[CONVENTIONS.md](CONVENTIONS.md)** — wzorce stacków, sekrety, konwencje
|
||||
6. **[GITEA-PREP.md](GITEA-PREP.md)** — git/Gitea: pierwszy commit, push, checklist sekretów
|
||||
|
||||
## Gitea
|
||||
|
||||
Repozytorium zainicjowane lokalnie (`main`). Remote i push — według [GITEA-PREP.md](GITEA-PREP.md) (URL od użytkownika).
|
||||
|
||||
## Zasady
|
||||
|
||||
- Instrukcje dla użytkownika: **po polsku**. Komendy: **po angielsku**.
|
||||
- **Nie commituj** `.env`, tokenów API ani haseł — tylko `.env.example` bez sekretów.
|
||||
- W notatkach w tym katalogu **nie zapisuj** wartości `LOCALAI_API_KEY`, `HF_TOKEN` itd.
|
||||
- Commity i push **tylko na prośbę** użytkownika.
|
||||
- Nie edytuj plików planu w `.cursor/plans/`.
|
||||
|
||||
## Ostatnia aktualizacja
|
||||
|
||||
Sesja: pierwszy import git pod Gitea; Server UI — GPU Fan chart (siatka, linijki, responsywność, wyśrodkowanie). Szczegóły: [GITEA-PREP.md](GITEA-PREP.md).
|
||||
@@ -0,0 +1,102 @@
|
||||
# Wdrożenie BGE-Reranker-v2-m3 w LocalAI
|
||||
|
||||
Data: 2026-07-01
|
||||
Host: `gmktec-k11` (RTX 3090 Ti, LocalAI `v4.4.3-gpu-nvidia-cuda-13`, port **8070**)
|
||||
|
||||
## Podsumowanie
|
||||
|
||||
| Element | Wartość |
|
||||
|---------|---------|
|
||||
| Model GGUF | `bge-reranker-v2-m3-FP16.gguf` (~1.1 GB) |
|
||||
| Źródło | [gpustack/bge-reranker-v2-m3-GGUF](https://huggingface.co/gpustack/bge-reranker-v2-m3-GGUF/resolve/main/bge-reranker-v2-m3-FP16.gguf) |
|
||||
| Backend | **`llama-cpp`** → automatycznie `cuda13-llama-cpp` na obrazie cuda-13 |
|
||||
| API id | `bge-reranker-v2-m3-FP16.gguf` |
|
||||
| Endpoint | `POST /v1/rerank` (format Jina/Cohere-compatible) |
|
||||
| Status | **OK** — HTTP 200, dokument o pandzie na indeksie 2 ma najwyższy `relevance_score` |
|
||||
|
||||
## Problem z importem z galerii (naprawiony)
|
||||
|
||||
Wcześniejsza próba importu przez UI LocalAI utworzyła błędny YAML:
|
||||
|
||||
```yaml
|
||||
backend: rerankers # źle dla GGUF
|
||||
parameters:
|
||||
model: gpustack/bge-reranker-v2-m3-GGUF/resolve/main/bge-reranker-v2-m3-FP16.gguf # URL zamiast pliku lokalnego
|
||||
```
|
||||
|
||||
Backend `rerankers` służy modelom HuggingFace (transformers), **nie** plikom GGUF. Dla GGUF wymagany jest `llama-cpp` z:
|
||||
|
||||
- `reranking: true`
|
||||
- `known_usecases: [rerank]`
|
||||
- `parameters.model` = lokalna nazwa pliku `.gguf`
|
||||
|
||||
Dodatkowy backend **nie był potrzebny** — `cuda13-llama-cpp` był już zainstalowany (ten sam co Gemma chat i BGE-M3 embed).
|
||||
|
||||
## Co zrobiono
|
||||
|
||||
1. Pobrano GGUF do `/data/apps/localai/models/bge-reranker-v2-m3-FP16.gguf`
|
||||
2. Zastosowano poprawny YAML (szablon w repo)
|
||||
3. Dodano skrypt `stacks/localai/scripts/download-reranker.sh`
|
||||
4. Restart kontenera `localai`
|
||||
5. Smoke test `/v1/rerank` — **200 OK**
|
||||
|
||||
## Pliki w repo
|
||||
|
||||
| Plik | Rola |
|
||||
|------|------|
|
||||
| [`stacks/localai/profiles/bge-reranker-v2-m3-FP16-rerank.yaml.example`](../stacks/localai/profiles/bge-reranker-v2-m3-FP16-rerank.yaml.example) | Szablon YAML |
|
||||
| [`stacks/localai/scripts/download-reranker.sh`](../stacks/localai/scripts/download-reranker.sh) | Pobieranie GGUF + aplikacja profilu |
|
||||
|
||||
## Pliki na hoście (runtime)
|
||||
|
||||
```
|
||||
/data/apps/localai/models/
|
||||
├── bge-reranker-v2-m3-FP16.gguf # ~1.1 GB
|
||||
└── bge-reranker-v2-m3-FP16.gguf.yaml # backend llama-cpp, reranking: true
|
||||
```
|
||||
|
||||
## Weryfikacja
|
||||
|
||||
```bash
|
||||
API_KEY=$(grep '^LOCALAI_API_KEY=' stacks/localai/.env | cut -d= -f2)
|
||||
|
||||
curl -s http://127.0.0.1:8070/v1/models \
|
||||
-H "Authorization: Bearer $API_KEY"
|
||||
# powinien zawierać bge-reranker-v2-m3-FP16.gguf
|
||||
|
||||
curl -s http://127.0.0.1:8070/v1/rerank \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "bge-reranker-v2-m3-FP16.gguf",
|
||||
"query": "What is a panda?",
|
||||
"top_n": 3,
|
||||
"documents": [
|
||||
"hi",
|
||||
"it is a bear",
|
||||
"The giant panda (Ailuropoda melanoleuca) is a bear species endemic to China."
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
Oczekiwany wynik: `results[0].index == 2` (dokument o pandzie), `relevance_score` najwyższy dla tego indeksu.
|
||||
|
||||
## VRAM i SINGLE_ACTIVE_BACKEND
|
||||
|
||||
Compose ma `SINGLE_ACTIVE_BACKEND=true` — przy przełączaniu chat → embed → rerank model jest przeładowywany (jak przy embed). Reranker (~1.1 GB) + Gemma (~6.5 GB) **nie mieszczą się naraz** w sensie aktywnego backendu; kolejne wywołania API przełączają model.
|
||||
|
||||
Dla pipeline RAG (embed → rerank → chat) klient musi liczyć się z latencją przeładowania (~kilka sekund między typami modeli).
|
||||
|
||||
## Integracja z klientem (ai-lawyer / dev)
|
||||
|
||||
| Zmienna | Wartość |
|
||||
|---------|---------|
|
||||
| `RERANK_MODEL` | `bge-reranker-v2-m3-FP16.gguf` |
|
||||
| `RERANK_URL` | `https://llm.rtx1.mobile.agency-ai.dev/v1/rerank` (lub LAN `http://192.168.100.5:8070/v1/rerank`) |
|
||||
| Auth | `Authorization: Bearer <LOCALAI_API_KEY>` |
|
||||
|
||||
Payload zgodny z OpenAI/Jina rerank API — pole `documents` to tablica stringów, `query` to zapytanie, `top_n` opcjonalne.
|
||||
|
||||
## Uwaga: uprawnienia plików
|
||||
|
||||
Pliki YAML utworzone przez kontener/UI mogą być owned by `root`. Skrypt `download-reranker.sh` przy błędzie `cp` zapisuje YAML przez `docker exec localai` (bez sudo na hoście).
|
||||
@@ -0,0 +1,62 @@
|
||||
# Handoff — BGE-Reranker w LocalAI
|
||||
|
||||
Dla agenta kodującego. Kontekst wdrożenia: [`RERANKER-DEPLOYMENT.md`](RERANKER-DEPLOYMENT.md).
|
||||
|
||||
## Stan: ukończone na hoście
|
||||
|
||||
- [x] GGUF pobrany
|
||||
- [x] YAML `llama-cpp` + `reranking: true`
|
||||
- [x] Model widoczny w `/v1/models`
|
||||
- [x] `/v1/rerank` zwraca HTTP 200
|
||||
|
||||
## Zadania opcjonalne (backlog)
|
||||
|
||||
- [ ] Dodać `RERANK_MODEL` do dokumentacji klienta / `.env.example` dev hosta
|
||||
- [ ] Smoke test rerank przez publiczną domenę NPMPlus (z zewnątrz LAN — hairpin NAT na RTX1)
|
||||
- [ ] Rozważyć Q4_K_M zamiast FP16 jeśli VRAM/latencja przełączania modeli jest problemem
|
||||
- [ ] Dodać rozdział w `manual-tutorial/05-localai-stack.md` o modelach embed + rerank
|
||||
|
||||
## Powtórzenie instalacji (nowy host)
|
||||
|
||||
```bash
|
||||
cd stacks/localai
|
||||
cp .env.example .env # ustaw LOCALAI_API_KEY, LOCALAI_PORT=8070
|
||||
./scripts/download-reranker.sh
|
||||
docker compose --profile localai restart localai
|
||||
# czekaj na readyz, potem curl /v1/rerank (patrz RERANKER-DEPLOYMENT.md)
|
||||
```
|
||||
|
||||
## Czego nie robić
|
||||
|
||||
- Nie używać `backend: rerankers` dla GGUF
|
||||
- Nie instalować osobnego backendu — `cuda13-llama-cpp` wystarczy
|
||||
- Nie commitować `LOCALAI_API_KEY`
|
||||
- Nie zmieniać `SINGLE_ACTIVE_BACKEND` bez testu VRAM (chat + embed + rerank sekwencyjnie)
|
||||
|
||||
## Kluczowe pliki do edycji w przyszłości
|
||||
|
||||
| Plik | Kiedy |
|
||||
|------|-------|
|
||||
| `stacks/localai/profiles/bge-reranker-v2-m3-FP16-rerank.yaml.example` | zmiana quant / parametrów rerankera |
|
||||
| `stacks/localai/scripts/download-reranker.sh` | nowy URL lub wariant Q4 |
|
||||
| `stacks/localai/coding-agent/STATE.md` | po każdej zmianie runtime |
|
||||
|
||||
## API — minimalny przykład (Python)
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
resp = requests.post(
|
||||
"http://127.0.0.1:8070/v1/rerank",
|
||||
headers={"Authorization": f"Bearer {api_key}"},
|
||||
json={
|
||||
"model": "bge-reranker-v2-m3-FP16.gguf",
|
||||
"query": user_query,
|
||||
"documents": chunk_texts,
|
||||
"top_n": 5,
|
||||
},
|
||||
timeout=120,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
ranked = resp.json()["results"] # sorted by relevance_score desc
|
||||
```
|
||||
@@ -0,0 +1,451 @@
|
||||
# Server UI — architektura uniwersalnego panelu (research DevOps)
|
||||
|
||||
**Data:** 2026-07-04
|
||||
**Host:** gmktec-k11, Ubuntu 26.04, RTX 3090 Ti
|
||||
**Kontekst:** Czy UI gpu-fan można przenieść do Server UI w Dockerze, skoro daemon NVML musi zostać na hoście (root)? Jak zbudować panel w stylu appliance (Proxmox / TrueNAS) na Ubuntu?
|
||||
|
||||
**Powiązane:**
|
||||
- [`stacks/server-ui/`](../stacks/server-ui/)
|
||||
- [`stacks/gpu-fan/coding-agent/DOCKER-VS-HOST-REPORT.md`](../stacks/gpu-fan/coding-agent/DOCKER-VS-HOST-REPORT.md)
|
||||
- [`DOCKER-UI-DEPLOYMENT.md`](DOCKER-UI-DEPLOYMENT.md)
|
||||
- [`ADR-001-host-agent-control-plane.md`](ADR-001-host-agent-control-plane.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive summary
|
||||
|
||||
**Werdykt:** Przeniesienie **UI** gpu-fan do Server UI przy **daemonie NVML na hoście** to **właściwy, branżowo spójny kierunek** (wzorzec *control plane UI + host agent*). **Nie** pakować sterowania wentylatorami do kontenera Docker.
|
||||
|
||||
**Rekomendowana strategia:** **C** — rozdziel gpu-fan na agent localhost (`127.0.0.1:18090`) + zintegrowany panel w Server UI + dockerizacja Server UI z `docker.sock` i proxy do agenta.
|
||||
|
||||
**Stan dziś:** Oba panele (gpu-fan :8090, server-ui :8091) działają jako **systemd na hoście**, nie w Dockerze. Cel „wszystkie UI serwerowe w Dockerze” wymaga najpierw dockerizacji server-ui, potem integracji gpu-fan jako host agent.
|
||||
|
||||
---
|
||||
|
||||
## 2. Pytanie biznesowe
|
||||
|
||||
> gpu-fan musi działać na hoście (root, NVML). Czy UI gpu-fan może działać w kontenerze Docker razem z server-ui, żeby wszystkie UI serwerowe zarządzały rootem z jednego miejsca?
|
||||
|
||||
**Odpowiedź skrócona:**
|
||||
|
||||
| Warstwa | Gdzie | Dlaczego |
|
||||
|---------|-------|----------|
|
||||
| Pętla NVML + zapis wentylatorów | **Host, systemd, root** | Jedyny wspierany i bezpieczny model ([gpu-fan report](../stacks/gpu-fan/coding-agent/DOCKER-VS-HOST-REPORT.md)) |
|
||||
| API agenta (status, krzywa, tryb) | **Host, localhost only** | Most dla UI bez publicznego NVML |
|
||||
| Web UI (dashboard, wykres, przyciski) | **Server UI (docelowo Docker)** | Jedna przeglądarka, jeden port, jeden API key |
|
||||
| Start/stop stacków Docker | **Server UI + docker.sock** | Wzorzec Portainer/Dockge |
|
||||
|
||||
„UI w Dockerze zarządza rootem” = **proxy HTTP do host agenta**, nie uruchomienie NVML write w kontenerze.
|
||||
|
||||
---
|
||||
|
||||
## 3. Stan obecny (as-is)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
browser1["Przeglądarka"]
|
||||
browser2["Przeglądarka"]
|
||||
gpuFan["gpu-fan.service root :8090"]
|
||||
serverUI["server-ui systemd :8091"]
|
||||
dockerStacks["Docker stacks"]
|
||||
gpu["GPU NVML"]
|
||||
|
||||
browser1 --> gpuFan
|
||||
browser2 --> serverUI
|
||||
gpuFan --> gpu
|
||||
serverUI -->|"docker compose CLI"| dockerStacks
|
||||
serverUI -.->|"link :8090"| gpuFan
|
||||
```
|
||||
|
||||
| Komponent | Runtime | Port | Uprawnienia | Pliki kluczowe |
|
||||
|-----------|---------|------|-------------|----------------|
|
||||
| gpu-fan | systemd, `User=root` | 8090 | NVML write + FastAPI + static UI w **jednym** procesie | `stacks/gpu-fan/app.py`, `fan_controller.py` |
|
||||
| server-ui | systemd | 8091 | `docker compose` subprocess, `nvidia-smi` read-only | `stacks/server-ui/app.py`, `compose_runner.py` |
|
||||
| comfyui / localai / vllm | Docker | 8188 / 8070 / 8000 | GPU compute | `stacks/*/compose.yaml` |
|
||||
| npmplus | Docker, `network_mode: host` | 80, 443, 81 | reverse proxy | `stacks/npmplus/compose.yaml` |
|
||||
|
||||
### Problemy architektoniczne
|
||||
|
||||
1. **Dwa panele** — porty 8090 i 8091, osobne API key, rozproszony UX.
|
||||
2. **Coupling UI+daemon w gpu-fan** — `app.py` uruchamia wątek NVML i uvicorn w jednym procesie; static HTML woła `/api/*` na tym samym origin.
|
||||
3. **Server-ui nie jest w Dockerze** — mimo nazwy „Docker UI” to FastAPI na hoście (`/opt/server-ui`, `server-ui.service`).
|
||||
4. **Brak jednego entry pointu** — NPMPlus domyślnie nie proxyuje 8090/8091; użytkownik musi znać wiele URL.
|
||||
5. **„Zarządzanie rootem”** — server-ui nie może bezpośrednio pisać do NVML; potrzebuje host agent API.
|
||||
|
||||
### Wymuszenie root w gpu-fan
|
||||
|
||||
```python
|
||||
# stacks/gpu-fan/app.py (linie 161–164)
|
||||
if os.geteuid() != 0 and not DRY_RUN:
|
||||
log.error("GPU fan control requires root (NVML write access). Run with sudo.")
|
||||
sys.exit(1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Analiza coupling gpu-fan (UI + daemon)
|
||||
|
||||
### Obecna struktura procesu
|
||||
|
||||
```
|
||||
gpu-fan.service (root)
|
||||
└── python app.py
|
||||
├── FanController.run_loop() # wątek daemon — co 2s NVML
|
||||
└── uvicorn :8090
|
||||
├── GET / → static/index.html (~750 linii JS)
|
||||
├── GET /api/status
|
||||
├── GET/PUT /api/curve
|
||||
├── POST /api/mode
|
||||
└── POST /api/reload
|
||||
```
|
||||
|
||||
Frontend (`static/index.html`) używa **względnych** ścieżek (`fetch('/api/status')`) — działa tylko gdy UI i API są na tym samym hoście:porcie.
|
||||
|
||||
### Co można rozdzielić bez ryzyka
|
||||
|
||||
| Element | Zostaje na hoście | Może do Server UI |
|
||||
|---------|-------------------|-------------------|
|
||||
| `fan_controller.py` pętla NVML | **Tak** | Nie |
|
||||
| FastAPI endpoints `/api/*` | **Tak** (localhost) | Proxy z server-ui |
|
||||
| `static/index.html` | Nie | **Tak** |
|
||||
| Graceful shutdown → auto fan | **Tak** (SIGTERM w systemd) | Nie |
|
||||
|
||||
### Docelowy podział (agent)
|
||||
|
||||
```
|
||||
gpu-fan-daemon.service (root)
|
||||
└── python fan_daemon.py # lub app.py --api-only
|
||||
├── FanController.run_loop()
|
||||
└── uvicorn 127.0.0.1:18090
|
||||
└── /api/* only (bez static/)
|
||||
|
||||
server-ui (Docker lub systemd)
|
||||
└── static + proxy /api/gpu-fan/* → http://host.docker.internal:18090/api/*
|
||||
```
|
||||
|
||||
**Krytyczne:** Pętla NVML musi działać **niezależnie** od UI — pad panelu nie może zatrzymać chłodzenia.
|
||||
|
||||
---
|
||||
|
||||
## 5. Porównanie branżowe
|
||||
|
||||
### Proxmox VE (Debian appliance)
|
||||
|
||||
| Aspekt | Implementacja | Lekcja |
|
||||
|--------|---------------|--------|
|
||||
| Web UI | `pveproxy` na hoście | UI oddzielone od workloadów, ale **nie** w „zwykłym” kontenerze aplikacyjnym |
|
||||
| Operacje VM/storage | `pvedaemon`, QEMU, LVM na hoście | Privileged zawsze na host kernel |
|
||||
| Model | Single-node hypervisor | Jeden panel, wiele usług backendowych na hoście |
|
||||
|
||||
Proxmox **nie** pakuje hypervisora do Docker — control plane jest częścią OS.
|
||||
|
||||
### TrueNAS
|
||||
|
||||
| Wariant | UI | Storage / hardware |
|
||||
|---------|-----|------------------|
|
||||
| CORE | nginx + middleware na hoście | ZFS w kernelu |
|
||||
| SCALE | UI w middleware (K8s) | Dane nadal przy host storage stack |
|
||||
|
||||
Lekcja: nawet przy „nowoczesnym” SCALE ciężkie operacje zostają przy hoście.
|
||||
|
||||
### Cockpit (Ubuntu)
|
||||
|
||||
- `cockpit.socket` + moduły na hoście
|
||||
- Dostęp do `systemd`, sieci, storage przez D-Bus / polkit
|
||||
- Oficjalny wzorzec Canonical dla headless Ubuntu
|
||||
|
||||
Alternatywa: zamiast własnego server-ui — Cockpit + plugin. Minus: brak whitelist stacków AI, polityki GPU, custom NVML fan.
|
||||
|
||||
### Portainer / Dockge (homelab Docker)
|
||||
|
||||
| Element | Wzorzec |
|
||||
|---------|---------|
|
||||
| UI | Kontener |
|
||||
| Docker API | Mount `/var/run/docker.sock` |
|
||||
| Host hardware (fan, disk, systemd) | **Poza scope** — nie sterują NVML |
|
||||
|
||||
To jest **najbliższy analog** do waszej wizji dla warstwy Docker; gpu-fan wymaga **dodatkowego** host agent (jak brakujący moduł Portainera).
|
||||
|
||||
### Macierz porównawcza
|
||||
|
||||
| Produkt | UI w kontenerze? | Host privileged ops | Pasuje do gmktec-k11? |
|
||||
|---------|------------------|----------------------|------------------------|
|
||||
| Proxmox | Nie (host) | Tak | Wzorzec appliance, za ciężki |
|
||||
| TrueNAS | Częściowo | Tak | Storage-first, nie AI |
|
||||
| Cockpit | Nie | Tak | Generyczny, słaba integracja AI stacks |
|
||||
| Portainer | Tak | Tylko docker.sock | Brak gpu-fan |
|
||||
| **Server UI (propozycja)** | **Tak (docelowo)** | **Via host agent** | **Dopasowany** |
|
||||
|
||||
---
|
||||
|
||||
## 6. Macierz strategii (A–E)
|
||||
|
||||
### A — Status quo (dwa panele, host systemd)
|
||||
|
||||
- **Plus:** Działa dziś, zero refactoru
|
||||
- **Minus:** 8090 + 8091, dwa klucze, rozproszony UX
|
||||
- **Verdict:** Utrzymanie krótkoterminowe
|
||||
|
||||
### B — Split gpu-fan + UI w server-ui, oba systemd na hoście
|
||||
|
||||
- **Plus:** Jeden port 8091, mniejszy refactor niż Docker
|
||||
- **Minus:** Server-ui nadal nie w kontenerze
|
||||
- **Verdict:** **Dobry krok pośredni** (Faza 1–2 bez Fazy 3)
|
||||
|
||||
### C — Split gpu-fan daemon + server-ui w Dockerze (REKOMENDOWANE)
|
||||
|
||||
- **Plus:** Portainer pattern; jeden panel; agent tylko localhost; :8090 znika z LAN
|
||||
- **Minus:** Dockerfile, mounty, `host.docker.internal` na Linux
|
||||
- **Verdict:** **Strategia docelowa**
|
||||
|
||||
### D — Cockpit zamiast server-ui
|
||||
|
||||
- **Plus:** Dojrzały ekosystem Ubuntu
|
||||
- **Minus:** Custom plugin NVML, brak polityki GPU / whitelist compose
|
||||
- **Verdict:** Tylko przy rezygnacji z własnego UI
|
||||
|
||||
### E — gpu-fan NVML w privileged kontenerze
|
||||
|
||||
- **Plus:** Literalnie „wszystko w Dockerze”
|
||||
- **Minus:** Kruche, `Insufficient Permissions` na GeForce, ryzyko manual fan po crash
|
||||
- **Verdict:** **Odrzuć** — patrz [`gpu-fan/docs/05-DLACZEGO-NIE-DOCKER.md`](../stacks/gpu-fan/docs/05-DLACZEGO-NIE-DOCKER.md)
|
||||
|
||||
**Wybór:** **C**, z opcjonalnym **B** jako pierwsze PR (integracja UI przed dockerizacją).
|
||||
|
||||
---
|
||||
|
||||
## 7. Architektura docelowa (to-be)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
browser["Przeglądarka"]
|
||||
npm["NPMPlus :443 opcjonalnie"]
|
||||
subgraph dockerPlane [Docker control plane]
|
||||
serverUI["server-ui container :8091"]
|
||||
end
|
||||
subgraph hostAgents [Host systemd root]
|
||||
gpuAgent["gpu-fan-daemon 127.0.0.1:18090"]
|
||||
futureAgent["host-agent przyszłość"]
|
||||
end
|
||||
subgraph workloads [Docker workloads]
|
||||
comfyui["comfyui"]
|
||||
localai["localai"]
|
||||
npmplus["npmplus host network"]
|
||||
end
|
||||
gpu["RTX 3090 Ti"]
|
||||
|
||||
browser --> npm
|
||||
npm --> serverUI
|
||||
browser --> serverUI
|
||||
serverUI -->|"docker.sock"| workloads
|
||||
serverUI -->|"proxy /api/gpu-fan"| gpuAgent
|
||||
serverUI -.->|"future /api/host"| futureAgent
|
||||
gpuAgent --> gpu
|
||||
```
|
||||
|
||||
### Warstwy odpowiedzialności
|
||||
|
||||
| Warstwa | Odpowiedzialność | Technologia |
|
||||
|---------|------------------|-------------|
|
||||
| **Presentation** | Jeden dashboard, auth, nawigacja | server-ui |
|
||||
| **Orchestration** | start/stop compose, logi, polityka GPU | server-ui + `docker.sock` |
|
||||
| **Host agents** | NVML fan; przyszłe: systemd, `/data` health | systemd, API localhost |
|
||||
| **Workloads** | AI inference / image gen | istniejące stacki compose |
|
||||
|
||||
### Co NIE wchodzi do Server UI (scope)
|
||||
|
||||
- Pełne osadzenie ComfyUI / LocalAI SPA w iframe — opcjonalne; dziś wystarczą linki `:8188`, `:8070`
|
||||
- Zastąpienie NPMPlus — osobny stack reverse proxy
|
||||
- Pełny hypervisor (VM, ZFS) — poza zakresem ubuntu-bare-metal
|
||||
|
||||
---
|
||||
|
||||
## 8. Plan implementacji (fazy)
|
||||
|
||||
### Faza 1 — Rozdziel gpu-fan (host)
|
||||
|
||||
**Cel:** Agent API bez publicznego UI.
|
||||
|
||||
| Zadanie | Pliki |
|
||||
|---------|-------|
|
||||
| Wydziel tryb API-only lub `fan_daemon.py` | `stacks/gpu-fan/app.py` lub nowy moduł |
|
||||
| Env: `GPU_FAN_API_HOST=127.0.0.1`, `GPU_FAN_API_PORT=18090` | `.env.example`, `/opt/control-plane/.env` |
|
||||
| Usuń serwowanie `static/` z produkcji | `app.py` |
|
||||
| Zaktualizuj `gpu-fan.service` | `gpu-fan.service` |
|
||||
| Deprecate publiczny :8090 w `enable-lan.sh` | `scripts/enable-lan.sh` |
|
||||
| Dokumentacja | `docs/`, `README.md` |
|
||||
|
||||
**Weryfikacja:**
|
||||
```bash
|
||||
curl -s http://127.0.0.1:18090/api/status -H "X-API-Key: $KEY"
|
||||
ss -tlnp | grep 18090 # 127.0.0.1 only
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://192.168.100.90:8090/ # connection refused
|
||||
```
|
||||
|
||||
### Faza 2 — Integracja UI w server-ui
|
||||
|
||||
| Zadanie | Pliki |
|
||||
|---------|-------|
|
||||
| Przenieś UI gpu-fan do zakładki / podstrony | `server-ui/static/gpu-fan.html` lub sekcja w `index.html` |
|
||||
| Zmień `fetch` na `/api/gpu-fan/...` | JS frontend |
|
||||
| Proxy backend | `server-ui/app.py` — `httpx` lub `requests` do agenta |
|
||||
| Jednolity `API_KEY` dla użytkownika | `.env` server-ui; agent akceptuje ten sam secret z localhost |
|
||||
| Usuń link `:8090` | `server-ui/static/index.html` |
|
||||
|
||||
**Weryfikacja:** Panel gpu-fan w `:8091`, brak potrzeby otwierania :8090.
|
||||
|
||||
### Faza 3 — Dockerize server-ui
|
||||
|
||||
| Zadanie | Pliki |
|
||||
|---------|-------|
|
||||
| `Dockerfile` | `stacks/server-ui/Dockerfile` |
|
||||
| `compose.yaml` | mount `docker.sock`, repo, env |
|
||||
| `extra_hosts: host.docker.internal:host-gateway` | compose |
|
||||
| Opcja: systemd wrapper `docker compose up -d` | `server-ui.service` lub nowy unit |
|
||||
| Whitelist w `stacks.yaml` — bez zmian | — |
|
||||
|
||||
**Weryfikacja:**
|
||||
```bash
|
||||
docker compose --profile server-ui up -d
|
||||
curl -s http://127.0.0.1:8091/api/health
|
||||
curl -s http://127.0.0.1:8091/api/stacks
|
||||
```
|
||||
|
||||
### Faza 4 — Jeden punkt wejścia (opcjonalnie)
|
||||
|
||||
- NPMPlus proxy host `panel.<domena>` → `127.0.0.1:8091`
|
||||
- UFW: zamknij 18090, opcjonalnie 8091 poza LAN
|
||||
- Patrz BACKLOG P1 — rozdział 08 firewall
|
||||
|
||||
### Faza 5 — `host-agent` (przyszłość)
|
||||
|
||||
Jeden daemon root agregujący:
|
||||
- gpu-fan (NVML)
|
||||
- `systemctl` status wybranych unitów (`gpu-fan`, `server-ui`)
|
||||
- health mountu `/data`
|
||||
- wersje driver / CUDA
|
||||
|
||||
Server-ui woła `/api/host/*` zamiast wielu portów agentów.
|
||||
|
||||
---
|
||||
|
||||
## 9. API contract — gpu-fan agent (do proxy)
|
||||
|
||||
Agent nasłuchuje na `127.0.0.1:18090`. Server-ui mapuje `/api/gpu-fan/{path}` → `/api/{path}`.
|
||||
|
||||
| Endpoint agenta | Metoda | Opis | Auth |
|
||||
|-----------------|--------|------|------|
|
||||
| `/api/status` | GET | Temperatura, fan speeds, tryb, krzywa | `X-API-Key` |
|
||||
| `/api/curve` | GET | Punkty krzywej | `X-API-Key` |
|
||||
| `/api/curve` | PUT | Zapis krzywej + tryb curve | `X-API-Key` |
|
||||
| `/api/mode` | POST | `{"mode":"auto\|curve\|manual","speed":100}` | `X-API-Key` |
|
||||
| `/api/reload` | POST | Przeładuj `curve.json` | `X-API-Key` |
|
||||
|
||||
**Bezpieczeństwo agenta:**
|
||||
- Bind tylko `127.0.0.1` (nie `0.0.0.0`)
|
||||
- Akceptuj requesty tylko z loopback lub z zaufanego `GPU_FAN_TRUSTED_PROXY` (server-ui IP w docker bridge — opcjonalnie)
|
||||
- Ten sam `API_KEY` co server-ui lub osobny `AGENT_KEY` w env server-ui (nie w repo)
|
||||
|
||||
**Przykład proxy (szkic Python):**
|
||||
```python
|
||||
# server-ui/app.py (szkic — nie zaimplementowano)
|
||||
GPU_FAN_AGENT = os.environ.get("GPU_FAN_AGENT_URL", "http://127.0.0.1:18090")
|
||||
|
||||
@app.api_route("/api/gpu-fan/{path:path}", methods=["GET", "PUT", "POST"])
|
||||
async def proxy_gpu_fan(path: str, request: Request):
|
||||
require_mutation_auth(request) # lub require_auth dla GET też
|
||||
url = f"{GPU_FAN_AGENT}/api/{path}"
|
||||
# forward method, body, X-API-Key header
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Szkic `compose.yaml` — server-ui (bez implementacji)
|
||||
|
||||
```yaml
|
||||
# stacks/server-ui/compose.yaml — PROPOZYCJA, nie wdrożono
|
||||
name: server-ui
|
||||
|
||||
services:
|
||||
server-ui:
|
||||
build: .
|
||||
container_name: server-ui
|
||||
profiles: [server-ui]
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${SERVER_UI_PORT:-8091}:8091"
|
||||
environment:
|
||||
- SERVER_UI_HOST=0.0.0.0
|
||||
- SERVER_UI_PORT=8091
|
||||
- API_KEY=${API_KEY}
|
||||
- REPO_ROOT=/repo
|
||||
- GPU_FAN_AGENT_URL=http://host.docker.internal:18090
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ${REPO_ROOT:-/home/tomasz-syn-grzegorza/cursor/ubuntu-bare-metal}:/repo:ro
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
```
|
||||
|
||||
**Uwagi:**
|
||||
- `docker compose` w kontenerze wymaga CLI w obrazie lub wywołania Docker API bezpośrednio (obecnie `compose_runner.py` używa subprocess — działa z mounted sock + `docker` CLI w image).
|
||||
- Ścieżki `compose_dir` w `stacks.yaml` muszą istnieć w `REPO_ROOT` wewnątrz kontenera (`/repo/stacks/...`).
|
||||
|
||||
---
|
||||
|
||||
## 11. Ryzyka i mitigacje
|
||||
|
||||
| Ryzyko | Prawdopodobieństwo | Mitigacja |
|
||||
|--------|-------------------|-----------|
|
||||
| Pad server-ui = brak edycji krzywej | Średnie | Daemon gpu-fan **niezależny** — ostatnia krzywa dalej działa |
|
||||
| Server-ui w Docker nie widzi ścieżek compose | Wysokie przy złym mount | Mount całego repo; test `REPO_ROOT` w healthcheck |
|
||||
| `host.docker.internal` niedostępny | Niskie na Docker 20.10+ | `extra_hosts: host-gateway` |
|
||||
| Dwa API key — UX | Średnie | Jeden klucz w UI; proxy dokleja secret do agenta |
|
||||
| Regresja graceful shutdown fan | Niskie przy zachowaniu systemd | Nie ruszać `fan_controller.shutdown()`; test po `systemctl stop` |
|
||||
| npmplus + server-ui port conflict | Brak | 8091 vs 80/443/81 |
|
||||
|
||||
---
|
||||
|
||||
## 12. Anti-patterns (czego unikać)
|
||||
|
||||
1. **NVML fan control w kontenerze Docker** — odrzucone w gpu-fan research.
|
||||
2. **Jeden monolityczny proces root z UI na 0.0.0.0** — obecny gpu-fan; do refaktoru.
|
||||
3. **Server-ui bez docker.sock a z pretensją do zarządzania kontenerami** — wymaga sock lub Docker API.
|
||||
4. **Osadzanie ComfyUI w server-ui zamiast linków** — duży scope, CORS, osobne sesje.
|
||||
5. **Budowa „Proxmox w Dockerze”** — over-engineering dla single-node AI homelab.
|
||||
6. **Publiczny port agenta 18090** — tylko localhost.
|
||||
|
||||
---
|
||||
|
||||
## 13. Autentykacja — model docelowy
|
||||
|
||||
| Warstwa | Mechanizm |
|
||||
|---------|-----------|
|
||||
| Użytkownik → server-ui | `X-API-Key` (mutacje + opcjonalnie odczyt gpu-fan) |
|
||||
| server-ui → gpu-fan agent | Ten sam key lub `AGENT_KEY` w env server-ui; request z kontenera przez `host.docker.internal` |
|
||||
| Użytkownik → ComfyUI/LocalAI | Osobne klucze aplikacji (poza server-ui) |
|
||||
| NPMPlus → server-ui | TLS + opcjonalnie basic auth na proxy host |
|
||||
|
||||
Docelowo: **jeden panel, jeden klucz** do operacji serwerowych (start/stop stacków + gpu-fan).
|
||||
|
||||
---
|
||||
|
||||
## 14. Checklist dla kolejnego agenta
|
||||
|
||||
### Research (ten dokument)
|
||||
- [x] Raport SERVER-UI-ARCHITECTURE-RESEARCH.md
|
||||
- [x] ADR-001-host-agent-control-plane.md
|
||||
- [x] Link w BACKLOG.md
|
||||
|
||||
### Implementacja (przyszłe PR)
|
||||
- [ ] Faza 1: gpu-fan API-only na localhost:18090
|
||||
- [ ] Faza 2: proxy + UI w server-ui
|
||||
- [ ] Faza 3: Dockerfile + compose server-ui
|
||||
- [ ] Faza 4: NPMPlus proxy panel
|
||||
- [ ] Faza 5: host-agent (opcjonalnie)
|
||||
|
||||
---
|
||||
|
||||
## 15. Podsumowanie jednym akapitem
|
||||
|
||||
Przeniesienie **samego UI** gpu-fan do Server UI w Dockerze, przy **daemonie NVML na hoście**, to właściwy plan zgodny z Portainer (UI w kontenerze) i Proxmox (privileged na hoście). Najpierw rozdziel proces gpu-fan na agent localhost i panel, potem dockerizuj server-ui z `docker.sock` i proxy do agenta; w dłuższej perspektywie rozważ jeden `host-agent` zamiast wielu portów. **Nie** przenoś NVML do kontenera.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Server UI CLI — bugfix (pusty terminal)
|
||||
|
||||
**Data:** 2026-07-05
|
||||
**Status:** naprawione
|
||||
|
||||
## Symptom
|
||||
|
||||
- Zakładka **CLI** pusta (`cli-terminal-wrap` bez zawartości)
|
||||
- Przycisk **Połącz ponownie** bez efektu
|
||||
- Inne zakładki (Stacki, Pliki, GPU Fan) działają z tym samym API Key
|
||||
- **Sprawdź klucz** zwraca „Klucz poprawny”
|
||||
|
||||
## Root cause
|
||||
|
||||
Błąd JavaScript w [`stacks/server-ui/static/index.html`](../stacks/server-ui/static/index.html) — `connectCli()`:
|
||||
|
||||
```javascript
|
||||
cliFitAddon = new FitAddon(); // TypeError: FitAddon is not a constructor
|
||||
```
|
||||
|
||||
Bundel UMD [`addon-fit.min.js`](../stacks/server-ui/static/vendor/xterm/addon-fit.min.js) eksportuje **obiekt modułu** `{ FitAddon: class }`, nie samą klasę na `window.FitAddon`.
|
||||
|
||||
Wywołanie rzuca wyjątek **przed** `cliTerm.open()` → pusty kontener, WebSocket nigdy nie startuje.
|
||||
|
||||
Auth WebSocket (`?api_key=`) nie był przyczyną — HTTP API używa tego samego klucza i działało.
|
||||
|
||||
## Fix
|
||||
|
||||
1. **`createFitAddon()`** — obsługa obu wariantów UMD:
|
||||
- `new FitAddon()` (gdy global to klasa)
|
||||
- `new FitAddon.FitAddon()` (aktualny bundel)
|
||||
2. **`try/catch`** w `connectCli()` — błąd widoczny w `#cli-status`
|
||||
3. **Sprawdzenie** `typeof Terminal === 'function'`
|
||||
4. **Fallback** bez FitAddon: `cols: 80`, `rows: 24`, `#cli-terminal { min-height: 360px }`
|
||||
5. **Reconnect** po udanym `verifyApiKey()` na zakładce CLI
|
||||
6. **Czytelne komunikaty** `cliWs.onclose` (kod 1008, zły klucz, CLI disabled)
|
||||
|
||||
## Weryfikacja
|
||||
|
||||
```bash
|
||||
# Deploy
|
||||
rsync -a --exclude '.venv' --exclude '__pycache__' stacks/server-ui/ /opt/server-ui/
|
||||
sudo systemctl restart server-ui
|
||||
|
||||
# WebSocket (wymaga websockets / wscat)
|
||||
API_KEY=$(sudo grep ^API_KEY= /opt/control-plane/.env | cut -d= -f2)
|
||||
# python -c z websockets → echo hello-cli-test
|
||||
```
|
||||
|
||||
**UI:**
|
||||
|
||||
1. `http://<IP>:8091/#cli` + API Key → **Zapisz** → **Sprawdź klucz**
|
||||
2. Prompt bash widoczny, `echo test` działa
|
||||
3. **Połącz ponownie** → nowa sesja OK
|
||||
4. Zły klucz → komunikat w statusie (nie pusty ekran)
|
||||
|
||||
## Pliki zmienione
|
||||
|
||||
| Plik | Zmiana |
|
||||
|------|--------|
|
||||
| `stacks/server-ui/static/index.html` | createFitAddon, try/catch, WS messages, CSS |
|
||||
| `manual-tutorial/10-server-ui-cli.md` | troubleshooting pustego CLI |
|
||||
| `coding-agent/SERVER-UI-CLI.md` | link do tego raportu |
|
||||
|
||||
Tutorial użytkownika: [`manual-tutorial/10-server-ui-cli.md`](../manual-tutorial/10-server-ui-cli.md)
|
||||
@@ -0,0 +1,101 @@
|
||||
# Server UI — CLI (terminal PTY)
|
||||
|
||||
**Data:** 2026-07-05
|
||||
**Status:** wdrożone (bugfix FitAddon: 2026-07-05 — patrz [SERVER-UI-CLI-BUGFIX.md](SERVER-UI-CLI-BUGFIX.md))
|
||||
|
||||
Zakładka **CLI** w panelu Server UI (`:8091`) — interaktywny shell bash w przeglądarce (PTY + xterm.js).
|
||||
|
||||
## UI
|
||||
|
||||
- Zakładka **CLI** (pierwsza, przed Pliki)
|
||||
- Hash URL: `http://<host>:8091/#cli`
|
||||
- Wymaga **API Key** w toolbarze (WebSocket nie obsługuje nagłówka `X-API-Key`)
|
||||
|
||||
## Architektura
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Browser
|
||||
participant xterm as xterm_js
|
||||
participant WS as WS_api_cli_ws
|
||||
participant PTY as cli_pty_py
|
||||
participant Shell as bash
|
||||
|
||||
Browser->>xterm: klawiatura
|
||||
xterm->>WS: dane terminala
|
||||
WS->>PTY: zapis master_fd
|
||||
PTY->>Shell: stdin
|
||||
Shell->>PTY: stdout/stderr
|
||||
PTY->>WS: odczyt master_fd
|
||||
WS->>xterm: output
|
||||
```
|
||||
|
||||
## WebSocket
|
||||
|
||||
| Parametr | Wartość |
|
||||
|----------|---------|
|
||||
| Ścieżka | `/api/cli/ws` |
|
||||
| Auth | `?api_key=<API_KEY>` (query string) |
|
||||
| Protokół I/O | surowe bajty terminala (text/binary frames) |
|
||||
| Resize | JSON: `{"type":"resize","cols":120,"rows":40}` |
|
||||
|
||||
### Przykład (wscat)
|
||||
|
||||
```bash
|
||||
API_KEY=$(sudo grep ^API_KEY= /opt/control-plane/.env | cut -d= -f2)
|
||||
|
||||
# wymaga: npm i -g wscat
|
||||
wscat -c "ws://127.0.0.1:8091/api/cli/ws?api_key=${API_KEY}"
|
||||
```
|
||||
|
||||
Bez klucza na bind LAN → połączenie zamknięte z kodem `1008`.
|
||||
|
||||
## Konfiguracja
|
||||
|
||||
W [`/opt/control-plane/.env`](/opt/control-plane/.env) (opcjonalnie):
|
||||
|
||||
| Zmienna | Domyślnie | Opis |
|
||||
|---------|-----------|------|
|
||||
| `CLI_ENABLED` | `1` | `0` = WebSocket odrzucany |
|
||||
| `CLI_SHELL` | `/bin/bash` | Shell PTY (np. `/bin/bash -l`) |
|
||||
| `CLI_DEFAULT_CWD` | `$HOME` usługi | Katalog startowy |
|
||||
| `CLI_MAX_SESSIONS` | `5` | Limit równoległych sesji WS |
|
||||
|
||||
## Uprawnienia POSIX
|
||||
|
||||
Server UI działa jako **`tomasz-syn-grzegorza`** (`server-ui.service`):
|
||||
|
||||
- `docker ps`, edycja repo, `/data/apps/` — OK
|
||||
- `sudo` — interaktywnie (hasło użytkownika w terminalu)
|
||||
- root-only pliki — odmowa jak w SSH
|
||||
|
||||
## Kod
|
||||
|
||||
| Plik | Rola |
|
||||
|------|------|
|
||||
| [`stacks/server-ui/cli_pty.py`](../stacks/server-ui/cli_pty.py) | PTY + asyncio bridge |
|
||||
| [`stacks/server-ui/app.py`](../stacks/server-ui/app.py) | WebSocket `/api/cli/ws`, mount `/static` |
|
||||
| [`stacks/server-ui/static/index.html`](../stacks/server-ui/static/index.html) | Zakładka CLI |
|
||||
| [`stacks/server-ui/static/vendor/xterm/`](../stacks/server-ui/static/vendor/xterm/) | xterm.js + FitAddon (vendored; UMD: `new FitAddon.FitAddon()`) |
|
||||
|
||||
## Bezpieczeństwo
|
||||
|
||||
- Pełny shell przez LAN = **wysokie ryzyko** — wymagany `API_KEY` przy `SERVER_UI_HOST=0.0.0.0`
|
||||
- `CLI_ENABLED=0` jako kill switch
|
||||
- `CLI_MAX_SESSIONS` ogranicza liczbę równoległych bashów
|
||||
- Brak whitelisty komend (właściciel serwera)
|
||||
- Sesja kończy się przy zamknięciu zakładki / rozłączeniu WS (nowy bash przy reconnect)
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
sudo rsync -a --exclude '.venv' --exclude '__pycache__' \
|
||||
stacks/server-ui/ /opt/server-ui/
|
||||
sudo systemctl restart server-ui
|
||||
```
|
||||
|
||||
Tutorial użytkownika: [`manual-tutorial/10-server-ui-cli.md`](../manual-tutorial/10-server-ui-cli.md)
|
||||
|
||||
## Znane problemy / bugfix
|
||||
|
||||
Pusty terminal przy działającym API Key: [`SERVER-UI-CLI-BUGFIX.md`](SERVER-UI-CLI-BUGFIX.md)
|
||||
@@ -0,0 +1,84 @@
|
||||
# Server UI — File Explorer
|
||||
|
||||
**Data:** 2026-07-05
|
||||
**Status:** wdrożone
|
||||
|
||||
Zakładka **Pliki** w panelu Server UI (`:8091`) — przeglądanie systemu plików, edycja tekstu, pełny CRUD.
|
||||
|
||||
## UI
|
||||
|
||||
- Zakładka **Pliki** (druga, po CLI)
|
||||
- Hash URL: `http://<host>:8091/#files`
|
||||
- Wymaga `X-API-Key` (ten sam co reszta panelu przy bind LAN)
|
||||
|
||||
## API
|
||||
|
||||
Wszystkie endpointy: nagłówek `X-API-Key` (gdy `API_KEY` ustawiony i bind LAN).
|
||||
|
||||
| Metoda | Ścieżka | Opis |
|
||||
|--------|---------|------|
|
||||
| GET | `/api/files?path=/` | Lista katalogu |
|
||||
| GET | `/api/files/read?path=...` | Odczyt pliku (UTF-8 lub base64 dla binarnych) |
|
||||
| PUT | `/api/files/write` | `{"path","content"}` — zapis tekstu |
|
||||
| POST | `/api/files/mkdir` | `{"path"}` — nowy folder |
|
||||
| POST | `/api/files/rename` | `{"old_path","new_path"}` |
|
||||
| DELETE | `/api/files?path=...` | Usuń plik lub pusty katalog |
|
||||
|
||||
### Przykłady curl
|
||||
|
||||
```bash
|
||||
API_KEY=$(sudo grep ^API_KEY= /opt/control-plane/.env | cut -d= -f2)
|
||||
|
||||
curl -s "http://127.0.0.1:8091/api/files?path=/tmp" \
|
||||
-H "X-API-Key: ${API_KEY}"
|
||||
|
||||
curl -s "http://127.0.0.1:8091/api/files/read?path=/tmp/test.txt" \
|
||||
-H "X-API-Key: ${API_KEY}"
|
||||
|
||||
curl -s -X PUT "http://127.0.0.1:8091/api/files/write" \
|
||||
-H "X-API-Key: ${API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"path":"/tmp/test.txt","content":"hello"}'
|
||||
```
|
||||
|
||||
## Konfiguracja
|
||||
|
||||
W [`/opt/control-plane/.env`](/opt/control-plane/.env) (opcjonalnie):
|
||||
|
||||
| Zmienna | Domyślnie | Opis |
|
||||
|---------|-----------|------|
|
||||
| `FILE_EXPLORER_ROOT` | `/` | Korzeń przeglądania |
|
||||
| `FILE_EXPLORER_MAX_BYTES` | `2097152` | Limit odczytu/zapisu (2 MiB) |
|
||||
|
||||
## Uprawnienia POSIX
|
||||
|
||||
Server UI działa jako użytkownik **`tomasz-syn-grzegorza`** (`server-ui.service`). Explorer **nie omija** uprawnień systemowych:
|
||||
|
||||
- `/home/...`, repo, `/data/apps/...` — zwykle OK
|
||||
- `/opt/control-plane/.env` (root 600) — odmowa w UI
|
||||
- pliki root-only — komunikat „Brak uprawnień”
|
||||
|
||||
## Kod
|
||||
|
||||
| Plik | Rola |
|
||||
|------|------|
|
||||
| [`stacks/server-ui/file_explorer.py`](../stacks/server-ui/file_explorer.py) | Logika FS |
|
||||
| [`stacks/server-ui/app.py`](../stacks/server-ui/app.py) | Endpointy `/api/files*` |
|
||||
| [`stacks/server-ui/static/index.html`](../stacks/server-ui/static/index.html) | Zakładka Pliki |
|
||||
|
||||
## Bezpieczeństwo
|
||||
|
||||
- Path traversal: `Path.resolve()` + sprawdzenie względem `FILE_EXPLORER_ROOT`
|
||||
- Pliki binarne: podgląd base64, zapis zablokowany (415)
|
||||
- Usuwanie: potwierdzenie w UI (`confirm()`)
|
||||
- Katalog niepusty: 409 przy DELETE
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
sudo bash stacks/server-ui/scripts/install.sh
|
||||
# lub tylko restart po rsync:
|
||||
sudo systemctl restart server-ui
|
||||
```
|
||||
|
||||
Tutorial użytkownika: [`manual-tutorial/09-file-explorer.md`](../manual-tutorial/09-file-explorer.md)
|
||||
@@ -0,0 +1,142 @@
|
||||
# Server UI — opcje instalacji (Control Plane)
|
||||
|
||||
**Data:** 2026-07-05
|
||||
**Host:** gmktec-k11
|
||||
**Kontekst:** Użytkownik nie widzi Server UI w `docker ps` — wyjaśnienie runtime + instalator z wyborem native/Docker.
|
||||
|
||||
**Powiązane:**
|
||||
- [`stacks/server-ui/`](../stacks/server-ui/)
|
||||
- [`manual-tutorial/08-server-ui-install.md`](../manual-tutorial/08-server-ui-install.md)
|
||||
- [`ADR-001-host-agent-control-plane.md`](ADR-001-host-agent-control-plane.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive summary
|
||||
|
||||
- **Server UI** domyślnie działa jako **systemd** (`server-ui.service`, port 8091) — **nie** jako kontener Docker.
|
||||
- **gpu-fan** działa wyłącznie jako **host agent** (systemd, port 18090 localhost) — Docker **nie jest obsługiwany** (NVML).
|
||||
- Nowy skrypt **`install-control-plane.sh`** instaluje oba komponenty z menu: gpu-fan native + Server UI native **lub** Docker.
|
||||
- Tutorial dla użytkownika: [`08-server-ui-install.md`](../manual-tutorial/08-server-ui-install.md).
|
||||
|
||||
---
|
||||
|
||||
## 2. Architektura
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph dockerStacks [Docker workloads]
|
||||
comfyui[comfyui :8188]
|
||||
localai[localai :8070]
|
||||
npmplus[npmplus :81]
|
||||
end
|
||||
subgraph hostNative [Host systemd]
|
||||
gpufan[gpu-fan agent :18090]
|
||||
end
|
||||
subgraph serverUINative [Server UI native]
|
||||
suiNative[server-ui :8091]
|
||||
end
|
||||
subgraph serverUIDocker [Server UI docker optional]
|
||||
suiDocker[container server-ui :8091]
|
||||
end
|
||||
browser[Browser] --> suiNative
|
||||
browser -.-> suiDocker
|
||||
suiNative --> dockerStacks
|
||||
suiDocker --> dockerStacks
|
||||
suiNative --> gpufan
|
||||
suiDocker --> gpufan
|
||||
gpufan --> gpu[RTX 3090 Ti]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Instalator
|
||||
|
||||
**Ścieżka:** `stacks/server-ui/scripts/install-control-plane.sh`
|
||||
|
||||
| Flaga | Wartości | Domyślnie (-y) |
|
||||
|-------|----------|----------------|
|
||||
| `--gpu-fan` | yes, no, skip | yes |
|
||||
| `--server-ui` | native, docker, skip | native |
|
||||
|
||||
**Przykłady:**
|
||||
|
||||
```bash
|
||||
cd ~/cursor/ubuntu-bare-metal/stacks/server-ui
|
||||
sudo ./scripts/install-control-plane.sh # interaktywne menu
|
||||
sudo ./scripts/install-control-plane.sh -y # gpu-fan + server-ui native
|
||||
sudo ./scripts/install-control-plane.sh --gpu-fan=yes --server-ui=docker
|
||||
```
|
||||
|
||||
**Skrypty pomocnicze:**
|
||||
|
||||
| Skrypt | Rola |
|
||||
|--------|------|
|
||||
| `install-control-plane.sh` | Menu: gpu-fan + server-ui |
|
||||
| `install.sh` | Tylko Server UI native |
|
||||
| `install-docker.sh` | Tylko Server UI Docker |
|
||||
|
||||
Przy przełączaniu native ↔ Docker instalator wyłącza drugi tryb (systemd disable lub `compose down`).
|
||||
|
||||
---
|
||||
|
||||
## 4. Klucze API
|
||||
|
||||
Jeden plik dla gpu-fan + Server UI:
|
||||
|
||||
| Środowisko | Plik | Zmienna |
|
||||
|------------|------|---------|
|
||||
| Produkcja (systemd) | `/opt/control-plane/.env` | `API_KEY` |
|
||||
| Dev / Docker | `stacks/control-plane/.env` | `API_KEY` |
|
||||
|
||||
`API_KEY` służy do auth panelu (`X-API-Key`) **i** proxy `/api/gpu-fan/*` do agenta.
|
||||
|
||||
```bash
|
||||
grep ^API_KEY= /opt/control-plane/.env
|
||||
```
|
||||
|
||||
Szczegóły: [`CONTROL-PLANE-ENV.md`](CONTROL-PLANE-ENV.md)
|
||||
|
||||
---
|
||||
|
||||
## 5. Server UI w Dockerze
|
||||
|
||||
| Plik | Opis |
|
||||
|------|------|
|
||||
| `Dockerfile` | Python 3.12 + docker CLI + compose plugin |
|
||||
| `docker-compose.yml` | profil `server-ui`, mount `docker.sock` + repo `/repo` |
|
||||
| `install-docker.sh` | build, up, generacja `.env` |
|
||||
|
||||
**GPU fan z kontenera:** `GPU_FAN_AGENT_URL=http://host.docker.internal:18090` + `extra_hosts: host-gateway`.
|
||||
|
||||
**Wymagania:** `REPO_ROOT` w `.env` wskazuje na katalog z `stacks/` (mount read-only do `/repo`).
|
||||
|
||||
---
|
||||
|
||||
## 6. Weryfikacja
|
||||
|
||||
```bash
|
||||
# Native
|
||||
systemctl is-active server-ui gpu-fan
|
||||
curl -s http://127.0.0.1:8091/api/health
|
||||
|
||||
# Docker
|
||||
docker compose --profile server-ui ps
|
||||
curl -s http://127.0.0.1:8091/api/stacks
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Checklist wdrożenia
|
||||
|
||||
- [x] `Dockerfile`, `docker-compose.yml`, `install-docker.sh`
|
||||
- [x] `install-control-plane.sh`
|
||||
- [x] `manual-tutorial/08-server-ui-install.md`
|
||||
- [x] README server-ui + główny README (rozdział 08)
|
||||
- [ ] Na produkcji: `sudo ./scripts/install-control-plane.sh` lub `install.sh` / `install-docker.sh`
|
||||
- [ ] Test GPU Fan proxy po instalacji Docker
|
||||
|
||||
---
|
||||
|
||||
## 8. Dlaczego gpu-fan bez Docker
|
||||
|
||||
Patrz [`stacks/gpu-fan/coding-agent/DOCKER-VS-HOST-REPORT.md`](../stacks/gpu-fan/coding-agent/DOCKER-VS-HOST-REPORT.md) i ADR-001. Instalator wyświetla komunikat i oferuje tylko native.
|
||||
@@ -0,0 +1,173 @@
|
||||
# Server UI — edycja portów stacków
|
||||
|
||||
**Data:** 2026-07-05
|
||||
**Host:** gmktec-k11
|
||||
**Kontekst:** Linki „Otwórz :PORT” w Server UI brały port z `stacks.yaml`, podczas gdy Docker mapował port z osobnego `stacks/<name>/.env`. Wdrożono jeden source of truth w `.env` stacku + edycję w UI.
|
||||
|
||||
**Powiązane:**
|
||||
- [`stacks/server-ui/`](../stacks/server-ui/)
|
||||
- [`coding-agent/SERVER-UI-ARCHITECTURE-RESEARCH.md`](SERVER-UI-ARCHITECTURE-RESEARCH.md)
|
||||
- [`coding-agent/CONVENTIONS.md`](CONVENTIONS.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive summary
|
||||
|
||||
Server UI odczytuje i zapisuje port hosta w pliku `stacks/<name>/.env` (np. `LOCALAI_PORT=8070`). Karty stacków mają pole numeryczne i przycisk **Zapisz port**; po zapisie uruchamiany jest `docker compose up -d --force-recreate` gdy stack działa. Nowa usługa wymaga tylko wpisu w `stacks.yaml` z polami `port_env`, `port_default`, `port_editable`.
|
||||
|
||||
---
|
||||
|
||||
## 2. As-is / to-be
|
||||
|
||||
| Aspekt | Przed | Po |
|
||||
|--------|-------|-----|
|
||||
| Źródło portu w linku UI | `stacks.yaml` → `ui_port` (statyczny) | `stacks/<name>/.env` → `port_env` |
|
||||
| Edycja portu | ręcznie w `.env` + restart compose | UI + `PATCH /api/stacks/{id}/port` |
|
||||
| Nowa usługa | hardcode w JS | metadane w `stacks.yaml` |
|
||||
| NPMPlus | link :81 | read-only (host network) |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
yaml[stacks.yaml port_env metadata]
|
||||
env[stack .env]
|
||||
api[Server UI API]
|
||||
ui[Stack card port input]
|
||||
compose[docker compose force-recreate]
|
||||
yaml --> api
|
||||
env --> api
|
||||
api --> ui
|
||||
api -->|PATCH| env
|
||||
env --> compose
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Konfiguracja stacków (`stacks.yaml`)
|
||||
|
||||
| Stack | `port_env` | `port_default` | `port_editable` |
|
||||
|-------|------------|----------------|-----------------|
|
||||
| localai | `LOCALAI_PORT` | 8080 | true |
|
||||
| comfyui | `COMFYUI_PORT` | 8188 | true |
|
||||
| vllm | `VLLM_PORT` | 8000 | true |
|
||||
| npmplus | — | 81 | false |
|
||||
|
||||
**Nowa usługa** — dodaj wpis:
|
||||
|
||||
```yaml
|
||||
- id: myservice
|
||||
name: My Service
|
||||
compose_dir: myservice
|
||||
profile: myservice
|
||||
container: myservice
|
||||
ui_port: 9000 # fallback gdy brak w .env
|
||||
port_env: MYSERVICE_PORT
|
||||
port_default: 9000
|
||||
port_editable: true
|
||||
gpu: false
|
||||
```
|
||||
|
||||
W `docker-compose.yml` stacku: `"${MYSERVICE_PORT:-9000}:9000"`.
|
||||
|
||||
---
|
||||
|
||||
## 4. API
|
||||
|
||||
### `GET /api/stacks`
|
||||
|
||||
Każdy stack zawiera dodatkowo:
|
||||
|
||||
```json
|
||||
{
|
||||
"ui_port": 8070,
|
||||
"port_env": "LOCALAI_PORT",
|
||||
"port_editable": true,
|
||||
"port_default": 8080,
|
||||
"published_port": 8070,
|
||||
"port_pending_restart": false
|
||||
}
|
||||
```
|
||||
|
||||
`port_pending_restart`: kontener działa, ale opublikowany port ≠ wartość w `.env`.
|
||||
|
||||
### `PATCH /api/stacks/{stack_id}/port`
|
||||
|
||||
**Auth:** `X-API-Key` (jak start/stop)
|
||||
|
||||
**Body:**
|
||||
```json
|
||||
{ "port": 8070, "recreate": true }
|
||||
```
|
||||
|
||||
**Odpowiedź:**
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"stack_id": "localai",
|
||||
"port": 8070,
|
||||
"port_env": "LOCALAI_PORT",
|
||||
"requires_restart": false,
|
||||
"recreated": true,
|
||||
"running": true
|
||||
}
|
||||
```
|
||||
|
||||
**Błędy:**
|
||||
- `400` — stack bez edycji portu (npmplus)
|
||||
- `409` — port zajęty / poza zakresem 1024–65535
|
||||
- `500` — błąd docker compose
|
||||
|
||||
**Zarezerwowane porty:** 80, 443, 8090 (gpu-fan), 18090 (gpu-fan agent), `SERVER_UI_PORT` (8091).
|
||||
|
||||
---
|
||||
|
||||
## 5. Pliki zmienione
|
||||
|
||||
| Plik | Zmiana |
|
||||
|------|--------|
|
||||
| `stacks/server-ui/stacks.yaml` | `port_env`, `port_default`, `port_editable` |
|
||||
| `stacks/server-ui/compose_runner.py` | read/write `.env`, walidacja, recreate |
|
||||
| `stacks/server-ui/app.py` | `PATCH /api/stacks/{id}/port` |
|
||||
| `stacks/server-ui/static/index.html` | input portu w kartach stacków |
|
||||
|
||||
---
|
||||
|
||||
## 6. Weryfikacja
|
||||
|
||||
```bash
|
||||
cd ~/cursor/ubuntu-bare-metal/stacks/server-ui
|
||||
|
||||
# Lista stacków z portami
|
||||
curl -s http://127.0.0.1:8091/api/stacks | python3 -m json.tool
|
||||
|
||||
# Zmiana portu (wymaga API_KEY z .env)
|
||||
curl -s -X PATCH http://127.0.0.1:8091/api/stacks/localai/port \
|
||||
-H "X-API-Key: $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"port":8070,"recreate":true}'
|
||||
|
||||
grep LOCALAI_PORT ../localai/.env
|
||||
```
|
||||
|
||||
**UI:** zakładka Stacki → karta LocalAI → pole portu → **Zapisz port** → link „Otwórz” się aktualizuje.
|
||||
|
||||
**Prod:** `sudo scripts/install.sh && sudo systemctl restart server-ui`
|
||||
|
||||
---
|
||||
|
||||
## 7. Uwagi operacyjne
|
||||
|
||||
- Po zmianie `LOCALAI_PORT` zaktualizuj **upstream w NPMPlus** ręcznie (proxy host → nowy port).
|
||||
- `docker compose restart` **nie** zmienia mapowania portów — wymagany recreate (`recreate: true` w API).
|
||||
- NPMPlus (`network_mode: host`) — port 81 tylko do odczytu w UI.
|
||||
|
||||
---
|
||||
|
||||
## 8. Checklist wdrożenia
|
||||
|
||||
- [x] Metadane portów w `stacks.yaml`
|
||||
- [x] Backend: odczyt/zapis `.env`, walidacja, recreate
|
||||
- [x] API `PATCH /port`
|
||||
- [x] UI: input + Zapisz w kartach
|
||||
- [x] NPMPlus read-only
|
||||
- [ ] `install.sh` + restart server-ui na produkcji
|
||||
- [ ] Test zmiany portu LocalAI na hoście
|
||||
@@ -0,0 +1,69 @@
|
||||
# Research: Stability Matrix na gmktec-k11
|
||||
|
||||
Data: 2026-07-04
|
||||
Host: `gmktec-k11` — Ubuntu 26.04 minimized, RTX 3090 Ti 24 GB, **tylko SSH (headless)**
|
||||
|
||||
## Czym jest Stability Matrix
|
||||
|
||||
[LykosAI/StabilityMatrix](https://github.com/LykosAI/StabilityMatrix) to menedżer pakietów + **GUI desktop** (Avalonia/.NET) do instalacji ekosystemu Stable Diffusion:
|
||||
|
||||
- Pakiety: ComfyUI, Forge/A1111, Fooocus, InvokeAI, Kohya, StableSwarmUI, …
|
||||
- Funkcje: Inference UI, Model Browser (CivitAI/HF), wspólny katalog modeli
|
||||
- Linux: oficjalnie **AppImage** (`StabilityMatrix-linux-x64.zip`, latest v2.16.1)
|
||||
- Licencja: AGPL (kod), EULA (binaria)
|
||||
|
||||
## Czy działa na Ubuntu?
|
||||
|
||||
**Tak — oficjalnie wspierane**, ale jako aplikacja graficzna, nie serwis headless.
|
||||
|
||||
| Wymaganie | gmktec-k11 | Ocena |
|
||||
|-----------|------------|-------|
|
||||
| OS Linux x64 | Ubuntu 26.04 | OK |
|
||||
| GPU NVIDIA | RTX 3090 Ti, `nvidia-smi` | OK |
|
||||
| FUSE (AppImage) | Ubuntu minimized — brak domyślnie | Dołożyć `libfuse2t64` |
|
||||
| GUI (X11/Wayland) | Serwer SSH, minimized | **Brak** — SM wymaga pulpitu |
|
||||
| Miejsce na dysku | `/data` ~1 TB | OK |
|
||||
| Konflikt GPU | LocalAI na 8070 | Jeden duży workload GPU naraz |
|
||||
|
||||
### Znane problemy (GitHub Issues)
|
||||
|
||||
- Ubuntu 24.04+: `libfuse2t64` wymagane do AppImage
|
||||
- Instalacja pakietów: czasem brakuje `build-essential`, `python3-dev`, … ([#1454](https://github.com/LykosAI/StabilityMatrix/issues/1454))
|
||||
- Headless: możliwe z VNC/X11, ale SM nie jest do tego zaprojektowany
|
||||
- PATH w subprocessach: sporadyczne błędy `nvidia-smi` ([#1577](https://github.com/LykosAI/StabilityMatrix/issues/1577))
|
||||
|
||||
## Werdykt
|
||||
|
||||
| Pytanie | Odpowiedź |
|
||||
|---------|-----------|
|
||||
| Czy można zainstalować na Ubuntu? | **Tak** (AppImage + zależności) |
|
||||
| Czy ma sens na **tym** hoście? | **Nie** — tylko SSH, bez GUI |
|
||||
| Zgodność z repo `ubuntu-bare-metal`? | **Słaba** — tutorial 03b zakłada ComfyUI **w Dockerze** |
|
||||
|
||||
## Decyzja
|
||||
|
||||
**Stability Matrix nie instalujemy.**
|
||||
|
||||
Zamiast SM wdrażamy stack **ComfyUI w Dockerze** (`stacks/comfyui/`) — odpowiednik funkcji (generowanie obrazów / ComfyUI), bez GUI-menedżera, zgodny z architekturą repo.
|
||||
|
||||
## Gdyby kiedyś (z GUI)
|
||||
|
||||
Tylko z VNC/RDP + lightweight desktop lub fizycznym monitorem:
|
||||
|
||||
```bash
|
||||
sudo apt install libfuse2t64
|
||||
cd /data/apps
|
||||
wget https://github.com/LykosAI/StabilityMatrix/releases/download/v2.16.1/StabilityMatrix-linux-x64.zip
|
||||
unzip StabilityMatrix-linux-x64.zip
|
||||
chmod +x StabilityMatrix.AppImage
|
||||
./StabilityMatrix.AppImage # wymaga DISPLAY
|
||||
```
|
||||
|
||||
Data Directory: `/data/apps/stability-matrix/` — osobna ścieżka, niezależna od Docker stacks.
|
||||
|
||||
## Powiązane
|
||||
|
||||
- [`stacks/comfyui/README.md`](../stacks/comfyui/README.md)
|
||||
- [`COMFYUI-DEPLOYMENT.md`](COMFYUI-DEPLOYMENT.md)
|
||||
- [`COMFYUI-HANDOFF.md`](COMFYUI-HANDOFF.md)
|
||||
- [`SWARMUI-REMOVAL.md`](SWARMUI-REMOVAL.md) — poprzedni UI obrazów usunięty
|
||||
@@ -0,0 +1,147 @@
|
||||
# Stan systemu i repo
|
||||
|
||||
Snapshot na koniec sesji. Weryfikuj na serwerze przed działaniem (`docker ps`, `mountpoint /data`).
|
||||
|
||||
## Co działa
|
||||
|
||||
| Komponent | Status | Uwagi |
|
||||
|-----------|--------|-------|
|
||||
| Docker CE | OK | Root: `/data/docker` |
|
||||
| NVIDIA driver | OK | ~595.x, `nvidia-smi` na hoście |
|
||||
| GPU w kontenerze | OK | `--gpus all` testowany wcześniej |
|
||||
| Mount `/data` | OK | ~1 TB, ext4, fstab UUID |
|
||||
| vLLM image | OK | `vllm/vllm-openai:latest` pulled |
|
||||
| vLLM container | STOP | `VLLM_MODEL` puste |
|
||||
| LocalAI image | OK | `v4.4.3-gpu-nvidia-cuda-13` |
|
||||
| LocalAI container | RUN | profil `localai`, port host **8070** |
|
||||
| Server UI | stack w repo | `stacks/server-ui/` :8091 — native (systemd) lub Docker; zakładki: **CLI**, Pliki, Stacki, GPU Fan |
|
||||
| gpu-fan agent | host systemd | `/opt/gpu-fan` :18090 localhost — **nie w `docker ps`** |
|
||||
| ComfyUI | stack w repo | `stacks/comfyui/` — **nie uruchomiony na hoście** (rozdział 07) |
|
||||
| Firewall / TLS | — | nie wdrożony (rozdział 07) |
|
||||
|
||||
## Ścieżki na `/data`
|
||||
|
||||
```
|
||||
/data/
|
||||
├── docker/ # obrazy i warstwy Docker
|
||||
└── apps/
|
||||
├── vllm/huggingface/ # cache HF (AWQ) — bez pobranego modelu
|
||||
├── gguf/
|
||||
│ ├── qwen3.6-27b/ # pod GGUF z katalogu vLLM
|
||||
│ └── gemma-4-12b/
|
||||
├── localai/
|
||||
│ ├── models/ # modele LocalAI (pobieranie w toku)
|
||||
│ ├── backends/
|
||||
│ ├── configuration/ # api_keys.json, runtime_settings.json
|
||||
│ ├── images/
|
||||
│ └── data/ # auth DB, agents (przyszłe LOCALAI_AUTH)
|
||||
└── comfyui/ # rozdział 07
|
||||
├── storage/
|
||||
├── models/
|
||||
├── cache/hf-hub/
|
||||
├── cache/torch-hub/
|
||||
├── input/
|
||||
├── output/
|
||||
├── custom_nodes/
|
||||
└── workflows/
|
||||
```
|
||||
|
||||
## Pliki `.env` na serwerze (nie w git)
|
||||
|
||||
| Plik | Kluczowe ustawienia |
|
||||
|------|---------------------|
|
||||
| `/opt/control-plane/.env` | `API_KEY`, `SERVER_UI_*`, `GPU_FAN_*` — **jedyny** klucz panelu; instrukcja: [`04a-api-key.md`](../manual-tutorial/04a-api-key.md) |
|
||||
| `stacks/vllm/.env` | `VLLM_MODEL=` (puste), `MAX_MODEL_LEN=131072`, port 8000 |
|
||||
| `stacks/localai/.env` | `LOCALAI_PORT=8070`, `LOCALAI_API_KEY` ustawiony (sekret), obraz cuda-13 |
|
||||
|
||||
## Porty stacków (Server UI)
|
||||
|
||||
| Stack | Zmienna `.env` | Domyślny | Edycja w UI |
|
||||
|-------|----------------|----------|-------------|
|
||||
| LocalAI | `LOCALAI_PORT` | 8080 | tak |
|
||||
| ComfyUI | `COMFYUI_PORT` | 8188 | tak |
|
||||
| vLLM | `VLLM_PORT` | 8000 | tak |
|
||||
| NPMPlus | — (host network) | 81 | nie (read-only) |
|
||||
|
||||
Szczegóły: [`SERVER-UI-PORT-CONFIG.md`](SERVER-UI-PORT-CONFIG.md)
|
||||
|
||||
## Control plane (instalacja)
|
||||
|
||||
| Komponent | Runtime | Instalator |
|
||||
|-----------|---------|------------|
|
||||
| gpu-fan | systemd native only | `install-control-plane.sh` lub `gpu-fan/scripts/install.sh` |
|
||||
| Server UI | systemd **lub** Docker | `install-control-plane.sh`, `install.sh`, `install-docker.sh` |
|
||||
|
||||
Szczegóły: [`SERVER-UI-INSTALL-OPTIONS.md`](SERVER-UI-INSTALL-OPTIONS.md), [`CONTROL-PLANE-ENV.md`](CONTROL-PLANE-ENV.md), [`SERVER-UI-FILE-EXPLORER.md`](SERVER-UI-FILE-EXPLORER.md), tutorial [`08-server-ui-install.md`](../manual-tutorial/08-server-ui-install.md)
|
||||
|
||||
## Struktura repo (kluczowe pliki)
|
||||
|
||||
```
|
||||
ubuntu-bare-metal/
|
||||
├── README.md
|
||||
├── scripts/setup-data-disk.sh
|
||||
├── coding-agent/ # ten katalog
|
||||
├── manual-tutorial/
|
||||
│ ├── 00-prerequisites.md
|
||||
│ ├── 01-system-update-and-docker.md
|
||||
│ ├── 02-nvidia-driver.md
|
||||
│ ├── 03-nvidia-container-toolkit.md
|
||||
│ ├── 03b-system-tools.md
|
||||
│ ├── 04-vllm-stack.md # część A dysk + część B vLLM
|
||||
│ └── 05-localai-stack.md
|
||||
│ ├── 06-gpu-fan-control.md
|
||||
│ └── 07-comfyui-stack.md
|
||||
└── stacks/
|
||||
├── vllm/
|
||||
│ ├── models.catalog.yaml
|
||||
│ ├── docker-compose.yml
|
||||
│ ├── profiles/
|
||||
│ └── scripts/
|
||||
├── localai/
|
||||
│ ├── docker-compose.yml
|
||||
│ └── scripts/
|
||||
├── comfyui/
|
||||
│ ├── docker-compose.yml
|
||||
│ └── scripts/
|
||||
└── llamacpp/
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Szybka weryfikacja (komendy)
|
||||
|
||||
```bash
|
||||
# dysk i docker
|
||||
mountpoint /data && df -h /data
|
||||
docker info | grep "Docker Root Dir"
|
||||
|
||||
# vLLM
|
||||
cd ~/cursor/ubuntu-bare-metal/stacks/vllm
|
||||
docker compose --profile vllm ps
|
||||
grep VLLM_MODEL .env
|
||||
|
||||
# LocalAI (port z .env użytkownika: 8070)
|
||||
cd ~/cursor/ubuntu-bare-metal/stacks/localai
|
||||
docker compose --profile localai ps
|
||||
curl -s http://localhost:8070/readyz
|
||||
|
||||
# GPU
|
||||
nvidia-smi
|
||||
```
|
||||
|
||||
## Znane rozjazdy (docs vs runtime)
|
||||
|
||||
| Temat | Repo / docs | Faktycznie na serwerze |
|
||||
|-------|-------------|------------------------|
|
||||
| LocalAI port | 8080 w `.env.example`, tutorial 05 | **8070** w `.env` użytkownika |
|
||||
| LOCALAI_API_KEY | brak w `docker-compose.yml` | ustawiony w `.env`, **może nie działać** w kontenerze |
|
||||
| vLLM model | katalog + skrypty gotowe | model nie pobrany, stack nie startowany |
|
||||
|
||||
## Git / Gitea
|
||||
|
||||
- Repo zainicjowane lokalnie w `ubuntu-bare-metal/` (branch `main`)
|
||||
- Root `.gitignore` — wyklucza `.env`, `.venv`, `upstream/`
|
||||
- Push na Gitea: czeka na URL remote — [GITEA-PREP.md](GITEA-PREP.md)
|
||||
|
||||
## Server UI — wykres GPU Fan (ostatnie zmiany UI)
|
||||
|
||||
W `stacks/server-ui/static/index.html`: siatka osi, linijki kreskowe, odstępy etykiet, `aspect-ratio` SVG, wyśrodkowanie w karcie. Tutorial: [06-gpu-fan-control.md](../manual-tutorial/06-gpu-fan-control.md).
|
||||
@@ -0,0 +1,31 @@
|
||||
# SwarmUI — usunięte z hosta (2026-07-04)
|
||||
|
||||
Użytkownik zrezygnował ze SwarmUI na serwerze `gmktec-k11`.
|
||||
|
||||
## Co usunięto
|
||||
|
||||
| Element | Status |
|
||||
|---------|--------|
|
||||
| Kontener `swarmui` | usunięty |
|
||||
| Obraz `swarmui:local` | usunięty |
|
||||
| Sieć `swarmui_default` | usunięta |
|
||||
| Dane `/data/apps/swarmui/` (~3.3 GB) | usunięte |
|
||||
| Stack `stacks/swarmui/` (compose, upstream, skrypty) | usunięty z repo |
|
||||
| Dokumentacja `SWARMUI-*.md` | usunięta |
|
||||
|
||||
## Co zostało
|
||||
|
||||
- **LocalAI** — główny workload LLM/embed/rerank (`8070`)
|
||||
- **Server UI** — zarządzanie stackami compose (`8091`).
|
||||
- **NPMPlus** — reverse proxy (`80/443`)
|
||||
- Katalog `/data/apps/comfyui/` — pusty placeholder (nie był używany przez SwarmUI stack)
|
||||
|
||||
## Portainer
|
||||
|
||||
W UI Portainer stack/kontener `swarmui` może jeszcze widnieć jako „orphan” do czasu odświeżenia — kontener nie istnieje (`docker ps -a`).
|
||||
|
||||
## Nie odtwarzać bez prośby
|
||||
|
||||
- Nie klonować ponownie `stacks/swarmui/`
|
||||
- Nie tworzyć `/data/apps/swarmui/`
|
||||
- Port **7801** jest wolny
|
||||
Reference in New Issue
Block a user