update
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 38s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 59s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 1m8s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m17s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 53s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 1m14s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 2m8s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m10s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 57s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 1m11s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 55s

This commit is contained in:
2026-01-01 14:43:33 +08:00
parent 76fde41e48
commit 0c00a9ffdc
13 changed files with 1096 additions and 578 deletions

View File

@@ -221,10 +221,16 @@ func (c *Client) handleStream(stream net.Conn) {
c.handlePluginConfig(msg)
case protocol.MsgTypeClientPluginStart:
c.handleClientPluginStart(stream, msg)
case protocol.MsgTypeClientPluginStop:
c.handleClientPluginStop(stream, msg)
case protocol.MsgTypeClientPluginConn:
c.handleClientPluginConn(stream, msg)
case protocol.MsgTypeJSPluginInstall:
c.handleJSPluginInstall(stream, msg)
case protocol.MsgTypeClientRestart:
c.handleClientRestart(stream, msg)
case protocol.MsgTypePluginConfigUpdate:
c.handlePluginConfigUpdate(stream, msg)
}
}
@@ -609,3 +615,126 @@ func (c *Client) verifyJSPluginSignature(pluginName, source, signature string) e
return nil
}
// handleClientPluginStop 处理客户端插件停止请求
func (c *Client) handleClientPluginStop(stream net.Conn, msg *protocol.Message) {
defer stream.Close()
var req protocol.ClientPluginStopRequest
if err := msg.ParsePayload(&req); err != nil {
c.sendPluginStatus(stream, req.PluginName, req.RuleName, true, "", err.Error())
return
}
key := req.PluginName + ":" + req.RuleName
c.pluginMu.Lock()
handler, ok := c.runningPlugins[key]
if ok {
if err := handler.Stop(); err != nil {
log.Printf("[Client] Plugin %s stop error: %v", key, err)
}
delete(c.runningPlugins, key)
}
c.pluginMu.Unlock()
log.Printf("[Client] Plugin %s stopped", key)
c.sendPluginStatus(stream, req.PluginName, req.RuleName, false, "", "")
}
// handleClientRestart 处理客户端重启请求
func (c *Client) handleClientRestart(stream net.Conn, msg *protocol.Message) {
defer stream.Close()
var req protocol.ClientRestartRequest
msg.ParsePayload(&req)
log.Printf("[Client] Restart requested: %s", req.Reason)
// 发送响应
resp := protocol.ClientRestartResponse{
Success: true,
Message: "restarting",
}
respMsg, _ := protocol.NewMessage(protocol.MsgTypeClientRestart, resp)
protocol.WriteMessage(stream, respMsg)
// 停止所有运行中的插件
c.pluginMu.Lock()
for key, handler := range c.runningPlugins {
log.Printf("[Client] Stopping plugin %s for restart", key)
handler.Stop()
}
c.runningPlugins = make(map[string]plugin.ClientPlugin)
c.pluginMu.Unlock()
// 关闭会话(会触发重连)
if c.session != nil {
c.session.Close()
}
}
// handlePluginConfigUpdate 处理插件配置更新请求
func (c *Client) handlePluginConfigUpdate(stream net.Conn, msg *protocol.Message) {
defer stream.Close()
var req protocol.PluginConfigUpdateRequest
if err := msg.ParsePayload(&req); err != nil {
c.sendPluginConfigUpdateResult(stream, req.PluginName, req.RuleName, false, err.Error())
return
}
key := req.PluginName + ":" + req.RuleName
log.Printf("[Client] Config update for plugin %s", key)
c.pluginMu.RLock()
handler, ok := c.runningPlugins[key]
c.pluginMu.RUnlock()
if !ok {
c.sendPluginConfigUpdateResult(stream, req.PluginName, req.RuleName, false, "plugin not running")
return
}
if req.Restart {
// 停止并重启插件
c.pluginMu.Lock()
if err := handler.Stop(); err != nil {
log.Printf("[Client] Plugin %s stop error: %v", key, err)
}
delete(c.runningPlugins, key)
c.pluginMu.Unlock()
// 重新初始化和启动
if err := handler.Init(req.Config); err != nil {
c.sendPluginConfigUpdateResult(stream, req.PluginName, req.RuleName, false, err.Error())
return
}
localAddr, err := handler.Start()
if err != nil {
c.sendPluginConfigUpdateResult(stream, req.PluginName, req.RuleName, false, err.Error())
return
}
c.pluginMu.Lock()
c.runningPlugins[key] = handler
c.pluginMu.Unlock()
log.Printf("[Client] Plugin %s restarted at %s with new config", key, localAddr)
}
c.sendPluginConfigUpdateResult(stream, req.PluginName, req.RuleName, true, "")
}
// sendPluginConfigUpdateResult 发送插件配置更新结果
func (c *Client) sendPluginConfigUpdateResult(stream net.Conn, pluginName, ruleName string, success bool, errMsg string) {
result := protocol.PluginConfigUpdateResponse{
PluginName: pluginName,
RuleName: ruleName,
Success: success,
Error: errMsg,
}
msg, _ := protocol.NewMessage(protocol.MsgTypePluginConfigUpdate, result)
protocol.WriteMessage(stream, msg)
}

View File

@@ -18,21 +18,6 @@ type Client struct {
Plugins []ClientPlugin `json:"plugins,omitempty"` // 已安装的插件
}
// PluginData 插件数据
type PluginData struct {
Name string `json:"name"`
Version string `json:"version"`
Type string `json:"type"`
Source string `json:"source"`
Description string `json:"description"`
Author string `json:"author"`
Icon string `json:"icon"`
Checksum string `json:"checksum"`
Size int64 `json:"size"`
Enabled bool `json:"enabled"`
WASMData []byte `json:"-"`
}
// JSPlugin JS 插件数据
type JSPlugin struct {
Name string `json:"name"`
@@ -40,6 +25,7 @@ type JSPlugin struct {
Signature string `json:"signature"` // 官方签名 (Base64)
Description string `json:"description"`
Author string `json:"author"`
Version string `json:"version,omitempty"`
AutoPush []string `json:"auto_push"`
Config map[string]string `json:"config"`
AutoStart bool `json:"auto_start"`
@@ -58,16 +44,6 @@ type ClientStore interface {
Close() error
}
// PluginStore 插件存储接口
type PluginStore interface {
GetAllPlugins() ([]PluginData, error)
GetPlugin(name string) (*PluginData, error)
SavePlugin(p *PluginData) error
DeletePlugin(name string) error
SetPluginEnabled(name string, enabled bool) error
GetPluginWASM(name string) ([]byte, error)
}
// JSPluginStore JS 插件存储接口
type JSPluginStore interface {
GetAllJSPlugins() ([]JSPlugin, error)
@@ -75,12 +51,12 @@ type JSPluginStore interface {
SaveJSPlugin(p *JSPlugin) error
DeleteJSPlugin(name string) error
SetJSPluginEnabled(name string, enabled bool) error
UpdateJSPluginConfig(name string, config map[string]string) error
}
// Store 统一存储接口
type Store interface {
ClientStore
PluginStore
JSPluginStore
Close() error
}

View File

@@ -52,41 +52,21 @@ func (s *SQLiteStore) init() error {
// 迁移:添加 plugins 列
s.db.Exec(`ALTER TABLE clients ADD COLUMN plugins TEXT NOT NULL DEFAULT '[]'`)
// 创建插件表
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS plugins (
name TEXT PRIMARY KEY,
version TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'proxy',
source TEXT NOT NULL DEFAULT 'wasm',
description TEXT,
author TEXT,
icon TEXT,
checksum TEXT,
size INTEGER DEFAULT 0,
enabled INTEGER DEFAULT 1,
wasm_data BLOB
)
`)
if err != nil {
return err
}
// 迁移:添加 icon 列
s.db.Exec(`ALTER TABLE plugins ADD COLUMN icon TEXT`)
// 创建 JS 插件表
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS js_plugins (
name TEXT PRIMARY KEY,
source TEXT NOT NULL,
signature TEXT NOT NULL DEFAULT '',
description TEXT,
author TEXT,
version TEXT DEFAULT '',
auto_push TEXT NOT NULL DEFAULT '[]',
config TEXT NOT NULL DEFAULT '',
config TEXT NOT NULL DEFAULT '{}',
auto_start INTEGER DEFAULT 1,
enabled INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`)
if err != nil {
@@ -95,6 +75,10 @@ func (s *SQLiteStore) init() error {
// 迁移:添加 signature 列
s.db.Exec(`ALTER TABLE js_plugins ADD COLUMN signature TEXT NOT NULL DEFAULT ''`)
// 迁移:添加 version 列
s.db.Exec(`ALTER TABLE js_plugins ADD COLUMN version TEXT DEFAULT ''`)
// 迁移:添加 updated_at 列
s.db.Exec(`ALTER TABLE js_plugins ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP`)
return nil
}
@@ -217,107 +201,6 @@ func (s *SQLiteStore) GetClientRules(id string) ([]protocol.ProxyRule, error) {
return c.Rules, nil
}
// ========== 插件存储方法 ==========
// GetAllPlugins 获取所有插件
func (s *SQLiteStore) GetAllPlugins() ([]PluginData, error) {
s.mu.RLock()
defer s.mu.RUnlock()
rows, err := s.db.Query(`
SELECT name, version, type, source, description, author, icon, checksum, size, enabled
FROM plugins
`)
if err != nil {
return nil, err
}
defer rows.Close()
var plugins []PluginData
for rows.Next() {
var p PluginData
var enabled int
var icon sql.NullString
err := rows.Scan(&p.Name, &p.Version, &p.Type, &p.Source,
&p.Description, &p.Author, &icon, &p.Checksum, &p.Size, &enabled)
if err != nil {
return nil, err
}
p.Enabled = enabled == 1
p.Icon = icon.String
plugins = append(plugins, p)
}
return plugins, nil
}
// GetPlugin 获取单个插件
func (s *SQLiteStore) GetPlugin(name string) (*PluginData, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var p PluginData
var enabled int
var icon sql.NullString
err := s.db.QueryRow(`
SELECT name, version, type, source, description, author, icon, checksum, size, enabled
FROM plugins WHERE name = ?
`, name).Scan(&p.Name, &p.Version, &p.Type, &p.Source,
&p.Description, &p.Author, &icon, &p.Checksum, &p.Size, &enabled)
if err != nil {
return nil, err
}
p.Enabled = enabled == 1
p.Icon = icon.String
return &p, nil
}
// SavePlugin 保存插件
func (s *SQLiteStore) SavePlugin(p *PluginData) error {
s.mu.Lock()
defer s.mu.Unlock()
enabled := 0
if p.Enabled {
enabled = 1
}
_, err := s.db.Exec(`
INSERT OR REPLACE INTO plugins
(name, version, type, source, description, author, icon, checksum, size, enabled, wasm_data)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, p.Name, p.Version, p.Type, p.Source, p.Description, p.Author,
p.Icon, p.Checksum, p.Size, enabled, p.WASMData)
return err
}
// DeletePlugin 删除插件
func (s *SQLiteStore) DeletePlugin(name string) error {
s.mu.Lock()
defer s.mu.Unlock()
_, err := s.db.Exec(`DELETE FROM plugins WHERE name = ?`, name)
return err
}
// SetPluginEnabled 设置插件启用状态
func (s *SQLiteStore) SetPluginEnabled(name string, enabled bool) error {
s.mu.Lock()
defer s.mu.Unlock()
val := 0
if enabled {
val = 1
}
_, err := s.db.Exec(`UPDATE plugins SET enabled = ? WHERE name = ?`, val, name)
return err
}
// GetPluginWASM 获取插件 WASM 数据
func (s *SQLiteStore) GetPluginWASM(name string) ([]byte, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var data []byte
err := s.db.QueryRow(`SELECT wasm_data FROM plugins WHERE name = ?`, name).Scan(&data)
return data, err
}
// ========== JS 插件存储方法 ==========
// GetAllJSPlugins 获取所有 JS 插件
@@ -326,7 +209,7 @@ func (s *SQLiteStore) GetAllJSPlugins() ([]JSPlugin, error) {
defer s.mu.RUnlock()
rows, err := s.db.Query(`
SELECT name, source, signature, description, author, auto_push, config, auto_start, enabled
SELECT name, source, signature, description, author, version, auto_push, config, auto_start, enabled
FROM js_plugins
`)
if err != nil {
@@ -338,12 +221,14 @@ func (s *SQLiteStore) GetAllJSPlugins() ([]JSPlugin, error) {
for rows.Next() {
var p JSPlugin
var autoPushJSON, configJSON string
var version sql.NullString
var autoStart, enabled int
err := rows.Scan(&p.Name, &p.Source, &p.Signature, &p.Description, &p.Author,
&autoPushJSON, &configJSON, &autoStart, &enabled)
&version, &autoPushJSON, &configJSON, &autoStart, &enabled)
if err != nil {
return nil, err
}
p.Version = version.String
json.Unmarshal([]byte(autoPushJSON), &p.AutoPush)
json.Unmarshal([]byte(configJSON), &p.Config)
p.AutoStart = autoStart == 1
@@ -360,15 +245,17 @@ func (s *SQLiteStore) GetJSPlugin(name string) (*JSPlugin, error) {
var p JSPlugin
var autoPushJSON, configJSON string
var version sql.NullString
var autoStart, enabled int
err := s.db.QueryRow(`
SELECT name, source, signature, description, author, auto_push, config, auto_start, enabled
SELECT name, source, signature, description, author, version, auto_push, config, auto_start, enabled
FROM js_plugins WHERE name = ?
`, name).Scan(&p.Name, &p.Source, &p.Signature, &p.Description, &p.Author,
&autoPushJSON, &configJSON, &autoStart, &enabled)
&version, &autoPushJSON, &configJSON, &autoStart, &enabled)
if err != nil {
return nil, err
}
p.Version = version.String
json.Unmarshal([]byte(autoPushJSON), &p.AutoPush)
json.Unmarshal([]byte(configJSON), &p.Config)
p.AutoStart = autoStart == 1
@@ -393,9 +280,9 @@ func (s *SQLiteStore) SaveJSPlugin(p *JSPlugin) error {
_, err := s.db.Exec(`
INSERT OR REPLACE INTO js_plugins
(name, source, signature, description, author, auto_push, config, auto_start, enabled)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`, p.Name, p.Source, p.Signature, p.Description, p.Author,
(name, source, signature, description, author, version, auto_push, config, auto_start, enabled, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
`, p.Name, p.Source, p.Signature, p.Description, p.Author, p.Version,
string(autoPushJSON), string(configJSON), autoStart, enabled)
return err
}
@@ -416,6 +303,15 @@ func (s *SQLiteStore) SetJSPluginEnabled(name string, enabled bool) error {
if enabled {
val = 1
}
_, err := s.db.Exec(`UPDATE js_plugins SET enabled = ? WHERE name = ?`, val, name)
_, err := s.db.Exec(`UPDATE js_plugins SET enabled = ?, updated_at = CURRENT_TIMESTAMP WHERE name = ?`, val, name)
return err
}
// UpdateJSPluginConfig 更新 JS 插件配置
func (s *SQLiteStore) UpdateJSPluginConfig(name string, config map[string]string) error {
s.mu.Lock()
defer s.mu.Unlock()
configJSON, _ := json.Marshal(config)
_, err := s.db.Exec(`UPDATE js_plugins SET config = ?, updated_at = CURRENT_TIMESTAMP WHERE name = ?`, string(configJSON), name)
return err
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/gotunnel/internal/server/config"
"github.com/gotunnel/internal/server/db"
"github.com/gotunnel/pkg/plugin"
"github.com/gotunnel/pkg/protocol"
)
@@ -53,6 +54,11 @@ type ServerInterface interface {
SyncPluginConfigToClient(clientID string, pluginName string, config map[string]string) error
// JS 插件
InstallJSPluginToClient(clientID string, req JSPluginInstallRequest) error
// 客户端/插件重启
RestartClient(clientID string) error
StopClientPlugin(clientID, pluginName, ruleName string) error
RestartClientPlugin(clientID, pluginName, ruleName string) error
UpdateClientPluginConfig(clientID, pluginName, ruleName string, config map[string]string, restart bool) error
}
// JSPluginInstallRequest JS 插件安装请求
@@ -135,6 +141,7 @@ func RegisterRoutes(r *Router, app AppInterface) {
api.HandleFunc("/client-plugin/", h.handleClientPlugin)
api.HandleFunc("/js-plugin/", h.handleJSPlugin)
api.HandleFunc("/js-plugins", h.handleJSPlugins)
api.HandleFunc("/rule-schemas", h.handleRuleSchemas)
}
func (h *APIHandler) handleStatus(rw http.ResponseWriter, r *http.Request) {
@@ -247,6 +254,22 @@ func (h *APIHandler) handleClient(rw http.ResponseWriter, r *http.Request) {
case "install-plugins":
h.installPluginsToClient(rw, r, clientID)
return
case "restart":
h.restartClient(rw, r, clientID)
return
}
}
// 检查是否是插件操作: /api/client/{id}/plugin/{name}/{action}
if len(parts) >= 2 && parts[1] == "plugin" {
// 重新解析路径
remaining := clientID[len(parts[0])+1:] // "plugin/xxx/action"
pluginParts := splitPath(remaining[7:]) // 跳过 "plugin/"
if len(pluginParts) >= 2 {
pluginName := pluginParts[0]
pluginAction := pluginParts[1]
h.handleClientPluginAction(rw, r, parts[0], pluginName, pluginAction)
return
}
}
@@ -1064,3 +1087,105 @@ func (h *APIHandler) pushJSPluginToClient(rw http.ResponseWriter, pluginName, cl
h.jsonResponse(rw, map[string]string{"status": "ok", "plugin": pluginName, "client": clientID})
}
// handleRuleSchemas 返回所有协议类型的配置模式
func (h *APIHandler) handleRuleSchemas(rw http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// 获取内置协议模式
schemas := make(map[string]RuleSchema)
for name, schema := range plugin.BuiltinRuleSchemas() {
schemas[name] = RuleSchema{
NeedsLocalAddr: schema.NeedsLocalAddr,
ExtraFields: convertConfigFields(schema.ExtraFields),
}
}
// 添加已注册插件的模式
plugins := h.server.GetPluginList()
for _, p := range plugins {
if p.RuleSchema != nil {
schemas[p.Name] = *p.RuleSchema
}
}
h.jsonResponse(rw, schemas)
}
// convertConfigFields 将 plugin.ConfigField 转换为 router.ConfigField
func convertConfigFields(fields []plugin.ConfigField) []ConfigField {
result := make([]ConfigField, len(fields))
for i, f := range fields {
result[i] = ConfigField{
Key: f.Key,
Label: f.Label,
Type: string(f.Type),
Default: f.Default,
Required: f.Required,
Options: f.Options,
Description: f.Description,
}
}
return result
}
// restartClient 重启客户端
func (h *APIHandler) restartClient(rw http.ResponseWriter, r *http.Request, clientID string) {
if r.Method != http.MethodPost {
http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed)
return
}
if err := h.server.RestartClient(clientID); err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
h.jsonResponse(rw, map[string]string{"status": "ok", "message": "client restart initiated"})
}
// handleClientPluginAction 处理客户端插件操作
func (h *APIHandler) handleClientPluginAction(rw http.ResponseWriter, r *http.Request, clientID, pluginName, action string) {
if r.Method != http.MethodPost && r.Method != http.MethodPut {
http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// 获取规则名称(从请求体或使用插件名作为默认值)
var req struct {
RuleName string `json:"rule_name"`
Config map[string]string `json:"config"`
Restart bool `json:"restart"`
}
json.NewDecoder(r.Body).Decode(&req)
if req.RuleName == "" {
req.RuleName = pluginName
}
var err error
switch action {
case "stop":
err = h.server.StopClientPlugin(clientID, pluginName, req.RuleName)
case "restart":
err = h.server.RestartClientPlugin(clientID, pluginName, req.RuleName)
case "config":
if req.Config == nil {
http.Error(rw, "config required", http.StatusBadRequest)
return
}
err = h.server.UpdateClientPluginConfig(clientID, pluginName, req.RuleName, req.Config, req.Restart)
default:
http.Error(rw, "unknown action", http.StatusBadRequest)
return
}
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
h.jsonResponse(rw, map[string]string{"status": "ok", "action": action, "plugin": pluginName})
}

View File

@@ -1146,3 +1146,163 @@ func (s *Server) shouldPushToClient(autoPush []string, clientID string) bool {
}
return false
}
// RestartClient 重启客户端(通过断开连接,让客户端自动重连)
func (s *Server) RestartClient(clientID string) error {
s.mu.RLock()
cs, ok := s.clients[clientID]
s.mu.RUnlock()
if !ok {
return fmt.Errorf("client %s not found or not online", clientID)
}
// 发送重启消息
stream, err := cs.Session.Open()
if err != nil {
return err
}
req := protocol.ClientRestartRequest{
Reason: "server requested restart",
}
msg, _ := protocol.NewMessage(protocol.MsgTypeClientRestart, req)
protocol.WriteMessage(stream, msg)
stream.Close()
// 等待一小段时间后断开连接
time.AfterFunc(100*time.Millisecond, func() {
cs.Session.Close()
})
log.Printf("[Server] Restart initiated for client %s", clientID)
return nil
}
// StopClientPlugin 停止客户端插件
func (s *Server) StopClientPlugin(clientID, pluginName, ruleName string) error {
s.mu.RLock()
cs, ok := s.clients[clientID]
s.mu.RUnlock()
if !ok {
return fmt.Errorf("client %s not found or not online", clientID)
}
return s.sendClientPluginStop(cs.Session, pluginName, ruleName)
}
// sendClientPluginStop 发送客户端插件停止命令
func (s *Server) sendClientPluginStop(session *yamux.Session, pluginName, ruleName string) error {
stream, err := session.Open()
if err != nil {
return err
}
defer stream.Close()
req := protocol.ClientPluginStopRequest{
PluginName: pluginName,
RuleName: ruleName,
}
msg, err := protocol.NewMessage(protocol.MsgTypeClientPluginStop, req)
if err != nil {
return err
}
if err := protocol.WriteMessage(stream, msg); err != nil {
return err
}
// 等待响应
resp, err := protocol.ReadMessage(stream)
if err != nil {
return err
}
if resp.Type != protocol.MsgTypeClientPluginStatus {
return fmt.Errorf("unexpected response type: %d", resp.Type)
}
var status protocol.ClientPluginStatusResponse
if err := resp.ParsePayload(&status); err != nil {
return err
}
if status.Running {
return fmt.Errorf("plugin still running: %s", status.Error)
}
return nil
}
// RestartClientPlugin 重启客户端插件
func (s *Server) RestartClientPlugin(clientID, pluginName, ruleName string) error {
s.mu.RLock()
cs, ok := s.clients[clientID]
s.mu.RUnlock()
if !ok {
return fmt.Errorf("client %s not found or not online", clientID)
}
// 查找规则
var rule *protocol.ProxyRule
for _, r := range cs.Rules {
if r.Name == ruleName && r.Type == pluginName {
rule = &r
break
}
}
if rule == nil {
return fmt.Errorf("rule %s not found for plugin %s", ruleName, pluginName)
}
// 先停止
if err := s.sendClientPluginStop(cs.Session, pluginName, ruleName); err != nil {
log.Printf("[Server] Stop plugin warning: %v", err)
}
// 再启动
return s.sendClientPluginStart(cs.Session, *rule)
}
// UpdateClientPluginConfig 更新客户端插件配置
func (s *Server) UpdateClientPluginConfig(clientID, pluginName, ruleName string, config map[string]string, restart bool) error {
s.mu.RLock()
cs, ok := s.clients[clientID]
s.mu.RUnlock()
if !ok {
return fmt.Errorf("client %s not found or not online", clientID)
}
// 发送配置更新消息
stream, err := cs.Session.Open()
if err != nil {
return err
}
defer stream.Close()
req := protocol.PluginConfigUpdateRequest{
PluginName: pluginName,
RuleName: ruleName,
Config: config,
Restart: restart,
}
msg, _ := protocol.NewMessage(protocol.MsgTypePluginConfigUpdate, req)
if err := protocol.WriteMessage(stream, msg); err != nil {
return err
}
// 等待响应
resp, err := protocol.ReadMessage(stream)
if err != nil {
return err
}
var result protocol.PluginConfigUpdateResponse
if err := resp.ParsePayload(&result); err != nil {
return err
}
if !result.Success {
return fmt.Errorf("config update failed: %s", result.Error)
}
return nil
}