Nodea — logo

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 przez Server::$with) oraz surowy blob info ze skryptu bootstrap.
  • VirtualMachineResource — nigdy nie serializuje root_password VM (zaszyfrowane, reveal tylko dla admina), współrzędnych klastra PVE ani per-VM użytkownika Proxmox.
  • ServerComponentResource — maskuje w data_rows klucze wyglądające na sekrety (zawierające pass, secret lub token********); akcje manage zapisują db_pass i podobne do zaszyfrowanego bloba data, 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 maszynowy status.

API v1 — endpointy

MetodaŚcieżkaScopeOpis
GET/api/v1/serversinfra:readLista serwerów (własne + zaakceptowane udziały), paginowana; ownership.
GET/api/v1/servers/{server}infra:readSzczegóły serwera (komponenty + sparowana VM).
GET/api/v1/servers/{server}/statisticsinfra:readSerie CPU/RAM/dysk (ostatnie próbki z Prometheusa).
GET/api/v1/servers/{server}/componentsinfra:readLista komponentów serwera i ich status.
GET/api/v1/servers/{server}/components/{component}infra:readSzczegóły komponentu (scoped binding) z data_rows.
GET/api/v1/servers/{server}/operationsinfra:readHistoria operacji (Ansible), najnowsze pierwsze; simplePaginate.
GET/api/v1/servers/{server}/wafinfra:readStatus WAF: has_agent, reguły, 20 ostatnich zdarzeń.
POST/api/v1/serversinfra:writeUtworzenie serwera (właściciel = OWNER). Zwraca 201.
PUT/PATCH/api/v1/servers/{server}infra:writeEdycja nazwy/portu SSH (IP niemutowalne; owner-only).
POST/api/v1/servers/{server}/test-sshinfra:writeTest połączenia SSH kluczem deploy. {ok, message} lub 502.
PATCH/api/v1/servers/{server}/noteinfra:writeUstawienie/wyczyszczenie notatki.
POST/api/v1/servers/{server}/cloneinfra:writeKlon serwera (limit create-server; VM odpięta). Zwraca 201.
POST/api/v1/servers/{server}/components/{component}/installinfra:writeInstalacja komponentu (kolejka ansible). Zwraca 202 + operację.
POST/api/v1/servers/{server}/vm/startinfra:writeStart VM (async). 422 gdy brak sparowanej VM.
POST/api/v1/servers/{server}/vm/rebootinfra:writeReboot VM (synchroniczny PVE). 422/409/502.
POST/api/v1/servers/{server}/waf/rules/{rule}/toggleinfra:writeWłącz/wyłącz regułę WAF; enabled (bool). 422 bez agenta fleet.
DELETE/api/v1/servers/{server}infra:deleteUsunięcie serwera + async destroy VM. Zwraca 204.
POST/api/v1/servers/{server}/vm/stopinfra:deleteStop 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ślnie created_at) — twarda whitelista.
  • direction (asc|desc; dla created_at domyślnie desc).
  • 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ędzieScopeTrybOpis
servers-listinfra:readread-onlyLista serwerów (własne + udziały FOREIGN); search, page, per_page (max 50). Zwraca etykietę ownership.
server-getinfra:readread-onlySzczegóły serwera (komponenty + VM). Param: id.
server-statisticsinfra:readread-onlySerie CPU/RAM/dysk. Param: id.
server-componentsinfra:readread-onlyLista komponentów; id, page, per_page.
server-operationsinfra:readread-onlyHistoria operacji; id, filtr component, paginacja. Bez surowego outputu playbooka.
server-waf-statusinfra:readread-onlyStatus WAF: has_agent, reguły, 20 ostatnich zdarzeń. Param: id.
server-createinfra:writezapisUtworzenie serwera; name (min 3), ip (ipv4), ssh_port. Limit create-server.
server-updateinfra:writeidempotentneEdycja name/ssh_port (częściowa; IP niemutowalne). Owner-only.
server-set-noteinfra:writeidempotentneUstawienie/wyczyszczenie notatki; id, note (nullable).
server-test-sshinfra:writezapis (dial infra)Test SSH kluczem deploy; {connected, error}. Owner-only.
server-cloneinfra:writezapisKlon serwera; id. Limit create-server; VM odpięta.
server-component-installinfra:writezapisInstalacja komponentów; id, components[] (min 1, wartości enuma). Kolejka ansible, rozłożone co 10 s.
server-vm-startinfra:writezapisStart VM; id. Błąd no_virtual_machine gdy brak VM.
server-vm-rebootinfra:writezapisReboot VM (synchroniczny PVE); id.
server-waf-rule-toggleinfra:writeidempotentnePrzełączenie reguły WAF; id, rule_id (max 64), enabled. Wysyła pełny zestaw override do agenta nodead.
server-deleteinfra:deletedestrukcyjneUsunięcie serwera + async destroy VM; id + wymagane confirm: true. Owner-only.
server-vm-stopinfra:deletedestrukcyjneStop 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 }
  }
}