Serwery — API v1 i MCP | Dokumentacja Nodea
Moduł Serwery to najobszerniejsza część API infrastruktury: pełny cykl życia zarządzanego serwera — tworzenie, edycja, usuwanie, test połączenia SSH, notatka, klonowanie, statystyki wydajności, komponenty (instalacja oprogramowania), historia operacji Ansible, status WAF z regułami oraz cykl życia sparowanej maszyny wirtualnej Proxmox (start / reboot / stop). Dokument opisuje warstwę REST API v1 (/api/v1/servers) oraz 17 narzędzi MCP z tego modułu.
Uwierzytelnianie i zakresy (scopes)
API v1 uwierzytelnia się tokenem osobistym Passport przez nagłówek Authorization: Bearer <token>. Poziomy dostępu:
infra:read— listowanie, podgląd, statystyki, komponenty, operacje, status WAF.infra:write— tworzenie, edycja + akcje niedestrukcyjne (test SSH, notatka, klon, instalacja komponentu, start i reboot VM, przełączanie reguły WAF).infra:delete— usunięcie serwera (kaskadowo niszczy sparowaną VM) oraz stop VM (wyłączenie produkcyjnej maszyny jest destrukcyjne).
Cały moduł jest dodatkowo bramkowany flagą Pennant features:servers — wyłączenie modułu ukrywa go zarówno w web UI, jak i w API/MCP. Limit zapytań: throttle:api-v1 = 240/min na użytkownika tokenu.
Własność i widoczność (ServerPolicy)
Dostęp na poziomie wiersza wymusza ServerPolicy: view = właściciel LUB zaakceptowany udział FOREIGN (współpracownik może korzystać ze wszystkich endpointów odczytu), manage = wyłącznie właściciel (pivot OWNER). Wszystkie mutacje wymagają manage. Serwer istniejący, ale niewidoczny dla tokenu zwraca 403 (polityka); nieznany identyfikator — 404 (route model binding). Listowanie korzysta z User::servers(), więc zaproszenia jeszcze niezaakceptowane nie wyciekają, a zaakceptowane udziały FOREIGN są widoczne (parytet z web).
Bezpieczeństwo — co NIE trafia do odpowiedzi
Selekcja pól jest jawna i celowo pomija wszystko wrażliwe:
- ServerResource — pomija relację
sshKey(zaszyfrowana para kluczy deploy public/private, ładowana na każdym zapytaniu przezServer::$with) oraz surowy blobinfoze skryptu bootstrap. - VirtualMachineResource — nigdy nie serializuje
root_passwordVM (zaszyfrowane, reveal tylko dla admina), współrzędnych klastra PVE ani per-VM użytkownika Proxmox. - ServerComponentResource — maskuje w
data_rowsklucze wyglądające na sekrety (zawierającepass,secretlubtoken→********); akcje manage zapisujądb_passi podobne do zaszyfrowanego blobadata, który nigdy nie wraca API-em. - ServerOperationResource — nie serializuje surowego bloba
output(pełny JSON Ansible z możliwymi zmiennymi playbooka); kontraktem API jest maszynowystatus.
API v1 — endpointy
| Metoda | Ścieżka | Scope | Opis |
|---|---|---|---|
| GET | /api/v1/servers | infra:read | Lista serwerów (własne + zaakceptowane udziały), paginowana; ownership. |
| GET | /api/v1/servers/{server} | infra:read | Szczegóły serwera (komponenty + sparowana VM). |
| GET | /api/v1/servers/{server}/statistics | infra:read | Serie CPU/RAM/dysk (ostatnie próbki z Prometheusa). |
| GET | /api/v1/servers/{server}/components | infra:read | Lista komponentów serwera i ich status. |
| GET | /api/v1/servers/{server}/components/{component} | infra:read | Szczegóły komponentu (scoped binding) z data_rows. |
| GET | /api/v1/servers/{server}/operations | infra:read | Historia operacji (Ansible), najnowsze pierwsze; simplePaginate. |
| GET | /api/v1/servers/{server}/waf | infra:read | Status WAF: has_agent, reguły, 20 ostatnich zdarzeń. |
| POST | /api/v1/servers | infra:write | Utworzenie serwera (właściciel = OWNER). Zwraca 201. |
| PUT/PATCH | /api/v1/servers/{server} | infra:write | Edycja nazwy/portu SSH (IP niemutowalne; owner-only). |
| POST | /api/v1/servers/{server}/test-ssh | infra:write | Test połączenia SSH kluczem deploy. {ok, message} lub 502. |
| PATCH | /api/v1/servers/{server}/note | infra:write | Ustawienie/wyczyszczenie notatki. |
| POST | /api/v1/servers/{server}/clone | infra:write | Klon serwera (limit create-server; VM odpięta). Zwraca 201. |
| POST | /api/v1/servers/{server}/components/{component}/install | infra:write | Instalacja komponentu (kolejka ansible). Zwraca 202 + operację. |
| POST | /api/v1/servers/{server}/vm/start | infra:write | Start VM (async). 422 gdy brak sparowanej VM. |
| POST | /api/v1/servers/{server}/vm/reboot | infra:write | Reboot VM (synchroniczny PVE). 422/409/502. |
| POST | /api/v1/servers/{server}/waf/rules/{rule}/toggle | infra:write | Włącz/wyłącz regułę WAF; enabled (bool). 422 bez agenta fleet. |
| DELETE | /api/v1/servers/{server} | infra:delete | Usunięcie serwera + async destroy VM. Zwraca 204. |
| POST | /api/v1/servers/{server}/vm/stop | infra:delete | Stop VM (destrukcyjne — wyłącza produkcyjną maszynę). 422 bez VM. |
Łącznie: 18 endpointów (7 read, 9 write, 2 delete).
GET /servers — parametry zapytania
search(string, opcjonalny, max 255) — dopasowanie po nazwie i IP.sort(enum:name|ip|created_at, domyślniecreated_at) — twarda whitelista.direction(asc|desc; dlacreated_atdomyślniedesc).per_page(int 1–100, domyślnie 20),page(int ≥1).
ServerResource — pola
id (UUID), name, ip, ssh_port, status (małe litery: new|connected|installed|error), note, ownership (owner|shared, tylko w index przez pivot), has_virtual_machine (bool), virtual_machine (obiekt lub null), components[] (gdy załadowane), created_at, updated_at. Obiekt virtual_machine: id, vm_id (VMID Proxmox), name, ip, status, last_error, oraz opcjonalnie plan, os_template, location.
POST/PATCH /servers — parametry ciała
name(string, wymagany, min 3, max 100).ip(wymagany dla POST,ipv4; niemutowalny — nieobecny w PATCH).ssh_port(wymagany, numeryczny).
Notatka: note (string, nullable). Toggle WAF: enabled (bool, wymagany). Instalacja: parametr URL {component} = wartość enuma Component (np. nginx, php83, mariadb1011, redis, postgres16, nodejs20).
Kody błędów
401— brak/niepoprawny token.403— brak scope lub istniejący, niewidoczny serwer (polityka).404— nieznany identyfikator; komponent z innego serwera (scoped binding).409— brak przypisania PVE przy reboot (no_pve_assignment).422— walidacja; brak sparowanej VM (no_virtual_machine); brak agenta fleet (no_fleet_agent).502— nieudany test SSH / nieudany reboot (reboot_failed).
Przykłady — API
Lista serwerów z sortowaniem
curl -s "https://app.nodea.io/api/v1/servers?sort=name&direction=asc&per_page=25" \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Accept: application/json"
Utworzenie serwera
curl -s -X POST https://app.nodea.io/api/v1/servers \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "web-01", "ip": "203.0.113.10", "ssh_port": 22 }'
Instalacja komponentu
curl -s -X POST https://app.nodea.io/api/v1/servers/{id}/components/nginx/install \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Accept: application/json"
Reboot maszyny wirtualnej
curl -s -X POST https://app.nodea.io/api/v1/servers/{id}/vm/reboot \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Accept: application/json"
Stop VM (destrukcyjne, scope infra:delete)
curl -s -X POST https://app.nodea.io/api/v1/servers/{id}/vm/stop \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Accept: application/json"
MCP — narzędzia (17)
Narzędzia MCP dzielą model uwierzytelniania i zakresów z API v1 (Bearer + scope w ensureScope) oraz flagę features:servers. Serwery adresowane są po UUID; brakujący i niedostępny identyfikator zwracają ten sam błąd (brak enumeracji między najemcami — InteractsWithServers::serverFor deleguje do polityk view/manage). Sekrety są maskowane identycznie jak w API (ten sam kształt zasobów). Narzędzia destrukcyjne (server-delete, server-vm-stop) wymagają confirm: true.
| Narzędzie | Scope | Tryb | Opis |
|---|---|---|---|
| servers-list | infra:read | read-only | Lista serwerów (własne + udziały FOREIGN); search, page, per_page (max 50). Zwraca etykietę ownership. |
| server-get | infra:read | read-only | Szczegóły serwera (komponenty + VM). Param: id. |
| server-statistics | infra:read | read-only | Serie CPU/RAM/dysk. Param: id. |
| server-components | infra:read | read-only | Lista komponentów; id, page, per_page. |
| server-operations | infra:read | read-only | Historia operacji; id, filtr component, paginacja. Bez surowego outputu playbooka. |
| server-waf-status | infra:read | read-only | Status WAF: has_agent, reguły, 20 ostatnich zdarzeń. Param: id. |
| server-create | infra:write | zapis | Utworzenie serwera; name (min 3), ip (ipv4), ssh_port. Limit create-server. |
| server-update | infra:write | idempotentne | Edycja name/ssh_port (częściowa; IP niemutowalne). Owner-only. |
| server-set-note | infra:write | idempotentne | Ustawienie/wyczyszczenie notatki; id, note (nullable). |
| server-test-ssh | infra:write | zapis (dial infra) | Test SSH kluczem deploy; {connected, error}. Owner-only. |
| server-clone | infra:write | zapis | Klon serwera; id. Limit create-server; VM odpięta. |
| server-component-install | infra:write | zapis | Instalacja komponentów; id, components[] (min 1, wartości enuma). Kolejka ansible, rozłożone co 10 s. |
| server-vm-start | infra:write | zapis | Start VM; id. Błąd no_virtual_machine gdy brak VM. |
| server-vm-reboot | infra:write | zapis | Reboot VM (synchroniczny PVE); id. |
| server-waf-rule-toggle | infra:write | idempotentne | Przełączenie reguły WAF; id, rule_id (max 64), enabled. Wysyła pełny zestaw override do agenta nodead. |
| server-delete | infra:delete | destrukcyjne | Usunięcie serwera + async destroy VM; id + wymagane confirm: true. Owner-only. |
| server-vm-stop | infra:delete | destrukcyjne | Stop VM (offline do restartu); id + wymagane confirm: true. Owner-only. |
Podział: 6 read-only, 9 zapisujących, 2 destrukcyjne.
Przykłady — MCP (JSON-RPC)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "server-component-install",
"arguments": { "id": "9b1f...uuid", "components": ["nginx", "php83"] }
}
}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "server-vm-stop",
"arguments": { "id": "9b1f...uuid", "confirm": true }
}
}