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:
tomasz-syn-grzegorza
2026-07-05 12:02:04 +00:00
commit 359afb3a59
153 changed files with 18169 additions and 0 deletions
@@ -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)
+94
View File
@@ -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 0003b, 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
+119
View File
@@ -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 ~812 GB; Flux ~1220 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: ~515 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
+82
View File
@@ -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)
+149
View File
@@ -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.*`).
+91
View File
@@ -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.
+90
View File
@@ -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)
+55
View File
@@ -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
```
+36
View File
@@ -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)
+113
View File
@@ -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 (0010, 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.*
+104
View File
@@ -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`
+144
View File
@@ -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.
+109
View File
@@ -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
+40
View File
@@ -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)
+225
View File
@@ -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+)
+28
View File
@@ -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 (P0P3)
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).
+102
View File
@@ -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).
+62
View File
@@ -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 161164)
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 (AE)
### 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 12 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.
+65
View File
@@ -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)
+101
View File
@@ -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)
+84
View File
@@ -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)
+142
View File
@@ -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.
+173
View File
@@ -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 102465535
- `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
+69
View File
@@ -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
+147
View File
@@ -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).
+31
View File
@@ -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