# 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://: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=` (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)