core/backend/internal/proxmox/client.go
enzo 5dbcb1df07 feat: initialisation complète du CORE ProxmoxPanel
Backend Go 1.23+ :
- API REST + WebSocket (chi, gorilla/websocket)
- Authentification PAM via SSH + JWT RS256
- Chiffrement AES-256-GCM pour secrets SQLite
- Pool SSH, client Proxmox REST, hub WebSocket pub/sub
- Système de modules compilés à initialisation conditionnelle
- Audit log, migrations SQLite versionnées

Frontend Vue 3 + Vite + TypeScript :
- Thème Neumorphism sombre/clair (CSS custom properties)
- Wizard d'installation, Dashboard drag-drop, Terminal xterm.js
- Toutes les vues CORE + stubs modules optionnels
- i18n EN/FR (vue-i18n v11)

Infrastructure :
- Docker multi-stage (Go → alpine, Node → nginx)
- docker-compose.yml, .gitattributes, LICENSE MIT, README

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:08:53 +01:00

212 lines
5.9 KiB
Go

// Package proxmox fournit un client pour l'API REST Proxmox VE.
// Les credentials (token API ou user/password) sont stockés chiffrés en SQLite.
package proxmox
import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
)
// Client est le client HTTP vers l'API Proxmox VE.
type Client struct {
baseURL string
httpClient *http.Client
token string // Format: "PVEAPIToken=user@realm!tokenid=secret"
}
// NodeStatus représente l'état d'un nœud Proxmox.
type NodeStatus struct {
Node string `json:"node"`
Status string `json:"status"`
CPU float64 `json:"cpu"`
MaxCPU int `json:"maxcpu"`
Mem int64 `json:"mem"`
MaxMem int64 `json:"maxmem"`
Uptime int64 `json:"uptime"`
}
// Resource représente un LXC, une VM ou un autre objet Proxmox.
type Resource struct {
VMID int `json:"vmid"`
Name string `json:"name"`
Node string `json:"node"`
Type string `json:"type"` // "lxc" | "qemu" | "storage" | "node"
Status string `json:"status"` // "running" | "stopped"
CPU float64 `json:"cpu"`
MaxCPU int `json:"maxcpu"`
Mem int64 `json:"mem"`
MaxMem int64 `json:"maxmem"`
Disk int64 `json:"disk"`
MaxDisk int64 `json:"maxdisk"`
Uptime int64 `json:"uptime"`
NetIn int64 `json:"netin"`
NetOut int64 `json:"netout"`
}
// proxmoxResponse est l'enveloppe générique des réponses API Proxmox.
type proxmoxResponse struct {
Data json.RawMessage `json:"data"`
Error string `json:"errors"`
}
// NewClient crée un client Proxmox avec le token API fourni.
// baseURL : ex "https://10.0.0.1:8006"
// token : ex "PVEAPIToken=enzo@pam!panel=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
func NewClient(baseURL, token string) *Client {
return &Client{
baseURL: strings.TrimRight(baseURL, "/"),
token: token,
httpClient: &http.Client{
Timeout: 15 * time.Second,
Transport: &http.Transport{
// Proxmox utilise des certificats auto-signés
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
},
}
}
// GetNodes retourne la liste des nœuds Proxmox.
func (c *Client) GetNodes() ([]NodeStatus, error) {
var nodes []NodeStatus
if err := c.get("/api2/json/nodes", &nodes); err != nil {
return nil, err
}
return nodes, nil
}
// GetResources retourne tous les LXC et VM de l'ensemble du cluster.
// Le paramètre type filtre les résultats ("lxc", "vm", ou "" pour tout).
func (c *Client) GetResources(resourceType string) ([]Resource, error) {
path := "/api2/json/cluster/resources"
if resourceType != "" {
path += "?type=" + resourceType
}
var resources []Resource
if err := c.get(path, &resources); err != nil {
return nil, err
}
return resources, nil
}
// GetLXCList retourne uniquement les conteneurs LXC.
func (c *Client) GetLXCList() ([]Resource, error) {
return c.GetResources("lxc")
}
// GetVMList retourne uniquement les machines virtuelles QEMU.
func (c *Client) GetVMList() ([]Resource, error) {
return c.GetResources("vm")
}
// StartLXC démarre un conteneur LXC.
func (c *Client) StartLXC(node string, vmid int) error {
_, err := c.post(fmt.Sprintf("/api2/json/nodes/%s/lxc/%d/status/start", node, vmid), nil)
return err
}
// StopLXC arrête un conteneur LXC.
func (c *Client) StopLXC(node string, vmid int) error {
_, err := c.post(fmt.Sprintf("/api2/json/nodes/%s/lxc/%d/status/stop", node, vmid), nil)
return err
}
// StartVM démarre une machine virtuelle.
func (c *Client) StartVM(node string, vmid int) error {
_, err := c.post(fmt.Sprintf("/api2/json/nodes/%s/qemu/%d/status/start", node, vmid), nil)
return err
}
// StopVM arrête une machine virtuelle.
func (c *Client) StopVM(node string, vmid int) error {
_, err := c.post(fmt.Sprintf("/api2/json/nodes/%s/qemu/%d/status/stop", node, vmid), nil)
return err
}
// TestConnection vérifie que le token API est valide en récupérant la liste des nœuds.
func (c *Client) TestConnection() error {
_, err := c.GetNodes()
return err
}
// get effectue une requête GET et décode la réponse dans dest.
func (c *Client) get(path string, dest any) error {
req, err := http.NewRequest("GET", c.baseURL+path, nil)
if err != nil {
return err
}
req.Header.Set("Authorization", c.token)
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("requête Proxmox : %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == 401 {
return fmt.Errorf("token Proxmox invalide ou expiré")
}
if resp.StatusCode >= 400 {
return fmt.Errorf("erreur Proxmox API : HTTP %d", resp.StatusCode)
}
return c.decodeResponse(resp.Body, dest)
}
// post effectue une requête POST.
func (c *Client) post(path string, body any) (json.RawMessage, error) {
var reader io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return nil, err
}
reader = strings.NewReader(string(data))
}
req, err := http.NewRequest("POST", c.baseURL+path, reader)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", c.token)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("requête Proxmox : %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == 401 {
return nil, fmt.Errorf("token Proxmox invalide")
}
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("erreur Proxmox API : HTTP %d", resp.StatusCode)
}
var result json.RawMessage
c.decodeResponse(resp.Body, &result)
return result, nil
}
// decodeResponse décode l'enveloppe JSON Proxmox et extrait le champ "data".
func (c *Client) decodeResponse(body io.Reader, dest any) error {
var wrapper proxmoxResponse
if err := json.NewDecoder(body).Decode(&wrapper); err != nil {
return fmt.Errorf("décodage réponse Proxmox : %w", err)
}
if wrapper.Error != "" {
return fmt.Errorf("erreur Proxmox : %s", wrapper.Error)
}
if dest == nil || wrapper.Data == nil {
return nil
}
return json.Unmarshal(wrapper.Data, dest)
}