From 229ae9e454df7472d7740c67f66c4f393a29465e Mon Sep 17 00:00:00 2001 From: sagit Date: Fri, 13 Feb 2026 06:01:52 +0000 Subject: [PATCH] feat(backend): route node commands to remote panels Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus --- .../internal/http/handler/control_plane.go | 50 ++++++++++++++++++- go-backend/internal/http/handler/mutations.go | 2 +- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/go-backend/internal/http/handler/control_plane.go b/go-backend/internal/http/handler/control_plane.go index 0e3d084..d61a86a 100644 --- a/go-backend/internal/http/handler/control_plane.go +++ b/go-backend/internal/http/handler/control_plane.go @@ -457,7 +457,17 @@ func (h *Handler) applyNodeProtocolChange(nodeID int64, httpVal, tlsVal, socksVa } func (h *Handler) sendNodeCommand(nodeID int64, commandType string, data interface{}, tolerateExists bool, tolerateNotFound bool) (ws.CommandResult, error) { - result, err := h.wsServer.SendCommand(nodeID, commandType, data, 12*time.Second) + var ( + result ws.CommandResult + err error + ) + + node, nodeErr := h.getNodeRecord(nodeID) + if nodeErr == nil && node != nil && node.IsRemote == 1 { + result, err = h.sendRemoteNodeCommand(node, commandType, data) + } else { + result, err = h.wsServer.SendCommand(nodeID, commandType, data, 12*time.Second) + } if err == nil { return result, nil } @@ -475,6 +485,44 @@ func (h *Handler) sendNodeCommand(nodeID int64, commandType string, data interfa return result, err } +func (h *Handler) sendRemoteNodeCommand(node *nodeRecord, commandType string, data interface{}) (ws.CommandResult, error) { + if node == nil { + return ws.CommandResult{}, errors.New("节点不存在") + } + remoteURL := strings.TrimSpace(node.RemoteURL) + remoteToken := strings.TrimSpace(node.RemoteToken) + if remoteURL == "" || remoteToken == "" { + return ws.CommandResult{}, errors.New("远程节点缺少共享配置") + } + + fc := client.NewFederationClient() + res, err := fc.Command(remoteURL, remoteToken, h.federationLocalDomain(), client.RuntimeNodeCommandRequest{ + CommandType: commandType, + Data: data, + }) + if err != nil { + return ws.CommandResult{}, err + } + if res == nil { + return ws.CommandResult{}, errors.New("远程节点未返回命令结果") + } + + result := ws.CommandResult{ + Type: res.Type, + Success: res.Success, + Message: res.Message, + Data: res.Data, + } + if !result.Success { + msg := strings.TrimSpace(result.Message) + if msg == "" { + msg = "命令执行失败" + } + return result, errors.New(msg) + } + return result, nil +} + func (h *Handler) diagnoseForwardRuntime(forward *forwardRecord) (map[string]interface{}, error) { if forward == nil { return nil, errForwardNotFound diff --git a/go-backend/internal/http/handler/mutations.go b/go-backend/internal/http/handler/mutations.go index cff0744..dfa9c3b 100644 --- a/go-backend/internal/http/handler/mutations.go +++ b/go-backend/internal/http/handler/mutations.go @@ -2061,7 +2061,7 @@ func (h *Handler) prepareTunnelCreateState(tx *store.Tx, req map[string]interfac } return nil, err } - if node.Status != 1 { + if node.IsRemote != 1 && node.Status != 1 { return nil, errors.New("部分节点不在线") } state.Nodes[nodeID] = node