update
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 48s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 48s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 1m47s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 57s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 49s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m5s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 50s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 45s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 51s
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 48s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 48s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 1m47s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 57s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 49s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m5s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 50s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 45s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 51s
This commit is contained in:
@@ -22,7 +22,6 @@ func NewManager() (*Manager, error) {
|
||||
registry: registry,
|
||||
}
|
||||
|
||||
// 注册内置 plugins
|
||||
if err := m.registerBuiltins(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -32,22 +31,21 @@ func NewManager() (*Manager, error) {
|
||||
|
||||
// registerBuiltins 注册内置 plugins
|
||||
func (m *Manager) registerBuiltins() error {
|
||||
// 注册服务端插件
|
||||
if err := m.registry.RegisterAll(builtin.GetAll()); err != nil {
|
||||
return err
|
||||
}
|
||||
// 注册客户端插件
|
||||
for _, h := range builtin.GetAllClientPlugins() {
|
||||
if err := m.registry.RegisterClientPlugin(h); err != nil {
|
||||
for _, h := range builtin.GetClientPlugins() {
|
||||
if err := m.registry.RegisterClient(h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Printf("[Plugin] Registered %d server plugins, %d client plugins",
|
||||
len(builtin.GetAll()), len(builtin.GetAllClientPlugins()))
|
||||
log.Printf("[Plugin] Registered %d client plugins", len(builtin.GetClientPlugins()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHandler 返回指定代理类型的 handler
|
||||
func (m *Manager) GetHandler(proxyType string) (plugin.ProxyHandler, error) {
|
||||
return m.registry.Get(proxyType)
|
||||
// GetClient 返回客户端插件
|
||||
func (m *Manager) GetClient(name string) (plugin.ClientPlugin, error) {
|
||||
return m.registry.GetClient(name)
|
||||
}
|
||||
|
||||
// GetRegistry 返回插件注册表
|
||||
func (m *Manager) GetRegistry() *plugin.Registry {
|
||||
return m.registry
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gotunnel/pkg/plugin"
|
||||
"github.com/gotunnel/pkg/plugin/script"
|
||||
"github.com/gotunnel/pkg/protocol"
|
||||
"github.com/gotunnel/pkg/relay"
|
||||
"github.com/hashicorp/yamux"
|
||||
@@ -38,7 +39,7 @@ type Client struct {
|
||||
rules []protocol.ProxyRule
|
||||
mu sync.RWMutex
|
||||
pluginRegistry *plugin.Registry
|
||||
runningPlugins map[string]plugin.ClientHandler // 运行中的客户端插件
|
||||
runningPlugins map[string]plugin.ClientPlugin // 运行中的客户端插件
|
||||
pluginMu sync.RWMutex
|
||||
}
|
||||
|
||||
@@ -51,7 +52,7 @@ func NewClient(serverAddr, token, id string) *Client {
|
||||
ServerAddr: serverAddr,
|
||||
Token: token,
|
||||
ID: id,
|
||||
runningPlugins: make(map[string]plugin.ClientHandler),
|
||||
runningPlugins: make(map[string]plugin.ClientPlugin),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +204,8 @@ func (c *Client) handleStream(stream net.Conn) {
|
||||
c.handleClientPluginStart(stream, msg)
|
||||
case protocol.MsgTypeClientPluginConn:
|
||||
c.handleClientPluginConn(stream, msg)
|
||||
case protocol.MsgTypeJSPluginInstall:
|
||||
c.handleJSPluginInstall(stream, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +371,7 @@ func (c *Client) handlePluginConfig(msg *protocol.Message) {
|
||||
|
||||
// 应用配置到插件
|
||||
if c.pluginRegistry != nil {
|
||||
handler, err := c.pluginRegistry.Get(cfg.PluginName)
|
||||
handler, err := c.pluginRegistry.GetClient(cfg.PluginName)
|
||||
if err != nil {
|
||||
log.Printf("[Client] Plugin %s not found: %v", cfg.PluginName, err)
|
||||
return
|
||||
@@ -399,7 +402,7 @@ func (c *Client) handleClientPluginStart(stream net.Conn, msg *protocol.Message)
|
||||
return
|
||||
}
|
||||
|
||||
handler, err := c.pluginRegistry.GetClientPlugin(req.PluginName)
|
||||
handler, err := c.pluginRegistry.GetClient(req.PluginName)
|
||||
if err != nil {
|
||||
c.sendPluginStatus(stream, req.PluginName, req.RuleName, false, "", err.Error())
|
||||
return
|
||||
@@ -462,3 +465,68 @@ func (c *Client) handleClientPluginConn(stream net.Conn, msg *protocol.Message)
|
||||
// 让插件处理连接
|
||||
handler.HandleConn(stream)
|
||||
}
|
||||
|
||||
// handleJSPluginInstall 处理 JS 插件安装请求
|
||||
func (c *Client) handleJSPluginInstall(stream net.Conn, msg *protocol.Message) {
|
||||
defer stream.Close()
|
||||
|
||||
var req protocol.JSPluginInstallRequest
|
||||
if err := msg.ParsePayload(&req); err != nil {
|
||||
c.sendJSPluginResult(stream, "", false, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[Client] Installing JS plugin: %s", req.PluginName)
|
||||
|
||||
// 创建 JS 插件
|
||||
jsPlugin, err := script.NewJSPlugin(req.PluginName, req.Source)
|
||||
if err != nil {
|
||||
c.sendJSPluginResult(stream, req.PluginName, false, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 注册到 registry
|
||||
if c.pluginRegistry != nil {
|
||||
c.pluginRegistry.RegisterClient(jsPlugin)
|
||||
}
|
||||
|
||||
log.Printf("[Client] JS plugin %s installed", req.PluginName)
|
||||
c.sendJSPluginResult(stream, req.PluginName, true, "")
|
||||
|
||||
// 自动启动
|
||||
if req.AutoStart {
|
||||
c.startJSPlugin(jsPlugin, req)
|
||||
}
|
||||
}
|
||||
|
||||
// sendJSPluginResult 发送 JS 插件安装结果
|
||||
func (c *Client) sendJSPluginResult(stream net.Conn, name string, success bool, errMsg string) {
|
||||
result := protocol.JSPluginInstallResult{
|
||||
PluginName: name,
|
||||
Success: success,
|
||||
Error: errMsg,
|
||||
}
|
||||
msg, _ := protocol.NewMessage(protocol.MsgTypeJSPluginResult, result)
|
||||
protocol.WriteMessage(stream, msg)
|
||||
}
|
||||
|
||||
// startJSPlugin 启动 JS 插件
|
||||
func (c *Client) startJSPlugin(handler plugin.ClientPlugin, req protocol.JSPluginInstallRequest) {
|
||||
if err := handler.Init(req.Config); err != nil {
|
||||
log.Printf("[Client] JS plugin %s init error: %v", req.PluginName, err)
|
||||
return
|
||||
}
|
||||
|
||||
localAddr, err := handler.Start()
|
||||
if err != nil {
|
||||
log.Printf("[Client] JS plugin %s start error: %v", req.PluginName, err)
|
||||
return
|
||||
}
|
||||
|
||||
key := req.PluginName + ":" + req.RuleName
|
||||
c.pluginMu.Lock()
|
||||
c.runningPlugins[key] = handler
|
||||
c.pluginMu.Unlock()
|
||||
|
||||
log.Printf("[Client] JS plugin %s started at %s", req.PluginName, localAddr)
|
||||
}
|
||||
|
||||
@@ -45,19 +45,21 @@ func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// WebServer Web控制台服务
|
||||
type WebServer struct {
|
||||
ClientStore db.ClientStore
|
||||
Server router.ServerInterface
|
||||
Config *config.ServerConfig
|
||||
ConfigPath string
|
||||
ClientStore db.ClientStore
|
||||
Server router.ServerInterface
|
||||
Config *config.ServerConfig
|
||||
ConfigPath string
|
||||
JSPluginStore db.JSPluginStore
|
||||
}
|
||||
|
||||
// NewWebServer 创建Web服务
|
||||
func NewWebServer(cs db.ClientStore, srv router.ServerInterface, cfg *config.ServerConfig, cfgPath string) *WebServer {
|
||||
func NewWebServer(cs db.ClientStore, srv router.ServerInterface, cfg *config.ServerConfig, cfgPath string, jsStore db.JSPluginStore) *WebServer {
|
||||
return &WebServer{
|
||||
ClientStore: cs,
|
||||
Server: srv,
|
||||
Config: cfg,
|
||||
ConfigPath: cfgPath,
|
||||
ClientStore: cs,
|
||||
Server: srv,
|
||||
Config: cfg,
|
||||
ConfigPath: cfgPath,
|
||||
JSPluginStore: jsStore,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,3 +148,8 @@ func (w *WebServer) GetConfigPath() string {
|
||||
func (w *WebServer) SaveConfig() error {
|
||||
return config.SaveServerConfig(w.ConfigPath, w.Config)
|
||||
}
|
||||
|
||||
// GetJSPluginStore 获取 JS 插件存储
|
||||
func (w *WebServer) GetJSPluginStore() db.JSPluginStore {
|
||||
return w.JSPluginStore
|
||||
}
|
||||
|
||||
@@ -13,6 +13,16 @@ type ServerConfig struct {
|
||||
Server ServerSettings `yaml:"server"`
|
||||
Web WebSettings `yaml:"web"`
|
||||
PluginStore PluginStoreSettings `yaml:"plugin_store"`
|
||||
JSPlugins []JSPluginConfig `yaml:"js_plugins,omitempty"`
|
||||
}
|
||||
|
||||
// JSPluginConfig JS 插件配置
|
||||
type JSPluginConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Path string `yaml:"path"` // JS 文件路径
|
||||
AutoPush []string `yaml:"auto_push,omitempty"` // 自动推送到的客户端 ID 列表
|
||||
Config map[string]string `yaml:"config,omitempty"` // 插件配置
|
||||
AutoStart bool `yaml:"auto_start,omitempty"` // 是否自动启动
|
||||
}
|
||||
|
||||
// PluginStoreSettings 扩展商店设置
|
||||
|
||||
@@ -33,6 +33,18 @@ type PluginData struct {
|
||||
WASMData []byte `json:"-"`
|
||||
}
|
||||
|
||||
// JSPlugin JS 插件数据
|
||||
type JSPlugin struct {
|
||||
Name string `json:"name"`
|
||||
Source string `json:"source"`
|
||||
Description string `json:"description"`
|
||||
Author string `json:"author"`
|
||||
AutoPush []string `json:"auto_push"`
|
||||
Config map[string]string `json:"config"`
|
||||
AutoStart bool `json:"auto_start"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// ClientStore 客户端存储接口
|
||||
type ClientStore interface {
|
||||
GetAllClients() ([]Client, error)
|
||||
@@ -55,9 +67,19 @@ type PluginStore interface {
|
||||
GetPluginWASM(name string) ([]byte, error)
|
||||
}
|
||||
|
||||
// JSPluginStore JS 插件存储接口
|
||||
type JSPluginStore interface {
|
||||
GetAllJSPlugins() ([]JSPlugin, error)
|
||||
GetJSPlugin(name string) (*JSPlugin, error)
|
||||
SaveJSPlugin(p *JSPlugin) error
|
||||
DeleteJSPlugin(name string) error
|
||||
SetJSPluginEnabled(name string, enabled bool) error
|
||||
}
|
||||
|
||||
// Store 统一存储接口
|
||||
type Store interface {
|
||||
ClientStore
|
||||
PluginStore
|
||||
JSPluginStore
|
||||
Close() error
|
||||
}
|
||||
|
||||
@@ -75,6 +75,24 @@ func (s *SQLiteStore) init() error {
|
||||
// 迁移:添加 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,
|
||||
description TEXT,
|
||||
author TEXT,
|
||||
auto_push TEXT NOT NULL DEFAULT '[]',
|
||||
config TEXT NOT NULL DEFAULT '',
|
||||
auto_start INTEGER DEFAULT 1,
|
||||
enabled INTEGER DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -296,3 +314,105 @@ func (s *SQLiteStore) GetPluginWASM(name string) ([]byte, error) {
|
||||
err := s.db.QueryRow(`SELECT wasm_data FROM plugins WHERE name = ?`, name).Scan(&data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
// ========== JS 插件存储方法 ==========
|
||||
|
||||
// GetAllJSPlugins 获取所有 JS 插件
|
||||
func (s *SQLiteStore) GetAllJSPlugins() ([]JSPlugin, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
rows, err := s.db.Query(`
|
||||
SELECT name, source, description, author, auto_push, config, auto_start, enabled
|
||||
FROM js_plugins
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var plugins []JSPlugin
|
||||
for rows.Next() {
|
||||
var p JSPlugin
|
||||
var autoPushJSON, configJSON string
|
||||
var autoStart, enabled int
|
||||
err := rows.Scan(&p.Name, &p.Source, &p.Description, &p.Author,
|
||||
&autoPushJSON, &configJSON, &autoStart, &enabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
json.Unmarshal([]byte(autoPushJSON), &p.AutoPush)
|
||||
json.Unmarshal([]byte(configJSON), &p.Config)
|
||||
p.AutoStart = autoStart == 1
|
||||
p.Enabled = enabled == 1
|
||||
plugins = append(plugins, p)
|
||||
}
|
||||
return plugins, nil
|
||||
}
|
||||
|
||||
// GetJSPlugin 获取单个 JS 插件
|
||||
func (s *SQLiteStore) GetJSPlugin(name string) (*JSPlugin, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
var p JSPlugin
|
||||
var autoPushJSON, configJSON string
|
||||
var autoStart, enabled int
|
||||
err := s.db.QueryRow(`
|
||||
SELECT name, source, description, author, auto_push, config, auto_start, enabled
|
||||
FROM js_plugins WHERE name = ?
|
||||
`, name).Scan(&p.Name, &p.Source, &p.Description, &p.Author,
|
||||
&autoPushJSON, &configJSON, &autoStart, &enabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
json.Unmarshal([]byte(autoPushJSON), &p.AutoPush)
|
||||
json.Unmarshal([]byte(configJSON), &p.Config)
|
||||
p.AutoStart = autoStart == 1
|
||||
p.Enabled = enabled == 1
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// SaveJSPlugin 保存 JS 插件
|
||||
func (s *SQLiteStore) SaveJSPlugin(p *JSPlugin) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
autoPushJSON, _ := json.Marshal(p.AutoPush)
|
||||
configJSON, _ := json.Marshal(p.Config)
|
||||
autoStart, enabled := 0, 0
|
||||
if p.AutoStart {
|
||||
autoStart = 1
|
||||
}
|
||||
if p.Enabled {
|
||||
enabled = 1
|
||||
}
|
||||
|
||||
_, err := s.db.Exec(`
|
||||
INSERT OR REPLACE INTO js_plugins
|
||||
(name, source, description, author, auto_push, config, auto_start, enabled)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, p.Name, p.Source, p.Description, p.Author,
|
||||
string(autoPushJSON), string(configJSON), autoStart, enabled)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteJSPlugin 删除 JS 插件
|
||||
func (s *SQLiteStore) DeleteJSPlugin(name string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
_, err := s.db.Exec(`DELETE FROM js_plugins WHERE name = ?`, name)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetJSPluginEnabled 设置 JS 插件启用状态
|
||||
func (s *SQLiteStore) SetJSPluginEnabled(name string, enabled bool) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
val := 0
|
||||
if enabled {
|
||||
val = 1
|
||||
}
|
||||
_, err := s.db.Exec(`UPDATE js_plugins SET enabled = ? WHERE name = ?`, val, name)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ func NewManager() (*Manager, error) {
|
||||
registry: registry,
|
||||
}
|
||||
|
||||
// 注册内置 plugins
|
||||
if err := m.registerBuiltins(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -32,28 +31,26 @@ func NewManager() (*Manager, error) {
|
||||
|
||||
// registerBuiltins 注册内置 plugins
|
||||
func (m *Manager) registerBuiltins() error {
|
||||
// 注册服务端插件
|
||||
if err := m.registry.RegisterAll(builtin.GetAll()); err != nil {
|
||||
if err := m.registry.RegisterAllServer(builtin.GetServerPlugins()); err != nil {
|
||||
return err
|
||||
}
|
||||
// 注册客户端插件
|
||||
for _, h := range builtin.GetAllClientPlugins() {
|
||||
if err := m.registry.RegisterClientPlugin(h); err != nil {
|
||||
for _, h := range builtin.GetClientPlugins() {
|
||||
if err := m.registry.RegisterClient(h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Printf("[Plugin] Registered %d server plugins, %d client plugins",
|
||||
len(builtin.GetAll()), len(builtin.GetAllClientPlugins()))
|
||||
log.Printf("[Plugin] Registered %d server, %d client plugins",
|
||||
len(builtin.GetServerPlugins()), len(builtin.GetClientPlugins()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHandler 返回指定代理类型的 handler
|
||||
func (m *Manager) GetHandler(proxyType string) (plugin.ProxyHandler, error) {
|
||||
return m.registry.Get(proxyType)
|
||||
// GetServer 返回服务端插件
|
||||
func (m *Manager) GetServer(name string) (plugin.ServerPlugin, error) {
|
||||
return m.registry.GetServer(name)
|
||||
}
|
||||
|
||||
// ListPlugins 返回所有可用的 plugins
|
||||
func (m *Manager) ListPlugins() []plugin.PluginInfo {
|
||||
// ListPlugins 返回所有插件
|
||||
func (m *Manager) ListPlugins() []plugin.Info {
|
||||
return m.registry.List()
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,18 @@ type ServerInterface interface {
|
||||
// 插件配置
|
||||
GetPluginConfigSchema(name string) ([]ConfigField, error)
|
||||
SyncPluginConfigToClient(clientID string, pluginName string, config map[string]string) error
|
||||
// JS 插件
|
||||
InstallJSPluginToClient(clientID string, req JSPluginInstallRequest) error
|
||||
}
|
||||
|
||||
// JSPluginInstallRequest JS 插件安装请求
|
||||
type JSPluginInstallRequest struct {
|
||||
PluginName string `json:"plugin_name"`
|
||||
Source string `json:"source"`
|
||||
RuleName string `json:"rule_name"`
|
||||
RemotePort int `json:"remote_port"`
|
||||
Config map[string]string `json:"config"`
|
||||
AutoStart bool `json:"auto_start"`
|
||||
}
|
||||
|
||||
// ConfigField 配置字段(从 plugin 包导出)
|
||||
@@ -89,21 +101,24 @@ type AppInterface interface {
|
||||
GetConfig() *config.ServerConfig
|
||||
GetConfigPath() string
|
||||
SaveConfig() error
|
||||
GetJSPluginStore() db.JSPluginStore
|
||||
}
|
||||
|
||||
// APIHandler API处理器
|
||||
type APIHandler struct {
|
||||
clientStore db.ClientStore
|
||||
server ServerInterface
|
||||
app AppInterface
|
||||
clientStore db.ClientStore
|
||||
server ServerInterface
|
||||
app AppInterface
|
||||
jsPluginStore db.JSPluginStore
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册所有 API 路由
|
||||
func RegisterRoutes(r *Router, app AppInterface) {
|
||||
h := &APIHandler{
|
||||
clientStore: app.GetClientStore(),
|
||||
server: app.GetServer(),
|
||||
app: app,
|
||||
clientStore: app.GetClientStore(),
|
||||
server: app.GetServer(),
|
||||
app: app,
|
||||
jsPluginStore: app.GetJSPluginStore(),
|
||||
}
|
||||
|
||||
api := r.Group("/api")
|
||||
@@ -116,6 +131,8 @@ func RegisterRoutes(r *Router, app AppInterface) {
|
||||
api.HandleFunc("/plugin/", h.handlePlugin)
|
||||
api.HandleFunc("/store/plugins", h.handleStorePlugins)
|
||||
api.HandleFunc("/client-plugin/", h.handleClientPlugin)
|
||||
api.HandleFunc("/js-plugin/", h.handleJSPlugin)
|
||||
api.HandleFunc("/js-plugins", h.handleJSPlugins)
|
||||
}
|
||||
|
||||
func (h *APIHandler) handleStatus(rw http.ResponseWriter, r *http.Request) {
|
||||
@@ -752,3 +769,185 @@ func (h *APIHandler) updateClientPluginConfig(rw http.ResponseWriter, r *http.Re
|
||||
|
||||
h.jsonResponse(rw, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// handleJSPlugin 处理单个 JS 插件操作
|
||||
// GET/PUT/DELETE /api/js-plugin/{name}
|
||||
// POST /api/js-plugin/{name}/push/{clientID}
|
||||
func (h *APIHandler) handleJSPlugin(rw http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path[len("/api/js-plugin/"):]
|
||||
if path == "" {
|
||||
http.Error(rw, "plugin name required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
parts := splitPathMulti(path)
|
||||
|
||||
// POST /api/js-plugin/{name}/push/{clientID}
|
||||
if len(parts) == 3 && parts[1] == "push" {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
h.pushJSPluginToClient(rw, parts[0], parts[2])
|
||||
return
|
||||
}
|
||||
|
||||
// GET/PUT/DELETE /api/js-plugin/{name}
|
||||
pluginName := parts[0]
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.getJSPlugin(rw, pluginName)
|
||||
case http.MethodPut:
|
||||
h.updateJSPlugin(rw, r, pluginName)
|
||||
case http.MethodDelete:
|
||||
h.deleteJSPlugin(rw, pluginName)
|
||||
default:
|
||||
http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
// installJSPluginToClient 安装 JS 插件到客户端
|
||||
func (h *APIHandler) installJSPluginToClient(rw http.ResponseWriter, r *http.Request, clientID string) {
|
||||
online, _, _ := h.server.GetClientStatus(clientID)
|
||||
if !online {
|
||||
http.Error(rw, "client not online", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req JSPluginInstallRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.PluginName == "" || req.Source == "" {
|
||||
http.Error(rw, "plugin_name and source required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.server.InstallJSPluginToClient(clientID, req); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.jsonResponse(rw, map[string]interface{}{
|
||||
"status": "ok",
|
||||
"plugin": req.PluginName,
|
||||
})
|
||||
}
|
||||
|
||||
// handleJSPlugins 处理 JS 插件列表和创建
|
||||
// GET /api/js-plugins - 获取所有 JS 插件
|
||||
// POST /api/js-plugins - 创建新 JS 插件
|
||||
func (h *APIHandler) handleJSPlugins(rw http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.getJSPlugins(rw)
|
||||
case http.MethodPost:
|
||||
h.createJSPlugin(rw, r)
|
||||
default:
|
||||
http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *APIHandler) getJSPlugins(rw http.ResponseWriter) {
|
||||
plugins, err := h.jsPluginStore.GetAllJSPlugins()
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if plugins == nil {
|
||||
plugins = []db.JSPlugin{}
|
||||
}
|
||||
h.jsonResponse(rw, plugins)
|
||||
}
|
||||
|
||||
func (h *APIHandler) createJSPlugin(rw http.ResponseWriter, r *http.Request) {
|
||||
var req db.JSPlugin
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Name == "" || req.Source == "" {
|
||||
http.Error(rw, "name and source required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
req.Enabled = true
|
||||
if err := h.jsPluginStore.SaveJSPlugin(&req); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.jsonResponse(rw, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func (h *APIHandler) getJSPlugin(rw http.ResponseWriter, name string) {
|
||||
p, err := h.jsPluginStore.GetJSPlugin(name)
|
||||
if err != nil {
|
||||
http.Error(rw, "plugin not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
h.jsonResponse(rw, p)
|
||||
}
|
||||
|
||||
func (h *APIHandler) updateJSPlugin(rw http.ResponseWriter, r *http.Request, name string) {
|
||||
var req db.JSPlugin
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
req.Name = name
|
||||
if err := h.jsPluginStore.SaveJSPlugin(&req); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
h.jsonResponse(rw, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func (h *APIHandler) deleteJSPlugin(rw http.ResponseWriter, name string) {
|
||||
if err := h.jsPluginStore.DeleteJSPlugin(name); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
h.jsonResponse(rw, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// pushJSPluginToClient 推送 JS 插件到指定客户端
|
||||
func (h *APIHandler) pushJSPluginToClient(rw http.ResponseWriter, pluginName, clientID string) {
|
||||
// 检查客户端是否在线
|
||||
online, _, _ := h.server.GetClientStatus(clientID)
|
||||
if !online {
|
||||
http.Error(rw, "client not online", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取插件
|
||||
p, err := h.jsPluginStore.GetJSPlugin(pluginName)
|
||||
if err != nil {
|
||||
http.Error(rw, "plugin not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !p.Enabled {
|
||||
http.Error(rw, "plugin is disabled", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 推送到客户端
|
||||
req := JSPluginInstallRequest{
|
||||
PluginName: p.Name,
|
||||
Source: p.Source,
|
||||
RuleName: p.Name,
|
||||
Config: p.Config,
|
||||
AutoStart: p.AutoStart,
|
||||
}
|
||||
|
||||
if err := h.server.InstallJSPluginToClient(clientID, req); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.jsonResponse(rw, map[string]string{"status": "ok", "plugin": pluginName, "client": clientID})
|
||||
}
|
||||
|
||||
@@ -47,6 +47,16 @@ type Server struct {
|
||||
mu sync.RWMutex
|
||||
tlsConfig *tls.Config
|
||||
pluginRegistry *plugin.Registry
|
||||
jsPlugins []JSPluginEntry // 配置的 JS 插件
|
||||
}
|
||||
|
||||
// JSPluginEntry JS 插件条目
|
||||
type JSPluginEntry struct {
|
||||
Name string
|
||||
Source string
|
||||
AutoPush []string
|
||||
Config map[string]string
|
||||
AutoStart bool
|
||||
}
|
||||
|
||||
// ClientSession 客户端会话
|
||||
@@ -85,6 +95,12 @@ func (s *Server) SetPluginRegistry(registry *plugin.Registry) {
|
||||
s.pluginRegistry = registry
|
||||
}
|
||||
|
||||
// LoadJSPlugins 加载 JS 插件配置
|
||||
func (s *Server) LoadJSPlugins(plugins []JSPluginEntry) {
|
||||
s.jsPlugins = plugins
|
||||
log.Printf("[Server] Loaded %d JS plugin configs", len(plugins))
|
||||
}
|
||||
|
||||
// Run 启动服务端
|
||||
func (s *Server) Run() error {
|
||||
addr := fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort)
|
||||
@@ -210,6 +226,9 @@ func (s *Server) setupClientSession(conn net.Conn, clientID string, rules []prot
|
||||
return
|
||||
}
|
||||
|
||||
// 自动推送 JS 插件
|
||||
s.autoPushJSPlugins(cs)
|
||||
|
||||
s.startProxyListeners(cs)
|
||||
go s.heartbeatLoop(cs)
|
||||
|
||||
@@ -362,7 +381,7 @@ func (s *Server) acceptProxyServerConns(cs *ClientSession, ln net.Listener, rule
|
||||
|
||||
// 优先使用插件系统
|
||||
if s.pluginRegistry != nil {
|
||||
if handler, err := s.pluginRegistry.Get(rule.Type); err == nil {
|
||||
if handler, err := s.pluginRegistry.GetServer(rule.Type); err == nil {
|
||||
handler.Init(rule.PluginConfig)
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
@@ -662,7 +681,7 @@ func (s *Server) InstallPluginsToClient(clientID string, plugins []string) error
|
||||
if !found {
|
||||
// 获取插件信息
|
||||
version := "1.0.0"
|
||||
if handler, err := s.pluginRegistry.Get(pluginName); err == nil && handler != nil {
|
||||
if handler, err := s.pluginRegistry.GetServer(pluginName); err == nil && handler != nil {
|
||||
version = handler.Metadata().Version
|
||||
}
|
||||
client.Plugins = append(client.Plugins, db.ClientPlugin{
|
||||
@@ -782,7 +801,7 @@ func (s *Server) GetPluginConfigSchema(name string) ([]router.ConfigField, error
|
||||
return nil, fmt.Errorf("plugin registry not initialized")
|
||||
}
|
||||
|
||||
handler, err := s.pluginRegistry.Get(name)
|
||||
handler, err := s.pluginRegistry.GetServer(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("plugin %s not found", name)
|
||||
}
|
||||
@@ -835,12 +854,65 @@ func (s *Server) sendPluginConfig(session *yamux.Session, pluginName string, con
|
||||
return protocol.WriteMessage(stream, msg)
|
||||
}
|
||||
|
||||
// InstallJSPluginToClient 安装 JS 插件到客户端
|
||||
func (s *Server) InstallJSPluginToClient(clientID string, req router.JSPluginInstallRequest) error {
|
||||
s.mu.RLock()
|
||||
cs, ok := s.clients[clientID]
|
||||
s.mu.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("client %s not online", clientID)
|
||||
}
|
||||
|
||||
stream, err := cs.Session.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
installReq := protocol.JSPluginInstallRequest{
|
||||
PluginName: req.PluginName,
|
||||
Source: req.Source,
|
||||
RuleName: req.RuleName,
|
||||
RemotePort: req.RemotePort,
|
||||
Config: req.Config,
|
||||
AutoStart: req.AutoStart,
|
||||
}
|
||||
|
||||
msg, err := protocol.NewMessage(protocol.MsgTypeJSPluginInstall, installReq)
|
||||
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
|
||||
}
|
||||
|
||||
var result protocol.JSPluginInstallResult
|
||||
if err := resp.ParsePayload(&result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !result.Success {
|
||||
return fmt.Errorf("install failed: %s", result.Error)
|
||||
}
|
||||
|
||||
log.Printf("[Server] JS plugin %s installed on client %s", req.PluginName, clientID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// isClientPlugin 检查是否为客户端插件
|
||||
func (s *Server) isClientPlugin(pluginType string) bool {
|
||||
if s.pluginRegistry == nil {
|
||||
return false
|
||||
}
|
||||
handler, err := s.pluginRegistry.GetClientPlugin(pluginType)
|
||||
handler, err := s.pluginRegistry.GetClient(pluginType)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -950,3 +1022,39 @@ func (s *Server) handleClientPluginConn(cs *ClientSession, conn net.Conn, rule p
|
||||
|
||||
relay.Relay(conn, stream)
|
||||
}
|
||||
|
||||
// autoPushJSPlugins 自动推送 JS 插件到客户端
|
||||
func (s *Server) autoPushJSPlugins(cs *ClientSession) {
|
||||
for _, jp := range s.jsPlugins {
|
||||
if !s.shouldPushToClient(jp.AutoPush, cs.ID) {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("[Server] Auto-pushing JS plugin %s to client %s", jp.Name, cs.ID)
|
||||
|
||||
req := router.JSPluginInstallRequest{
|
||||
PluginName: jp.Name,
|
||||
Source: jp.Source,
|
||||
RuleName: jp.Name,
|
||||
Config: jp.Config,
|
||||
AutoStart: jp.AutoStart,
|
||||
}
|
||||
|
||||
if err := s.InstallJSPluginToClient(cs.ID, req); err != nil {
|
||||
log.Printf("[Server] Failed to push JS plugin %s: %v", jp.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shouldPushToClient 检查是否应推送到指定客户端
|
||||
func (s *Server) shouldPushToClient(autoPush []string, clientID string) bool {
|
||||
if len(autoPush) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, id := range autoPush {
|
||||
if id == clientID || id == "*" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user