Files
godoxy-yusing/internal/proxmox
yusing b4646b665f feat(proxmox): add LXC container stats endpoint with streaming support
Implement a new API endpoint to retrieve real-time statistics for Proxmox
LXC containers, similar to `docker stats` functionality.

Changes:
- Add `GET /api/v1/proxmox/stats/:node/:vmid` endpoint with HTTP and WebSocket support
- Implement resource polling loop to cache VM metadata every 3 seconds
- Create `LXCStats()` method with streaming (websocket) and single-shot modes
- Format output as: STATUS|CPU%|MEM USAGE/LIMIT|MEM%|NET I/O|BLOCK I/O
- Add `GetResource()` method for efficient VM resource lookup by kind and ID
- Fix task creation bug using correct client reference

Example response:
  running|31.1%|9.6GiB/20GiB|48.87%|4.7GiB/3.3GiB|25GiB/36GiB
2026-01-25 01:37:13 +08:00
..
2025-04-24 15:02:31 +08:00

Proxmox

The proxmox package provides Proxmox VE integration for GoDoxy, enabling management of Proxmox LXC containers.

Overview

The proxmox package implements Proxmox API client management, node discovery, and LXC container operations including power management and IP address retrieval.

Key Features

  • Proxmox API client management
  • Node discovery and pool management
  • LXC container operations (start, stop, status)
  • IP address retrieval for containers
  • TLS configuration options
  • Token-based authentication

Architecture

graph TD
    A[Proxmox Config] --> B[Create Client]
    B --> C[Connect to API]
    C --> D[Fetch Cluster Info]
    D --> E[Discover Nodes]
    E --> F[Add to Node Pool]

    G[LXC Operations] --> H[Get IPs]
    G --> I[Start Container]
    G --> J[Stop Container]
    G --> K[Check Status]

    subgraph Node Pool
        F --> L[Nodes Map]
        L --> M[Node 1]
        L --> N[Node 2]
        L --> O[Node 3]
    end

Core Components

Config

type Config struct {
    URL       string            `json:"url" validate:"required,url"`
    TokenID   string            `json:"token_id" validate:"required"`
    Secret    strutils.Redacted `json:"secret" validate:"required"`
    NoTLSVerify bool            `json:"no_tls_verify"`

    client *Client
}

Client

type Client struct {
    *proxmox.Client
    proxmox.Cluster
    Version *proxmox.Version
}

Node

type Node struct {
    name   string
    id     string
    client *proxmox.Client
}

var Nodes = pool.New[*Node]("proxmox_nodes")

Public API

Configuration

// Init initializes the Proxmox client.
func (c *Config) Init(ctx context.Context) gperr.Error

// Client returns the Proxmox client.
func (c *Config) Client() *Client

Node Operations

// AvailableNodeNames returns all available node names.
func AvailableNodeNames() string

Usage

Basic Setup

proxmoxCfg := &proxmox.Config{
    URL:         "https://proxmox.example.com:8006",
    TokenID:     "user@pam!token-name",
    Secret:      "your-api-token-secret",
    NoTLSVerify: false,
}

ctx := context.Background()
err := proxmoxCfg.Init(ctx)
if err != nil {
    log.Fatal(err)
}

client := proxmoxCfg.Client()

Node Access

// Get a specific node
node, ok := proxmox.Nodes.Get("pve")
if !ok {
    log.Fatal("Node not found")
}

fmt.Printf("Node: %s (%s)\n", node.Name(), node.Key())

Available Nodes

names := proxmox.AvailableNodeNames()
fmt.Printf("Available nodes: %s\n", names)

LXC Operations

Get Container IPs

func getContainerIPs(ctx context.Context, node *proxmox.Node, vmid int) ([]net.IP, error) {
    var ips []net.IP

    err := node.Get(ctx, "/lxc/"+strconv.Itoa(vmid)+"/config", &config)
    if err != nil {
        return nil, err
    }

    // Parse IP addresses from config
    for _, ip := range config {
        if ipNet := net.ParseCIDR(ip); ipNet != nil {
            ips = append(ips, ipNet.IP)
        }
    }

    return ips, nil
}

Check Container Status

func (node *Node) LXCIsRunning(ctx context.Context, vmid int) (bool, error) {
    var status struct {
        Status string `json:"status"`
    }

    err := node.Get(ctx, "/lxc/"+strconv.Itoa(vmid)+"/status/current", &status)
    if err != nil {
        return false, err
    }

    return status.Status == "running", nil
}

Start Container

func (node *Node) LXCAction(ctx context.Context, vmid int, action string) error {
    return node.Post(ctx,
        "/lxc/"+strconv.Itoa(vmid)+"/status/"+action,
        nil,
        nil,
    )
}

const LXCStart = "start"

Data Flow

sequenceDiagram
    participant Config
    participant Client
    participant NodePool
    participant LXC

    Config->>Client: NewClient(url, options)
    Client->>ProxmoxAPI: GET /cluster/status
    ProxmoxAPI-->>Client: Cluster Info

    Client->>NodePool: Add Nodes
    NodePool->>NodePool: Store in Pool

    participant User
    User->>NodePool: Get Node
    NodePool-->>User: Node

    User->>Node: LXCGetIPs(vmid)
    Node->>ProxmoxAPI: GET /lxc/{vmid}/config
    ProxmoxAPI-->>Node: Config with IPs
    Node-->>User: IP addresses

    User->>Node: LXCAction(vmid, "start")
    Node->>ProxmoxAPI: POST /lxc/{vmid}/status/start
    ProxmoxAPI-->>Node: Success
    Node-->>User: Done

Configuration

YAML Configuration

providers:
  proxmox:
    - url: https://proxmox.example.com:8006
      token_id: user@pam!token-name
      secret: your-api-token-secret
      no_tls_verify: false

TLS Configuration

// With TLS verification (default)
tr := gphttp.NewTransport()

// Without TLS verification (insecure)
tr := gphttp.NewTransportWithTLSConfig(&tls.Config{
    InsecureSkipVerify: true,
})

Node Pool

The package maintains a global node pool:

var Nodes = pool.New[*Node]("proxmox_nodes")

Pool Operations

// Add a node
Nodes.Add(&Node{name: "pve1", id: "node/pve1", client: client})

// Get a node
node, ok := Nodes.Get("pve1")

// Iterate nodes
for _, node := range Nodes.Iter {
    fmt.Printf("Node: %s\n", node.Name())
}

Integration with Route

The proxmox package integrates with the route package for idlewatcher:

// In route validation
if r.Idlewatcher != nil && r.Idlewatcher.Proxmox != nil {
    node := r.Idlewatcher.Proxmox.Node
    vmid := r.Idlewatcher.Proxmox.VMID

    node, ok := proxmox.Nodes.Get(node)
    if !ok {
        return gperr.Errorf("proxmox node %s not found", node)
    }

    // Get container IPs
    ips, err := node.LXCGetIPs(ctx, vmid)
    // ... check reachability
}

Authentication

The package uses API tokens for authentication:

opts := []proxmox.Option{
    proxmox.WithAPIToken(c.TokenID, c.Secret.String()),
    proxmox.WithHTTPClient(&http.Client{
        Transport: tr,
    }),
}

Error Handling

// Timeout handling
if errors.Is(err, context.DeadlineExceeded) {
    return gperr.New("timeout fetching proxmox cluster info")
}

// Connection errors
return gperr.New("failed to fetch proxmox cluster info").With(err)

Performance Considerations

  • Cluster info fetched once on init
  • Nodes cached in pool
  • Per-operation API calls
  • 3-second timeout for initial connection