# GPU Fan Control stack Sterowanie wentylatorami **RTX 3090 Ti** na headless Ubuntu przez NVML — bez `nvidia-settings` i bez GUI. **Panel webowy** jest w **Server UI** (`http://:8091` → zakładka **GPU Fan**). Ten stack uruchamia tylko **agenta API** na localhost. ## Porty | Serwis | Port | Dostęp | |--------|------|--------| | GPU Fan agent API | **18090** | `127.0.0.1` tylko (systemd, root) | | GPU Fan UI | — | Server UI **:8091** (zakładka GPU Fan) | Port **8090** nie jest już używany w produkcji. ## Jak to działa ```mermaid flowchart LR browser["Przeglądarka :8091"] serverUI["server-ui"] agent["fan_daemon.py :18090"] nvml["NVML / pynvml"] gpu["RTX 3090 Ti"] browser --> serverUI serverUI -->|"proxy /api/gpu-fan"| agent agent --> nvml nvml --> gpu ``` | Element | Opis | |---------|------| | Agent | `fan_daemon.py` — pętla NVML + API REST | | Dev UI | `app.py` — monolit z UI (tylko dev / DRY_RUN) | | Logika | `fan_controller.py` — interpolacja krzywej | | Konfiguracja | `/etc/gpu-fan/curve.json` | | systemd | `gpu-fan.service` (root, auto-restart) | ## Wymagania - Sterownik NVIDIA ≥ 520 (testowane: **595-server-open**) - Root / sudo (zapis NVML wymaga uprawnień root) - Python 3 + venv (instalowane przez `install.sh`) - **Server UI** na :8091 (proxy do agenta) ## Szybki start ```bash cd stacks/gpu-fan cp ../control-plane/.env.example ../control-plane/.env sudo scripts/install.sh sudo systemctl start gpu-fan cd ../server-ui sudo scripts/install.sh ``` Panel: `http://:8091` → zakładka **GPU Fan**. Po zmianach w kodzie: ```bash sudo scripts/install.sh && sudo systemctl restart gpu-fan cd ../server-ui && sudo scripts/install.sh && sudo systemctl restart server-ui ``` Konfiguracja: produkcja `/opt/control-plane/.env`, dev `stacks/control-plane/.env`. ### Klucz API Jeden wspólny `API_KEY` w `/opt/control-plane/.env` — auth panelu Server UI i agenta gpu-fan (proxy `/api/gpu-fan/*`). W panelu Server UI wpisz ten sam klucz w polu **API Key** (lub `?api_key=...` w URL). ## Preset max cooling Domyślna krzywa w [`curve.default.json`](curve.default.json): | Temp | Speed | |------|-------| | 30°C | 50% | | 40°C | 65% | | 50°C | 80% | | 55°C | 90% | | 60°C | 100% | | 70°C+ | 100% | ## Tryby | Tryb | Opis | |------|------| | `curve` | Krzywa z JSON — interpolacja liniowa | | `manual` | Stała prędkość (np. 100% awaryjnie) | | `auto` | Oddaje sterowanie driverowi NVIDIA | ## API agenta (localhost :18090) | Endpoint | Metoda | Opis | |----------|--------|------| | `/api/status` | GET | Temperatura, wentylatory, moc, tryb | | `/api/curve` | GET/PUT | Odczyt / zapis krzywej | | `/api/mode` | POST | `{"mode":"auto\|curve\|manual","speed":100}` | | `/api/reload` | POST | Przeładuj `curve.json` (jak `SIGHUP`) | Z LAN używaj proxy Server UI: `/api/gpu-fan/status`, `/api/gpu-fan/curve`, itd. ## Ograniczenia NVIDIA - `nvidia-smi` **nie steruje** wentylatorami — tylko odczyt - API akceptuje **0%** (= auto) lub **30–100%** - Po `systemctl stop gpu-fan` wentylatory wracają do trybu auto drivera ## Struktura ``` stacks/gpu-fan/ ├── README.md ├── fan_daemon.py # produkcja — agent API ├── app.py # dev — UI + API (opcjonalnie) ├── fan_controller.py ├── curve.default.json ├── requirements.txt ├── gpu-fan.service ├── .env.example ├── static/index.html # referencja UI (wbudowane w server-ui) └── scripts/ ├── install.sh ├── enable-lan.sh # konfiguruje agent + wskazówka :8091 └── status.sh ``` ## Troubleshooting **`Insufficient Permissions`** — uruchom jako root (`sudo systemctl start gpu-fan`). **Panel GPU Fan pusty / 502** — sprawdź agent: `curl -s http://127.0.0.1:18090/api/status -H "X-API-Key: ..."` **Port 18090 zajęty** — `scripts/status.sh` / `sudo scripts/status.sh --cleanup` **Przeładuj krzywą bez restartu:** ```bash sudo systemctl reload gpu-fan ``` ## Dokumentacja | Dokument | Opis | |----------|------| | [docs/00-START-TUTAJ.md](docs/00-START-TUTAJ.md) | Mapa — zacznij tutaj | | [docs/02-OTWIERANIE-UI-W-PRZEGLADARCE.md](docs/02-OTWIERANIE-UI-W-PRZEGLADARCE.md) | Panel w Server UI :8091 | | [docs/05-DLACZEGO-NIE-DOCKER.md](docs/05-DLACZEGO-NIE-DOCKER.md) | Dlaczego host, nie Docker |