package proxmox import "encoding/json" // Types mirror the exact JSON shapes captured from the live demo host // (demo-felhom, PVE 9.2.2, 2026-06-08) via `pvesh get ... --output-format json`. // Decoding ignores unknown fields, so we depend only on the fields we use. // Version is GET /version. type Version struct { Release string `json:"release"` // "9.2" RepoID string `json:"repoid"` Version string `json:"version"` // "9.2.2" } // Node is one entry of GET /nodes. type Node struct { Node string `json:"node"` // node name, e.g. "demo-felhom" Status string `json:"status"` // "online" CPU float64 `json:"cpu"` // load fraction 0..1 MaxCPU int `json:"maxcpu"` Mem int64 `json:"mem"` MaxMem int64 `json:"maxmem"` Disk int64 `json:"disk"` MaxDisk int64 `json:"maxdisk"` Uptime int64 `json:"uptime"` SSLFingerprint string `json:"ssl_fingerprint"` } // NodeStatus is GET /nodes/{node}/status (host metrics; needs Sys.Audit). type NodeStatus struct { CPU float64 `json:"cpu"` // load fraction 0..1 Uptime int64 `json:"uptime"` LoadAvg []string `json:"loadavg"` // 1/5/15-min, as strings in the API PVEVersion string `json:"pveversion"` KVersion string `json:"kversion"` Memory struct { Total int64 `json:"total"` Used int64 `json:"used"` Free int64 `json:"free"` Available int64 `json:"available"` } `json:"memory"` RootFS struct { Total int64 `json:"total"` Used int64 `json:"used"` Free int64 `json:"free"` Avail int64 `json:"avail"` } `json:"rootfs"` Swap struct { Total int64 `json:"total"` Used int64 `json:"used"` Free int64 `json:"free"` } `json:"swap"` CPUInfo struct { Cores int `json:"cores"` CPUs int `json:"cpus"` Sockets int `json:"sockets"` Model string `json:"model"` } `json:"cpuinfo"` } // Guest is one entry of GET /nodes/{node}/lxc and the body of // GET /nodes/{node}/lxc/{vmid}/status/current. The status/current response has no // vmid field (it is in the path), so callers set VMID from the request argument. type Guest struct { VMID int `json:"vmid"` Name string `json:"name"` Status string `json:"status"` // "running" | "stopped" Type string `json:"type"` // "lxc" CPUs int `json:"cpus"` CPU float64 `json:"cpu"` Mem int64 `json:"mem"` MaxMem int64 `json:"maxmem"` Disk int64 `json:"disk"` MaxDisk int64 `json:"maxdisk"` Uptime int64 `json:"uptime"` } // GuestConfig is GET /nodes/{node}/lxc/{vmid}/config. The config surface is // dynamic (net0..netN, mp0..mpN, unusedN), so known fields are typed and the full // raw map is preserved in Extra for the dynamic ones. type GuestConfig struct { Hostname string `json:"hostname"` Arch string `json:"arch"` Cores int `json:"cores"` Memory int64 `json:"memory"` Swap int64 `json:"swap"` OSType string `json:"ostype"` RootFS string `json:"rootfs"` Features string `json:"features"` // e.g. "nesting=1,keyctl=1" Unprivileged int `json:"unprivileged"` // 1 if unprivileged Digest string `json:"digest"` // Extra holds every field as raw JSON, including the dynamic netN/mpN/unusedN // keys not promoted above. Extra map[string]json.RawMessage `json:"-"` } // UnmarshalJSON fills both the typed known fields and the raw Extra map. func (g *GuestConfig) UnmarshalJSON(b []byte) error { type alias GuestConfig // avoid recursion var a alias if err := json.Unmarshal(b, &a); err != nil { return err } *g = GuestConfig(a) return json.Unmarshal(b, &g.Extra) } // MountPoints returns the mpN entries (e.g. "mp0" -> "local-lvm:1,mp=/mnt/mp1,backup=0") // pulled from Extra. Relevant for later slices' bulk-volume placement. func (g *GuestConfig) MountPoints() map[string]string { return g.prefixed("mp") } // Nets returns the netN entries from Extra. func (g *GuestConfig) Nets() map[string]string { return g.prefixed("net") } func (g *GuestConfig) prefixed(prefix string) map[string]string { out := map[string]string{} for k, raw := range g.Extra { if len(k) <= len(prefix) || k[:len(prefix)] != prefix { continue } // require the suffix to be a digit (mp0, net0 — not "memory") if c := k[len(prefix)]; c < '0' || c > '9' { continue } var s string if json.Unmarshal(raw, &s) == nil { out[k] = s } } return out } // Storage is one entry of GET /storage (cluster) and GET /nodes/{node}/storage // (the latter adds usage fields). Unused fields stay zero. type Storage struct { Storage string `json:"storage"` Type string `json:"type"` // "dir" | "lvmthin" | "nfs" | "cifs" | "pbs" Content string `json:"content"` // comma list, e.g. "vztmpl,backup,iso,import" Path string `json:"path,omitempty"` Total int64 `json:"total,omitempty"` Used int64 `json:"used,omitempty"` Avail int64 `json:"avail,omitempty"` Active int `json:"active,omitempty"` Enabled int `json:"enabled,omitempty"` Shared int `json:"shared,omitempty"` UsedFraction float64 `json:"used_fraction,omitempty"` } // StorageContent is one entry of GET /nodes/{node}/storage/{store}/content // (e.g. vzdump archives, CT templates, guest volumes). type StorageContent struct { VolID string `json:"volid"` // e.g. "local:backup/vzdump-lxc-9001-...tar.zst" Content string `json:"content"` Format string `json:"format"` Size int64 `json:"size"` CTime int64 `json:"ctime"` VMID int `json:"vmid,omitempty"` }