From b835daff1aaa6650744dd2347ee196a0d6c9a2e8 Mon Sep 17 00:00:00 2001 From: openclaw Date: Tue, 21 Apr 2026 02:28:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(pve):=20=E4=BF=AE=E5=A4=8DPVE=20API?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E5=92=8CVM=E7=8A=B6=E6=80=81=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PVE ticket字段名是'ticket'不是'PVEAuthCookie' - Cookie header格式应为 PVEAuthCookie= - VM状态API路径改为 /status/current - VM列表API从cluster级改为node级 /nodes/{node}/qemu - vmid从int解析再转string [砚砚/K2.6🐾] --- server/services/pve.go | 51 +++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/server/services/pve.go b/server/services/pve.go index c296c31..b532cc7 100644 --- a/server/services/pve.go +++ b/server/services/pve.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/cookiejar" "net/url" + "strconv" "strings" "time" @@ -81,16 +82,21 @@ func (c *PVEClient) Login() error { var result struct { Data struct { CSRFToken string `json:"CSRFPreventionToken"` - Cookie string `json:"PVEAuthCookie"` + Ticket string `json:"ticket"` } `json:"data"` } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return err + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("read login response failed: %v", err) + } + + if err := json.Unmarshal(body, &result); err != nil { + return fmt.Errorf("decode login response failed: %v, body: %s", err, string(body)) } c.CSRFToken = result.Data.CSRFToken - c.Cookie = result.Data.Cookie + c.Cookie = "PVEAuthCookie=" + result.Data.Ticket return nil } @@ -103,7 +109,7 @@ func (c *PVEClient) GetVMStatus(vmid string) (*models.PVEVMStatus, error) { } // 获取 node 名称(通常是 pve) - statusURL := c.baseURL() + "/api2/json/nodes/" + c.NodeName + "/qemu/" + vmid + "/status" + statusURL := c.baseURL() + "/api2/json/nodes/" + c.NodeName + "/qemu/" + vmid + "/status/current" req, err := http.NewRequest("GET", statusURL, nil) if err != nil { @@ -246,7 +252,7 @@ func (c *PVEClient) GetVMList() ([]struct { } } - listURL := c.baseURL() + "/api2/json/cluster/resources?type=vm" + listURL := c.baseURL() + "/api2/json/nodes/" + c.NodeName + "/qemu" req, err := http.NewRequest("GET", listURL, nil) if err != nil { @@ -262,22 +268,45 @@ func (c *PVEClient) GetVMList() ([]struct { defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("get vm list failed, status: %d", resp.StatusCode) + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("get vm list failed, status: %d, body: %s", resp.StatusCode, string(body)) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read vm list body failed: %v", err) } var result struct { Data []struct { - VMID string `json:"vmid"` + VMID int `json:"vmid"` Name string `json:"name"` Status string `json:"status"` } `json:"data"` } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return nil, err + if err := json.Unmarshal(body, &result); err != nil { + return nil, fmt.Errorf("decode vm list failed: %v, body: %s", err, string(body)) } - return result.Data, nil + // vmid 是 int,转换为 string + vms := make([]struct { + VMID string `json:"vmid"` + Name string `json:"name"` + Status string `json:"status"` + }, len(result.Data)) + for i, vm := range result.Data { + vms[i] = struct { + VMID string `json:"vmid"` + Name string `json:"name"` + Status string `json:"status"` + }{ + VMID: strconv.Itoa(vm.VMID), + Name: vm.Name, + Status: vm.Status, + } + } + return vms, nil } // PVEHostService PVE 主机服务