From d37239c795b39cf639b0b458c38b1cb437104d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D1=80=D0=B8=D0=B3=D0=BE=D1=80=D0=B8=D0=B9=20=D0=A1?= =?UTF-8?q?=D0=B0=D1=84=D1=80=D0=BE=D0=BD=D0=BE=D0=B2?= Date: Sat, 13 Jun 2026 19:28:52 +0000 Subject: [PATCH] Delete internal/plugin/777plugin.go --- internal/plugin/777plugin.go | 2400 ---------------------------------- 1 file changed, 2400 deletions(-) delete mode 100644 internal/plugin/777plugin.go diff --git a/internal/plugin/777plugin.go b/internal/plugin/777plugin.go deleted file mode 100644 index 029e081..0000000 --- a/internal/plugin/777plugin.go +++ /dev/null @@ -1,2400 +0,0 @@ -/* - * Copyright 2026 Safronov Grigorii - * - * Licensed under the CDDL, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * https://opensource.org/licenses/CDDL-1.0 - */ - -// Файл: internal/plugin/plugin.go -// Назначение: Система плагинов на основе Lua для расширения функциональности СУБД. -// Позволяет загружать Lua-скрипты как плагины, выполнять их в изолированном окружении, -// взаимодействовать с данными СУБД и логировать действия плагинов в общий лог-файл. -// УЛУЧШЕНИЯ: Sandbox с CPU/memory limits, Plugin Dependencies, Hot Reload (без fsnotify), -// Plugin Marketplace API, Пул Lua-состояний с ограничениями. - -package plugin - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - "sync" - "sync/atomic" - "time" - - "futriis/internal/storage" - - lua "github.com/yuin/gopher-lua" -) - -// ========== Константы ========== - -const ( - DefaultCPULimit = 100 * time.Millisecond - DefaultMemoryLimit = 50 * 1024 * 1024 - DefaultExecutionTimeout = 5 * time.Second - HotReloadCheckInterval = 30 * time.Second - MaxPluginLoadTime = 10 * time.Second - DefaultMaxEventLogSize = 1000 - DefaultBatchSize = 100 - DefaultWriteBufferSize = 64 * 1024 - - // Константы для пула Lua-состояний - MaxLuaStates = 100 - LuaStateTTL = 10 * time.Minute - LuaStatePoolSize = 50 -) - -// PluginConfig представляет конфигурацию плагинов (добавлено для совместимости с main.go) -type PluginConfig struct { - Enabled bool `toml:"enabled"` - ScriptDir string `toml:"script_dir"` - AllowList []string `toml:"allow_list"` - MaxCPUMS int64 `toml:"max_cpu_ms"` - MaxMemoryMB int64 `toml:"max_memory_mb"` -} - -// ========== Пул Lua-состояний ========== - -// LuaStatePool управляет пулом Lua-состояний для ограничения ресурсов -type LuaStatePool struct { - states chan *InstrumentedLState - active atomic.Int32 - maxSize int - mu sync.RWMutex - createdAt map[*InstrumentedLState]time.Time - closed bool -} - -var globalStatePool *LuaStatePool -var statePoolOnce sync.Once - -// GetLuaStatePool возвращает глобальный пул Lua-состояний -func GetLuaStatePool() *LuaStatePool { - statePoolOnce.Do(func() { - globalStatePool = &LuaStatePool{ - states: make(chan *InstrumentedLState, LuaStatePoolSize), - maxSize: MaxLuaStates, - createdAt: make(map[*InstrumentedLState]time.Time), - } - }) - return globalStatePool -} - -// Acquire получает состояние из пула или создаёт новое -func (p *LuaStatePool) Acquire(limits SandboxLimits) (*InstrumentedLState, error) { - if p.closed { - return nil, fmt.Errorf("state pool is closed") - } - - select { - case ls := <-p.states: - if ls == nil { - return p.Acquire(limits) - } - p.mu.RLock() - createdAt, ok := p.createdAt[ls] - p.mu.RUnlock() - - if !ok || time.Since(createdAt) > LuaStateTTL { - ls.Close() - p.mu.Lock() - delete(p.createdAt, ls) - p.mu.Unlock() - p.active.Add(-1) - return p.Acquire(limits) - } - return ls, nil - default: - if p.active.Load() >= int32(p.maxSize) { - return nil, fmt.Errorf("max Lua states limit reached: %d", p.maxSize) - } - - ls := NewInstrumentedLState(limits) - p.active.Add(1) - p.mu.Lock() - p.createdAt[ls] = time.Now() - p.mu.Unlock() - return ls, nil - } -} - -// Release возвращает состояние в пул -func (p *LuaStatePool) Release(ls *InstrumentedLState) { - if ls == nil || p.closed { - if ls != nil { - ls.Close() - p.active.Add(-1) - } - return - } - - ls.instructions.Store(0) - ls.startTime = time.Now() - - select { - case p.states <- ls: - default: - ls.Close() - p.active.Add(-1) - p.mu.Lock() - delete(p.createdAt, ls) - p.mu.Unlock() - } -} - -// GetActiveCount возвращает количество активных состояний -func (p *LuaStatePool) GetActiveCount() int32 { - return p.active.Load() -} - -// GetPoolSize возвращает размер пула -func (p *LuaStatePool) GetPoolSize() int { - return len(p.states) -} - -// Close закрывает пул и все состояния -func (p *LuaStatePool) Close() { - p.mu.Lock() - if p.closed { - p.mu.Unlock() - return - } - p.closed = true - p.mu.Unlock() - - close(p.states) - for ls := range p.states { - if ls != nil { - ls.Close() - } - } - - p.mu.Lock() - for ls := range p.createdAt { - if ls != nil { - ls.Close() - } - } - p.createdAt = make(map[*InstrumentedLState]time.Time) - p.mu.Unlock() - - p.active.Store(0) -} - -// ========== Sandbox с ограничениями ========== - -// SandboxLimits определяет ограничения для Lua-скрипта -type SandboxLimits struct { - MaxCPUTime time.Duration - MaxMemory int64 - MaxExecutionTime time.Duration - MaxStackDepth int - MaxInstructions int64 -} - -// InstrumentedLState расширяет lua.LState с мониторингом ресурсов -type InstrumentedLState struct { - *lua.LState - limits SandboxLimits - instructions atomic.Int64 - startTime time.Time - ctx context.Context - cancel context.CancelFunc - mu sync.RWMutex - closed bool -} - -// NewInstrumentedLState создаёт Lua-состояние с ограничениями -func NewInstrumentedLState(limits SandboxLimits) *InstrumentedLState { - ctx, cancel := context.WithCancel(context.Background()) - - ls := &InstrumentedLState{ - LState: lua.NewState(lua.Options{ - SkipOpenLibs: true, - }), - limits: limits, - startTime: time.Now(), - ctx: ctx, - cancel: cancel, - } - - lua.OpenBase(ls.LState) - lua.OpenString(ls.LState) - lua.OpenTable(ls.LState) - lua.OpenMath(ls.LState) - - runtime.SetFinalizer(ls, func(ls *InstrumentedLState) { - if ls != nil && !ls.closed { - ls.Close() - } - }) - - return ls -} - -// SetCPULimit устанавливает лимит CPU времени -func (ls *InstrumentedLState) SetCPULimit(limit time.Duration) { - ls.mu.Lock() - defer ls.mu.Unlock() - ls.limits.MaxCPUTime = limit -} - -// SetMemoryLimit устанавливает лимит памяти -func (ls *InstrumentedLState) SetMemoryLimit(limit int64) { - ls.mu.Lock() - defer ls.mu.Unlock() - ls.limits.MaxMemory = limit -} - -// CheckMemory проверяет текущее использование памяти -func (ls *InstrumentedLState) CheckMemory() bool { - if ls.limits.MaxMemory <= 0 { - return true - } - var m runtime.MemStats - runtime.ReadMemStats(&m) - return int64(m.Alloc) <= ls.limits.MaxMemory -} - -// GetSandboxStats возвращает статистику песочницы -func (ls *InstrumentedLState) GetSandboxStats() map[string]interface{} { - var m runtime.MemStats - runtime.ReadMemStats(&m) - - return map[string]interface{}{ - "instructions": ls.instructions.Load(), - "execution_ms": time.Since(ls.startTime).Milliseconds(), - "memory_alloc": m.Alloc, - "memory_sys": m.Sys, - "num_gc": m.NumGC, - "pause_total_ms": m.PauseTotalNs / 1000000, - } -} - -// IsClosed возвращает статус закрытия -func (ls *InstrumentedLState) IsClosed() bool { - ls.mu.RLock() - defer ls.mu.RUnlock() - return ls.closed -} - -// Close закрывает Lua состояние -func (ls *InstrumentedLState) Close() { - ls.mu.Lock() - defer ls.mu.Unlock() - - if ls.closed { - return - } - ls.closed = true - ls.cancel() - if ls.LState != nil { - ls.LState.Close() - } -} - -// ========== Plugin Dependencies ========== - -// PluginDependency определяет зависимость плагина -type PluginDependency struct { - Name string `json:"name"` - Version string `json:"version"` - MinVersion string `json:"min_version,omitempty"` - MaxVersion string `json:"max_version,omitempty"` - Optional bool `json:"optional,omitempty"` -} - -// PluginManifest описывает метаданные плагина -type PluginManifest struct { - Name string `json:"name"` - Version string `json:"version"` - Author string `json:"author"` - Description string `json:"description"` - Dependencies []PluginDependency `json:"dependencies,omitempty"` - APIVersion string `json:"api_version"` - MinGoVersion string `json:"min_go_version,omitempty"` - EntryPoint string `json:"entry_point,omitempty"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -// PluginWithDeps расширяет Plugin с поддержкой зависимостей -type PluginWithDeps struct { - *Plugin - Manifest *PluginManifest - Dependencies map[string]*PluginWithDeps - Dependents map[string]*PluginWithDeps - LoadOrder int - depMu sync.RWMutex -} - -// DependencyResolver разрешает зависимости между плагинами -type DependencyResolver struct { - plugins map[string]*PluginWithDeps - mu sync.RWMutex -} - -// NewDependencyResolver создаёт новый резолвер зависимостей -func NewDependencyResolver() *DependencyResolver { - return &DependencyResolver{ - plugins: make(map[string]*PluginWithDeps), - } -} - -// RegisterPlugin регистрирует плагин с его манифестом -func (dr *DependencyResolver) RegisterPlugin(plugin *PluginWithDeps) error { - dr.mu.Lock() - defer dr.mu.Unlock() - - dr.plugins[plugin.Name] = plugin - return nil -} - -// ResolveOrder возвращает порядок загрузки плагинов (топологическая сортировка) -func (dr *DependencyResolver) ResolveOrder() ([]*PluginWithDeps, error) { - dr.mu.RLock() - defer dr.mu.RUnlock() - - graph := make(map[string][]string) - inDegree := make(map[string]int) - - for name, plugin := range dr.plugins { - inDegree[name] = 0 - for _, dep := range plugin.Manifest.Dependencies { - if !dep.Optional { - graph[dep.Name] = append(graph[dep.Name], name) - inDegree[name]++ - } - } - } - - queue := make([]string, 0) - for name, degree := range inDegree { - if degree == 0 { - queue = append(queue, name) - } - } - - result := make([]*PluginWithDeps, 0) - for len(queue) > 0 { - current := queue[0] - queue = queue[1:] - - result = append(result, dr.plugins[current]) - - for _, dependent := range graph[current] { - inDegree[dependent]-- - if inDegree[dependent] == 0 { - queue = append(queue, dependent) - } - } - } - - if len(result) != len(dr.plugins) { - return nil, fmt.Errorf("circular dependency detected among plugins") - } - - for i, plugin := range result { - plugin.LoadOrder = i - } - - return result, nil -} - -// CheckVersionCompatibility проверяет совместимость версий -func (dr *DependencyResolver) CheckVersionCompatibility(dep PluginDependency, actualVersion string) bool { - if dep.MinVersion != "" && actualVersion < dep.MinVersion { - return false - } - if dep.MaxVersion != "" && actualVersion > dep.MaxVersion { - return false - } - if dep.Version != "" && actualVersion != dep.Version { - return false - } - return true -} - -// ========== File Watcher (без fsnotify) ========== - -// FileWatcher самостоятельно отслеживает изменения файлов -type FileWatcher struct { - files map[string]time.Time - mu sync.RWMutex - interval time.Duration - onChange func(path string) error - stopChan chan struct{} - wg sync.WaitGroup - logger storage.LoggerInterface -} - -// NewFileWatcher создаёт новый файловый вотчер без fsnotify -func NewFileWatcher(interval time.Duration, onChange func(path string) error, logger storage.LoggerInterface) *FileWatcher { - if interval <= 0 { - interval = HotReloadCheckInterval - } - return &FileWatcher{ - files: make(map[string]time.Time), - interval: interval, - onChange: onChange, - stopChan: make(chan struct{}), - logger: logger, - } -} - -// Add отслеживает файл -func (fw *FileWatcher) Add(path string) error { - info, err := os.Stat(path) - if err != nil { - return err - } - - fw.mu.Lock() - defer fw.mu.Unlock() - fw.files[path] = info.ModTime() - return nil -} - -// Remove прекращает отслеживание файла -func (fw *FileWatcher) Remove(path string) { - fw.mu.Lock() - defer fw.mu.Unlock() - delete(fw.files, path) -} - -// Start запускает цикл проверки изменений -func (fw *FileWatcher) Start() { - fw.wg.Add(1) - go fw.watchLoop() -} - -// Stop останавливает цикл проверки -func (fw *FileWatcher) Stop() { - close(fw.stopChan) - fw.wg.Wait() -} - -// watchLoop периодически проверяет изменения файлов -func (fw *FileWatcher) watchLoop() { - defer fw.wg.Done() - - ticker := time.NewTicker(fw.interval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - fw.checkChanges() - case <-fw.stopChan: - return - } - } -} - -// checkChanges проверяет изменения всех отслеживаемых файлов -func (fw *FileWatcher) checkChanges() { - fw.mu.RLock() - files := make([]string, 0, len(fw.files)) - for path := range fw.files { - files = append(files, path) - } - fw.mu.RUnlock() - - for _, path := range files { - info, err := os.Stat(path) - if err != nil { - if fw.logger != nil { - fw.logger.Warn(fmt.Sprintf("Failed to stat file %s: %v", path, err)) - } - continue - } - - fw.mu.RLock() - lastMod := fw.files[path] - fw.mu.RUnlock() - - if info.ModTime().After(lastMod) { - if fw.logger != nil { - fw.logger.Info(fmt.Sprintf("File changed: %s (was %v, now %v)", path, lastMod, info.ModTime())) - } - - if err := fw.onChange(path); err != nil { - if fw.logger != nil { - fw.logger.Error(fmt.Sprintf("Failed to handle change for %s: %v", path, err)) - } - } - - fw.mu.Lock() - fw.files[path] = info.ModTime() - fw.mu.Unlock() - } - } -} - -// ========== HotReloadManager управляет горячей перезагрузкой плагинов ========== - -type HotReloadManager struct { - pluginManager *PluginManager - watcher *FileWatcher - reloadQueue chan string - reloading map[string]bool - mu sync.RWMutex - logger storage.LoggerInterface -} - -// NewHotReloadManager создаёт менеджер горячей перезагрузки -func NewHotReloadManager(pm *PluginManager, logger storage.LoggerInterface) *HotReloadManager { - hrm := &HotReloadManager{ - pluginManager: pm, - reloadQueue: make(chan string, 100), - reloading: make(map[string]bool), - logger: logger, - } - - hrm.watcher = NewFileWatcher(HotReloadCheckInterval, hrm.handleFileChange, logger) - - go hrm.processReloadQueue() - - return hrm -} - -// Start запускает мониторинг изменений -func (hrm *HotReloadManager) Start() { - hrm.watcher.Start() -} - -// Stop останавливает мониторинг -func (hrm *HotReloadManager) Stop() { - hrm.watcher.Stop() - close(hrm.reloadQueue) -} - -// WatchPlugin начинает отслеживать плагин -func (hrm *HotReloadManager) WatchPlugin(pluginPath string) error { - return hrm.watcher.Add(pluginPath) -} - -// handleFileChange обрабатывает изменение файла -func (hrm *HotReloadManager) handleFileChange(path string) error { - baseName := filepath.Base(path) - pluginName := strings.TrimSuffix(baseName, ".lua") - - hrm.mu.Lock() - if hrm.reloading[pluginName] { - hrm.mu.Unlock() - return nil - } - hrm.reloading[pluginName] = true - hrm.mu.Unlock() - - select { - case hrm.reloadQueue <- pluginName: - default: - if hrm.logger != nil { - hrm.logger.Warn(fmt.Sprintf("Reload queue full, skipping reload for %s", pluginName)) - } - } - - return nil -} - -// processReloadQueue обрабатывает очередь перезагрузки -func (hrm *HotReloadManager) processReloadQueue() { - for pluginName := range hrm.reloadQueue { - hrm.reloadPlugin(pluginName) - - hrm.mu.Lock() - delete(hrm.reloading, pluginName) - hrm.mu.Unlock() - } -} - -// reloadPlugin выполняет горячую перезагрузку плагина -func (hrm *HotReloadManager) reloadPlugin(pluginName string) { - if hrm.logger != nil { - hrm.logger.Info(fmt.Sprintf("Hot reloading plugin: %s", pluginName)) - } - - oldPlugin, err := hrm.pluginManager.GetPlugin(pluginName) - if err != nil { - if hrm.logger != nil { - hrm.logger.Error(fmt.Sprintf("Plugin not found for reload: %s", pluginName)) - } - return - } - - var oldState map[string]interface{} - - if oldWithDeps, ok := interface{}(oldPlugin).(*PluginWithDeps); ok { - if oldWithDeps.LState != nil && !oldWithDeps.IsClosed() { - fn := oldWithDeps.LState.GetGlobal("on_before_reload") - if fn != lua.LNil { - oldWithDeps.LState.CallByParam(lua.P{ - Fn: fn, - NRet: 1, - Protect: true, - }) - ret := oldWithDeps.LState.Get(-1) - if table, ok := ret.(*lua.LTable); ok { - oldState = make(map[string]interface{}) - table.ForEach(func(key, value lua.LValue) { - oldState[key.String()] = value.String() - }) - } - oldWithDeps.LState.Pop(1) - } - } - } else if oldPlugin.LState != nil && !oldPlugin.IsClosed() { - fn := oldPlugin.LState.GetGlobal("on_before_reload") - if fn != lua.LNil { - oldPlugin.LState.CallByParam(lua.P{ - Fn: fn, - NRet: 1, - Protect: true, - }) - ret := oldPlugin.LState.Get(-1) - if table, ok := ret.(*lua.LTable); ok { - oldState = make(map[string]interface{}) - table.ForEach(func(key, value lua.LValue) { - oldState[key.String()] = value.String() - }) - } - oldPlugin.LState.Pop(1) - } - } - - if err := hrm.pluginManager.StopPlugin(pluginName); err != nil { - if hrm.logger != nil { - hrm.logger.Warn(fmt.Sprintf("Failed to stop plugin before reload: %v", err)) - } - } - - if err := hrm.pluginManager.UnloadPlugin(pluginName); err != nil { - if hrm.logger != nil { - hrm.logger.Error(fmt.Sprintf("Failed to unload plugin for reload: %v", err)) - } - return - } - - pluginPath := filepath.Join(hrm.pluginManager.GetPluginsDir(), pluginName+".lua") - if err := hrm.pluginManager.LoadPluginWithSandbox(pluginName, pluginPath, SandboxLimits{ - MaxCPUTime: DefaultCPULimit, - MaxMemory: DefaultMemoryLimit, - MaxExecutionTime: DefaultExecutionTimeout, - MaxInstructions: 1000000, - }); err != nil { - if hrm.logger != nil { - hrm.logger.Error(fmt.Sprintf("Failed to load plugin during reload: %v", err)) - } - return - } - - newPlugin, _ := hrm.pluginManager.GetPlugin(pluginName) - - if newWithDeps, ok := interface{}(newPlugin).(*PluginWithDeps); ok && oldState != nil { - if newWithDeps.LState != nil && !newWithDeps.IsClosed() { - fn := newWithDeps.LState.GetGlobal("on_after_reload") - if fn != lua.LNil { - stateTable := newWithDeps.LState.NewTable() - for k, v := range oldState { - stateTable.RawSetString(k, lua.LString(fmt.Sprintf("%v", v))) - } - newWithDeps.LState.CallByParam(lua.P{ - Fn: fn, - NRet: 0, - Protect: true, - }, stateTable) - } - } - } else if newPlugin != nil && newPlugin.LState != nil && !newPlugin.IsClosed() && oldState != nil { - fn := newPlugin.LState.GetGlobal("on_after_reload") - if fn != lua.LNil { - stateTable := newPlugin.LState.NewTable() - for k, v := range oldState { - stateTable.RawSetString(k, lua.LString(fmt.Sprintf("%v", v))) - } - newPlugin.LState.CallByParam(lua.P{ - Fn: fn, - NRet: 0, - Protect: true, - }, stateTable) - } - } - - if err := hrm.pluginManager.StartPlugin(pluginName); err != nil { - if hrm.logger != nil { - hrm.logger.Error(fmt.Sprintf("Failed to start plugin after reload: %v", err)) - } - } - - if hrm.logger != nil { - hrm.logger.Info(fmt.Sprintf("Plugin hot reload completed: %s", pluginName)) - } - - storage.LogAudit("PLUGIN_HOT_RELOAD", "PLUGIN", pluginName, map[string]interface{}{ - "reload_time": time.Now().UnixMilli(), - }) -} - -// ========== Plugin Marketplace API ========== - -// MarketplacePlugin представляет плагин в маркетплейсе -type MarketplacePlugin struct { - ID string `json:"id"` - Name string `json:"name"` - Version string `json:"version"` - Author string `json:"author"` - Description string `json:"description"` - Downloads int64 `json:"downloads"` - Rating float64 `json:"rating"` - Tags []string `json:"tags"` - Repository string `json:"repository"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -// PluginMarketplace управляет плагинами из маркетплейса -type PluginMarketplace struct { - plugins map[string]*MarketplacePlugin - installed map[string]bool - apiEndpoint string - mu sync.RWMutex - logger storage.LoggerInterface - pm *PluginManager -} - -// NewPluginMarketplace создаёт новый маркетплейс -func NewPluginMarketplace(apiEndpoint string, pm *PluginManager, logger storage.LoggerInterface) *PluginMarketplace { - return &PluginMarketplace{ - plugins: make(map[string]*MarketplacePlugin), - installed: make(map[string]bool), - apiEndpoint: apiEndpoint, - logger: logger, - pm: pm, - } -} - -// ListAvailablePlugins возвращает список доступных плагинов -func (pmkt *PluginMarketplace) ListAvailablePlugins() []*MarketplacePlugin { - pmkt.mu.RLock() - defer pmkt.mu.RUnlock() - - result := make([]*MarketplacePlugin, 0, len(pmkt.plugins)) - for _, p := range pmkt.plugins { - result = append(result, p) - } - return result -} - -// RegisterPlugin регистрирует плагин в маркетплейсе -func (pmkt *PluginMarketplace) RegisterPlugin(plugin *MarketplacePlugin) { - pmkt.mu.Lock() - defer pmkt.mu.Unlock() - pmkt.plugins[plugin.ID] = plugin -} - -// InstallPlugin устанавливает плагин из маркетплейса -func (pmkt *PluginMarketplace) InstallPlugin(pluginID, version string) error { - pmkt.mu.RLock() - plugin, ok := pmkt.plugins[pluginID] - pmkt.mu.RUnlock() - - if !ok { - return fmt.Errorf("plugin not found in marketplace: %s", pluginID) - } - - pluginPath := filepath.Join(pmkt.pm.GetPluginsDir(), plugin.Name+".lua") - - if err := pmkt.pm.LoadPluginWithSandbox(plugin.Name, pluginPath, SandboxLimits{ - MaxCPUTime: DefaultCPULimit, - MaxMemory: DefaultMemoryLimit, - MaxExecutionTime: DefaultExecutionTimeout, - MaxInstructions: 1000000, - }); err != nil { - return fmt.Errorf("failed to load plugin: %v", err) - } - - if err := pmkt.pm.StartPlugin(plugin.Name); err != nil { - return fmt.Errorf("failed to start plugin: %v", err) - } - - pmkt.mu.Lock() - pmkt.installed[pluginID] = true - pmkt.mu.Unlock() - - if pmkt.logger != nil { - pmkt.logger.Info(fmt.Sprintf("Installed plugin from marketplace: %s v%s", plugin.Name, plugin.Version)) - } - - return nil -} - -// UninstallPlugin удаляет плагин -func (pmkt *PluginMarketplace) UninstallPlugin(pluginID string) error { - pmkt.mu.RLock() - plugin, ok := pmkt.plugins[pluginID] - pmkt.mu.RUnlock() - - if !ok { - return fmt.Errorf("plugin not found: %s", pluginID) - } - - if err := pmkt.pm.StopPlugin(plugin.Name); err != nil { - return err - } - - if err := pmkt.pm.UnloadPlugin(plugin.Name); err != nil { - return err - } - - pmkt.mu.Lock() - delete(pmkt.installed, pluginID) - pmkt.mu.Unlock() - - return nil -} - -// UpdatePlugin обновляет плагин до новой версии -func (pmkt *PluginMarketplace) UpdatePlugin(pluginID, newVersion string) error { - if err := pmkt.UninstallPlugin(pluginID); err != nil { - return err - } - return pmkt.InstallPlugin(pluginID, newVersion) -} - -// GetInstalledPlugins возвращает список установленных плагинов -func (pmkt *PluginMarketplace) GetInstalledPlugins() []string { - pmkt.mu.RLock() - defer pmkt.mu.RUnlock() - - result := make([]string, 0, len(pmkt.installed)) - for id := range pmkt.installed { - result = append(result, id) - } - return result -} - -// ========== Plugin Struct ========== - -// PluginStatus представляет состояние плагина -type PluginStatus int32 - -const ( - StatusLoaded PluginStatus = iota - StatusRunning - StatusStopped - StatusError -) - -// PluginEventLog представляет событие плагина с временными метками -type PluginEventLog struct { - PluginName string `json:"plugin_name"` - EventType string `json:"event_type"` - Data interface{} `json:"data"` - Timestamp int64 `json:"timestamp"` - TimestampStr string `json:"timestamp_str"` - DurationMs int64 `json:"duration_ms,omitempty"` - Error string `json:"error,omitempty"` -} - -// Plugin представляет загруженный Lua-плагин -type Plugin struct { - Name string - FilePath string - Status atomic.Int32 - LState *lua.LState - logger storage.LoggerInterface - storage *storage.Storage - mu sync.RWMutex - loadedAt time.Time - loadedAtMs int64 - loadedAtStr string - version string - author string - description string - eventLog []PluginEventLog - eventLogMu sync.RWMutex - maxEventLog int - closed bool -} - -// IsClosed возвращает статус закрытия плагина -func (p *Plugin) IsClosed() bool { - p.mu.RLock() - defer p.mu.RUnlock() - return p.closed -} - -// closePlugin закрывает плагин -func (p *Plugin) closePlugin() { - p.mu.Lock() - defer p.mu.Unlock() - - if p.closed { - return - } - p.closed = true - if p.LState != nil { - p.LState.Close() - } -} - -// ========== PluginManager ========== - -// PluginManager управляет всеми загруженными плагинами -type PluginManager struct { - plugins sync.Map - logger storage.LoggerInterface - storage *storage.Storage - pluginsDir string - eventBus chan PluginEvent - enabled bool - depResolver *DependencyResolver - hotReload *HotReloadManager - marketplace *PluginMarketplace - statePool *LuaStatePool -} - -// PluginEvent представляет событие от плагина -type PluginEvent struct { - PluginName string - EventType string - Data interface{} - Timestamp int64 - TimestampStr string -} - -// NewPluginManager создаёт новый менеджер плагинов -func NewPluginManager(pluginsDir string, logger storage.LoggerInterface, store *storage.Storage, enabled bool) *PluginManager { - pm := &PluginManager{ - logger: logger, - storage: store, - pluginsDir: pluginsDir, - eventBus: make(chan PluginEvent, 1000), - enabled: enabled, - depResolver: NewDependencyResolver(), - statePool: GetLuaStatePool(), - } - - if !enabled { - if logger != nil { - logger.Info("Plugin system is disabled") - } - return pm - } - - if err := os.MkdirAll(pluginsDir, 0755); err != nil { - if logger != nil { - logger.Error(fmt.Sprintf("Failed to create plugins directory: %v", err)) - } - } - - go pm.eventLoop() - - pm.hotReload = NewHotReloadManager(pm, logger) - pm.hotReload.Start() - - go pm.autoLoadPlugins() - - if logger != nil { - logger.Info(fmt.Sprintf("Plugin system initialized, plugins directory: %s", pluginsDir)) - } - - return pm -} - -// NewPluginManagerFromConfig создаёт новый менеджер плагинов из конфигурации -func NewPluginManagerFromConfig(cfg *PluginConfig, logger storage.LoggerInterface, store *storage.Storage) *PluginManager { - if cfg == nil { - return NewPluginManager("plugins", logger, store, false) - } - - pm := NewPluginManager(cfg.ScriptDir, logger, store, cfg.Enabled) - - if cfg.Enabled && len(cfg.AllowList) > 0 { - if pm.logger != nil { - pm.logger.Info(fmt.Sprintf("Plugin allow list configured: %v", cfg.AllowList)) - } - } - - return pm -} - -// autoLoadPlugins автоматически загружает все .lua файлы из директории плагинов -func (pm *PluginManager) autoLoadPlugins() { - if !pm.enabled { - return - } - - pm.loadPluginsFromDir() - - ticker := time.NewTicker(10 * time.Second) - defer ticker.Stop() - - for range ticker.C { - pm.loadPluginsFromDir() - } -} - -// loadPluginsFromDir загружает все плагины из директории -func (pm *PluginManager) loadPluginsFromDir() { - entries, err := os.ReadDir(pm.pluginsDir) - if err != nil { - if pm.logger != nil { - pm.logger.Error(fmt.Sprintf("Failed to read plugins directory: %v", err)) - } - return - } - - for _, entry := range entries { - if entry.IsDir() { - continue - } - - name := entry.Name() - if !strings.HasSuffix(name, ".lua") { - continue - } - - pluginName := strings.TrimSuffix(name, ".lua") - if _, exists := pm.plugins.Load(pluginName); !exists { - pluginPath := filepath.Join(pm.pluginsDir, name) - if err := pm.LoadPluginWithSandbox(pluginName, pluginPath, SandboxLimits{ - MaxCPUTime: DefaultCPULimit, - MaxMemory: DefaultMemoryLimit, - MaxExecutionTime: DefaultExecutionTimeout, - MaxInstructions: 1000000, - }); err != nil { - if pm.logger != nil { - pm.logger.Error(fmt.Sprintf("Failed to auto-load plugin %s: %v", pluginName, err)) - } - } else if pm.logger != nil { - pm.logger.Info(fmt.Sprintf("Auto-loaded plugin: %s", pluginName)) - } - } - } -} - -// LoadPlugin загружает Lua-плагин из файла -func (pm *PluginManager) LoadPlugin(name, filePath string) error { - return pm.LoadPluginWithSandbox(name, filePath, SandboxLimits{ - MaxCPUTime: DefaultCPULimit, - MaxMemory: DefaultMemoryLimit, - MaxExecutionTime: DefaultExecutionTimeout, - MaxInstructions: 1000000, - }) -} - -// LoadPluginWithSandbox загружает плагин с ограничениями, используя пул состояний -func (pm *PluginManager) LoadPluginWithSandbox(name, filePath string, limits SandboxLimits) error { - if !pm.enabled { - return fmt.Errorf("plugin system is disabled") - } - - if pm.statePool.GetActiveCount() >= int32(MaxLuaStates) { - return fmt.Errorf("max Lua states limit reached: %d", MaxLuaStates) - } - - script, err := os.ReadFile(filePath) - if err != nil { - return fmt.Errorf("failed to read plugin file: %v", err) - } - - manifest := pm.loadManifest(filePath) - - if manifest != nil && len(manifest.Dependencies) > 0 { - if err := pm.checkDependencies(manifest.Dependencies); err != nil { - return fmt.Errorf("dependency check failed: %v", err) - } - } - - L, err := pm.statePool.Acquire(limits) - if err != nil { - return fmt.Errorf("failed to acquire Lua state: %v", err) - } - - pm.registerDatabaseFunctions(L.LState) - pm.registerTransactionFunctions(L.LState) - pm.registerTriggerFunctions(L.LState) - pm.registerTimestampFunctions(L.LState) - - done := make(chan error, 1) - go func() { - defer func() { - if r := recover(); r != nil { - done <- fmt.Errorf("panic during script execution: %v", r) - } - }() - done <- L.DoString(string(script)) - }() - - select { - case err := <-done: - if err != nil { - pm.statePool.Release(L) - return fmt.Errorf("failed to execute plugin script: %v", err) - } - case <-time.After(MaxPluginLoadTime): - pm.statePool.Release(L) - return fmt.Errorf("plugin load timeout") - } - - version := pm.getPluginMetadata(L.LState, "version") - author := pm.getPluginMetadata(L.LState, "author") - description := pm.getPluginMetadata(L.LState, "description") - - now := time.Now() - nowMs := now.UnixMilli() - nowStr := now.Format("2006-01-02 15:04:05.000") - - var plugin interface{} - if manifest != nil { - pluginWithDeps := &PluginWithDeps{ - Plugin: &Plugin{ - Name: name, - FilePath: filePath, - LState: L.LState, - logger: pm.logger, - storage: pm.storage, - loadedAt: now, - loadedAtMs: nowMs, - loadedAtStr: nowStr, - version: version, - author: author, - description: description, - eventLog: make([]PluginEventLog, 0), - maxEventLog: DefaultMaxEventLogSize, - }, - Manifest: manifest, - Dependencies: make(map[string]*PluginWithDeps), - Dependents: make(map[string]*PluginWithDeps), - } - pluginWithDeps.Status.Store(int32(StatusLoaded)) - plugin = pluginWithDeps - - pm.depResolver.RegisterPlugin(pluginWithDeps) - } else { - simplePlugin := &Plugin{ - Name: name, - FilePath: filePath, - LState: L.LState, - logger: pm.logger, - storage: pm.storage, - loadedAt: now, - loadedAtMs: nowMs, - loadedAtStr: nowStr, - version: version, - author: author, - description: description, - eventLog: make([]PluginEventLog, 0), - maxEventLog: DefaultMaxEventLogSize, - } - simplePlugin.Status.Store(int32(StatusLoaded)) - plugin = simplePlugin - } - - pm.plugins.Store(name, plugin) - - if pm.hotReload != nil { - pm.hotReload.WatchPlugin(filePath) - } - - if pm.logger != nil { - pm.logger.Info(fmt.Sprintf("Plugin loaded: %s v%s by %s - %s at %s", name, version, author, description, nowStr)) - } - - auditTimeMs, auditTimeStr := storage.GetCurrentTimestamp() - storage.LogAudit("PLUGIN_LOAD", "PLUGIN", name, map[string]interface{}{ - "version": version, - "author": author, - "description": description, - "loaded_at": nowMs, - "loaded_at_str": nowStr, - "audit_time": auditTimeMs, - "audit_time_str": auditTimeStr, - "limits_cpu_ms": limits.MaxCPUTime.Milliseconds(), - "limits_memory_mb": limits.MaxMemory / 1024 / 1024, - "active_lua_states": pm.statePool.GetActiveCount(), - }) - - if err := pm.callPluginFunction(plugin, "on_load"); err != nil { - if pm.logger != nil { - pm.logger.Warn(fmt.Sprintf("Plugin %s on_load error: %v", name, err)) - } - pm.addPluginEventLog(plugin, "on_load_error", nil, err.Error()) - } else { - pm.addPluginEventLog(plugin, "on_load", nil, "") - } - - return nil -} - -// loadManifest загружает манифест плагина -func (pm *PluginManager) loadManifest(pluginPath string) *PluginManifest { - manifestPath := strings.TrimSuffix(pluginPath, ".lua") + ".json" - data, err := os.ReadFile(manifestPath) - if err != nil { - return nil - } - - var manifest PluginManifest - if err := json.Unmarshal(data, &manifest); err != nil { - return nil - } - return &manifest -} - -// checkDependencies проверяет зависимости плагина -func (pm *PluginManager) checkDependencies(deps []PluginDependency) error { - for _, dep := range deps { - if dep.Optional { - continue - } - - val, ok := pm.plugins.Load(dep.Name) - if !ok { - return fmt.Errorf("missing dependency: %s", dep.Name) - } - - if withDeps, ok := val.(*PluginWithDeps); ok && withDeps.Manifest != nil { - if !pm.depResolver.CheckVersionCompatibility(dep, withDeps.Manifest.Version) { - return fmt.Errorf("version incompatibility: %s requires %s but have %s", - dep.Name, dep.Version, withDeps.Manifest.Version) - } - } - } - return nil -} - -// registerTimestampFunctions регистрирует функции для работы с временными метками -func (pm *PluginManager) registerTimestampFunctions(L *lua.LState) { - L.SetGlobal("get_current_timestamp_ms", L.NewFunction(func(L *lua.LState) int { - L.Push(lua.LNumber(time.Now().UnixMilli())) - return 1 - })) - - L.SetGlobal("get_current_timestamp_str", L.NewFunction(func(L *lua.LState) int { - L.Push(lua.LString(time.Now().Format("2006-01-02 15:04:05.000"))) - return 1 - })) - - L.SetGlobal("get_current_time", L.NewFunction(func(L *lua.LState) int { - now := time.Now() - table := L.NewTable() - table.RawSetString("unix_ms", lua.LNumber(now.UnixMilli())) - table.RawSetString("unix_sec", lua.LNumber(now.Unix())) - table.RawSetString("string", lua.LString(now.Format("2006-01-02 15:04:05.000"))) - table.RawSetString("year", lua.LNumber(now.Year())) - table.RawSetString("month", lua.LNumber(int(now.Month()))) - table.RawSetString("day", lua.LNumber(now.Day())) - table.RawSetString("hour", lua.LNumber(now.Hour())) - table.RawSetString("minute", lua.LNumber(now.Minute())) - table.RawSetString("second", lua.LNumber(now.Second())) - L.Push(table) - return 1 - })) -} - -// registerDatabaseFunctions регистрирует функции доступа к СУБД в Lua -func (pm *PluginManager) registerDatabaseFunctions(L *lua.LState) { - L.SetGlobal("get_database", L.NewFunction(func(L *lua.LState) int { - dbName := L.CheckString(1) - db, err := pm.storage.GetDatabase(dbName) - if err != nil { - L.Push(lua.LNil) - L.Push(lua.LString(err.Error())) - return 2 - } - - ud := L.NewUserData() - ud.Value = db - L.SetMetatable(ud, L.GetTypeMetatable("database")) - L.Push(ud) - return 1 - })) - - L.SetGlobal("get_collection", L.NewFunction(func(L *lua.LState) int { - dbName := L.CheckString(1) - collName := L.CheckString(2) - - db, err := pm.storage.GetDatabase(dbName) - if err != nil { - L.Push(lua.LNil) - L.Push(lua.LString(err.Error())) - return 2 - } - - coll, err := db.GetCollection(collName) - if err != nil { - L.Push(lua.LNil) - L.Push(lua.LString(err.Error())) - return 2 - } - - ud := L.NewUserData() - ud.Value = coll - L.SetMetatable(ud, L.GetTypeMetatable("collection")) - L.Push(ud) - return 1 - })) - - L.SetGlobal("plugin_log", L.NewFunction(func(L *lua.LState) int { - level := L.CheckString(1) - message := L.CheckString(2) - - if pm.logger != nil { - logMsg := fmt.Sprintf("[PLUGIN] %s: %s", level, message) - switch level { - case "debug": - pm.logger.Debug(logMsg) - case "info": - pm.logger.Info(logMsg) - case "warn": - pm.logger.Warn(logMsg) - case "error": - pm.logger.Error(logMsg) - default: - pm.logger.Info(logMsg) - } - } - - return 0 - })) - - L.SetGlobal("emit_event", L.NewFunction(func(L *lua.LState) int { - eventType := L.CheckString(1) - eventData := L.CheckAny(2) - - nowMs := time.Now().UnixMilli() - nowStr := time.Now().Format("2006-01-02 15:04:05.000") - event := PluginEvent{ - EventType: eventType, - Data: pm.luaValueToGo(eventData), - Timestamp: nowMs, - TimestampStr: nowStr, - } - - select { - case pm.eventBus <- event: - default: - if pm.logger != nil { - pm.logger.Warn("Plugin event bus full, event dropped") - } - } - - return 0 - })) - - pm.setupDatabaseMetatable(L) - pm.setupCollectionMetatable(L) -} - -// registerTransactionFunctions регистрирует функции для работы с транзакциями -func (pm *PluginManager) registerTransactionFunctions(L *lua.LState) { - L.SetGlobal("begin_transaction", L.NewFunction(func(L *lua.LState) int { - tx := storage.BeginTransaction() - if tx == nil { - L.Push(lua.LNil) - L.Push(lua.LString("Failed to begin transaction")) - return 2 - } - - ud := L.NewUserData() - ud.Value = tx - L.SetMetatable(ud, L.GetTypeMetatable("transaction")) - L.Push(ud) - return 1 - })) - - L.SetGlobal("commit_transaction", L.NewFunction(func(L *lua.LState) int { - if err := storage.CommitCurrentTransaction(); err != nil { - L.Push(lua.LString(err.Error())) - return 1 - } - L.Push(lua.LNil) - return 1 - })) - - L.SetGlobal("abort_transaction", L.NewFunction(func(L *lua.LState) int { - if err := storage.AbortCurrentTransaction(); err != nil { - L.Push(lua.LString(err.Error())) - return 1 - } - L.Push(lua.LNil) - return 1 - })) - - L.SetGlobal("has_active_transaction", L.NewFunction(func(L *lua.LState) int { - L.Push(lua.LBool(storage.HasActiveTransaction())) - return 1 - })) - - L.SetGlobal("get_current_transaction_id", L.NewFunction(func(L *lua.LState) int { - txID := storage.GetCurrentTransactionID() - if txID == "" { - L.Push(lua.LNil) - } else { - L.Push(lua.LString(txID)) - } - return 1 - })) - - L.SetGlobal("get_active_transactions", L.NewFunction(func(L *lua.LState) int { - txs := storage.GetActiveTransactions() - table := L.NewTable() - for i, tx := range txs { - txTable := L.NewTable() - txTable.RawSetString("id", lua.LString(tx.ID)) - txTable.RawSetString("status", lua.LString(tx.Status)) - txTable.RawSetString("start_time", lua.LNumber(tx.StartTime)) - txTable.RawSetString("operation_count", lua.LNumber(tx.OperationCount)) - table.RawSetInt(i+1, txTable) - } - L.Push(table) - return 1 - })) - - mt := L.NewTypeMetatable("transaction") - L.SetField(mt, "__index", L.NewFunction(func(L *lua.LState) int { - ud := L.CheckUserData(1) - tx, ok := ud.Value.(*storage.Transaction) - if !ok { - L.Push(lua.LNil) - return 1 - } - method := L.CheckString(2) - - switch method { - case "get_id": - L.Push(lua.LString(fmt.Sprintf("%d", tx.ID))) - case "get_operation_count": - L.Push(lua.LNumber(0)) - case "get_start_time": - L.Push(lua.LNumber(tx.StartTime)) - case "get_status": - status := "active" - L.Push(lua.LString(status)) - default: - L.Push(lua.LNil) - } - return 1 - })) -} - -// registerTriggerFunctions регистрирует функции для работы с триггерами -func (pm *PluginManager) registerTriggerFunctions(L *lua.LState) { - tm := storage.GetTriggerManager() - - L.SetGlobal("create_trigger", L.NewFunction(func(L *lua.LState) int { - database := L.CheckString(1) - collection := L.CheckString(2) - name := L.CheckString(3) - event := L.CheckString(4) - config := L.CheckTable(5) - - configMap := make(map[string]interface{}) - config.ForEach(func(key, value lua.LValue) { - if key.Type() == lua.LTString { - configMap[key.String()] = pm.luaValueToGo(value) - } - }) - - if err := tm.CreateTrigger(database, collection, name, storage.TriggerEvent(event), configMap); err != nil { - L.Push(lua.LString(err.Error())) - return 1 - } - L.Push(lua.LNil) - return 1 - })) - - L.SetGlobal("drop_trigger", L.NewFunction(func(L *lua.LState) int { - collection := L.CheckString(1) - event := L.CheckString(2) - name := L.CheckString(3) - - if err := tm.DropTrigger(collection, event, name); err != nil { - L.Push(lua.LString(err.Error())) - return 1 - } - L.Push(lua.LNil) - return 1 - })) - - L.SetGlobal("enable_trigger", L.NewFunction(func(L *lua.LState) int { - collection := L.CheckString(1) - event := L.CheckString(2) - name := L.CheckString(3) - - if err := tm.EnableTrigger(collection, event, name); err != nil { - L.Push(lua.LString(err.Error())) - return 1 - } - L.Push(lua.LNil) - return 1 - })) - - L.SetGlobal("disable_trigger", L.NewFunction(func(L *lua.LState) int { - collection := L.CheckString(1) - event := L.CheckString(2) - name := L.CheckString(3) - - if err := tm.DisableTrigger(collection, event, name); err != nil { - L.Push(lua.LString(err.Error())) - return 1 - } - L.Push(lua.LNil) - return 1 - })) - - L.SetGlobal("list_triggers", L.NewFunction(func(L *lua.LState) int { - collection := L.OptString(1, "") - triggers := tm.ListTriggers(collection) - - table := L.NewTable() - for i, trigger := range triggers { - triggerTable := L.NewTable() - triggerTable.RawSetString("name", lua.LString(trigger.Name)) - triggerTable.RawSetString("collection", lua.LString(trigger.Collection)) - triggerTable.RawSetString("event", lua.LString(string(trigger.Event))) - triggerTable.RawSetString("action", lua.LString(string(trigger.Action))) - triggerTable.RawSetString("enabled", lua.LBool(trigger.Enabled)) - triggerTable.RawSetString("description", lua.LString(trigger.Description)) - table.RawSetInt(i+1, triggerTable) - } - L.Push(table) - return 1 - })) -} - -// setupDatabaseMetatable настраивает методы для объекта базы данных в Lua -func (pm *PluginManager) setupDatabaseMetatable(L *lua.LState) { - mt := L.NewTypeMetatable("database") - L.SetField(mt, "__index", L.NewFunction(func(L *lua.LState) int { - db := L.CheckUserData(1).Value.(*storage.Database) - method := L.CheckString(2) - - switch method { - case "create_collection": - L.Push(L.NewFunction(func(L *lua.LState) int { - name := L.CheckString(1) - err := db.CreateCollection(name) - if err != nil { - L.Push(lua.LString(err.Error())) - return 1 - } - L.Push(lua.LNil) - return 1 - })) - case "get_collection": - L.Push(L.NewFunction(func(L *lua.LState) int { - name := L.CheckString(1) - coll, err := db.GetCollection(name) - if err != nil { - L.Push(lua.LNil) - L.Push(lua.LString(err.Error())) - return 2 - } - ud := L.NewUserData() - ud.Value = coll - L.SetMetatable(ud, L.GetTypeMetatable("collection")) - L.Push(ud) - return 1 - })) - case "drop_collection": - L.Push(L.NewFunction(func(L *lua.LState) int { - name := L.CheckString(1) - err := db.DropCollection(name) - if err != nil { - L.Push(lua.LString(err.Error())) - return 1 - } - L.Push(lua.LNil) - return 1 - })) - case "list_collections": - L.Push(L.NewFunction(func(L *lua.LState) int { - collections := db.ListCollections() - table := L.NewTable() - for i, name := range collections { - table.RawSetInt(i+1, lua.LString(name)) - } - L.Push(table) - return 1 - })) - case "name": - L.Push(lua.LString(db.Name())) - default: - L.Push(lua.LNil) - } - return 1 - })) -} - -// setupCollectionMetatable настраивает методы для объекта коллекции в Lua -func (pm *PluginManager) setupCollectionMetatable(L *lua.LState) { - mt := L.NewTypeMetatable("collection") - L.SetField(mt, "__index", L.NewFunction(func(L *lua.LState) int { - coll := L.CheckUserData(1).Value.(*storage.Collection) - method := L.CheckString(2) - - switch method { - case "insert": - L.Push(L.NewFunction(func(L *lua.LState) int { - doc := L.CheckTable(1) - fields := make(map[string]interface{}) - doc.ForEach(func(key, value lua.LValue) { - if key.Type() == lua.LTString { - fields[key.String()] = pm.luaValueToGo(value) - } - }) - - newDoc := storage.NewDocument() - for k, v := range fields { - newDoc.SetField(k, v) - } - - err := coll.Insert(newDoc) - if err != nil { - L.Push(lua.LNil) - L.Push(lua.LString(err.Error())) - return 2 - } - L.Push(lua.LString(newDoc.ID)) - L.Push(lua.LNil) - return 2 - })) - case "find": - L.Push(L.NewFunction(func(L *lua.LState) int { - id := L.CheckString(1) - doc, err := coll.Find(id) - if err != nil { - L.Push(lua.LNil) - L.Push(lua.LString(err.Error())) - return 2 - } - table := L.NewTable() - table.RawSetString("_id", lua.LString(doc.ID)) - for k, v := range doc.GetFields() { - table.RawSetString(k, pm.goValueToLua(L, v)) - } - L.Push(table) - return 1 - })) - case "find_by_index": - L.Push(L.NewFunction(func(L *lua.LState) int { - indexName := L.CheckString(1) - value := pm.luaValueToGo(L.CheckAny(2)) - - docs, err := coll.FindByIndex(indexName, value) - if err != nil { - L.Push(lua.LNil) - L.Push(lua.LString(err.Error())) - return 2 - } - - table := L.NewTable() - for i, doc := range docs { - docTable := L.NewTable() - docTable.RawSetString("_id", lua.LString(doc.ID)) - for k, v := range doc.GetFields() { - docTable.RawSetString(k, pm.goValueToLua(L, v)) - } - table.RawSetInt(i+1, docTable) - } - L.Push(table) - return 1 - })) - case "update": - L.Push(L.NewFunction(func(L *lua.LState) int { - id := L.CheckString(1) - updates := L.CheckTable(2) - - fields := make(map[string]interface{}) - updates.ForEach(func(key, value lua.LValue) { - if key.Type() == lua.LTString { - fields[key.String()] = pm.luaValueToGo(value) - } - }) - - err := coll.Update(id, fields) - if err != nil { - L.Push(lua.LString(err.Error())) - return 1 - } - L.Push(lua.LNil) - return 1 - })) - case "delete": - L.Push(L.NewFunction(func(L *lua.LState) int { - id := L.CheckString(1) - err := coll.Delete(id) - if err != nil { - L.Push(lua.LString(err.Error())) - return 1 - } - L.Push(lua.LNil) - return 1 - })) - case "count": - L.Push(L.NewFunction(func(L *lua.LState) int { - count := coll.Count() - L.Push(lua.LNumber(count)) - return 1 - })) - case "name": - L.Push(lua.LString(coll.Name())) - default: - L.Push(lua.LNil) - } - return 1 - })) -} - -// luaValueToGo конвертирует Lua-значение в Go-значение -func (pm *PluginManager) luaValueToGo(val lua.LValue) interface{} { - if val == nil || val == lua.LNil { - return nil - } - - switch v := val.(type) { - case lua.LString: - return string(v) - case lua.LNumber: - return float64(v) - case lua.LBool: - return bool(v) - case *lua.LTable: - result := make(map[string]interface{}) - v.ForEach(func(key, value lua.LValue) { - keyStr := "unknown" - if key.Type() == lua.LTString { - keyStr = key.String() - } else if key.Type() == lua.LTNumber { - keyStr = fmt.Sprintf("%d", int64(key.(lua.LNumber))) - } - result[keyStr] = pm.luaValueToGo(value) - }) - return result - default: - return v.String() - } -} - -// goValueToLua конвертирует Go-значение в Lua-значение -func (pm *PluginManager) goValueToLua(L *lua.LState, val interface{}) lua.LValue { - if val == nil { - return lua.LNil - } - - switch v := val.(type) { - case string: - return lua.LString(v) - case int: - return lua.LNumber(float64(v)) - case int64: - return lua.LNumber(float64(v)) - case float32: - return lua.LNumber(float64(v)) - case float64: - return lua.LNumber(v) - case bool: - return lua.LBool(v) - case map[string]interface{}: - table := L.NewTable() - for k, val := range v { - table.RawSetString(k, pm.goValueToLua(L, val)) - } - return table - case []interface{}: - table := L.NewTable() - for i, val := range v { - table.RawSetInt(i+1, pm.goValueToLua(L, val)) - } - return table - default: - return lua.LString(fmt.Sprintf("%v", v)) - } -} - -// getPluginMetadata извлекает метаданные из загруженного Lua-скрипта -func (pm *PluginManager) getPluginMetadata(L *lua.LState, field string) string { - val := L.GetGlobal(field) - if str, ok := val.(lua.LString); ok { - return string(str) - } - return "unknown" -} - -// callPluginFunction вызывает функцию плагина по имени с защитой от паники -func (pm *PluginManager) callPluginFunction(plugin interface{}, funcName string) error { - var L *lua.LState - var p *Plugin - - switch pl := plugin.(type) { - case *Plugin: - p = pl - p.mu.RLock() - L = p.LState - p.mu.RUnlock() - case *PluginWithDeps: - p = pl.Plugin - p.mu.RLock() - L = p.LState - p.mu.RUnlock() - default: - return fmt.Errorf("unknown plugin type") - } - - if L == nil || p.IsClosed() { - return fmt.Errorf("plugin has no Lua state or is closed") - } - - fn := L.GetGlobal(funcName) - if fn == lua.LNil { - return nil - } - - var callErr error - func() { - defer func() { - if r := recover(); r != nil { - callErr = fmt.Errorf("panic during %s: %v", funcName, r) - } - }() - callErr = L.CallByParam(lua.P{ - Fn: fn, - NRet: 0, - Protect: true, - }) - }() - - if callErr != nil { - return fmt.Errorf("failed to call %s: %v", funcName, callErr) - } - - return nil -} - -// addPluginEventLog добавляет событие в лог плагина -func (pm *PluginManager) addPluginEventLog(plugin interface{}, eventType string, data interface{}, errMsg string) { - var p *Plugin - - switch pl := plugin.(type) { - case *Plugin: - p = pl - case *PluginWithDeps: - p = pl.Plugin - default: - return - } - - p.eventLogMu.Lock() - defer p.eventLogMu.Unlock() - - nowMs := time.Now().UnixMilli() - nowStr := time.Now().Format("2006-01-02 15:04:05.000") - eventLog := PluginEventLog{ - PluginName: p.Name, - EventType: eventType, - Data: data, - Timestamp: nowMs, - TimestampStr: nowStr, - Error: errMsg, - } - - p.eventLog = append(p.eventLog, eventLog) - if len(p.eventLog) > p.maxEventLog { - p.eventLog = p.eventLog[1:] - } -} - -// eventLoop обрабатывает события от плагинов -func (pm *PluginManager) eventLoop() { - for event := range pm.eventBus { - if pm.logger != nil { - pm.logger.Debug(fmt.Sprintf("Plugin event [%s] at %s: %+v", event.EventType, event.TimestampStr, event.Data)) - } - - pm.plugins.Range(func(key, value interface{}) bool { - go pm.notifyPlugin(value, event) - return true - }) - } -} - -// notifyPlugin уведомляет конкретный плагин о событии -func (pm *PluginManager) notifyPlugin(plugin interface{}, event PluginEvent) { - var L *lua.LState - var name string - var p *Plugin - - switch pl := plugin.(type) { - case *Plugin: - p = pl - p.mu.RLock() - L = p.LState - name = p.Name - p.mu.RUnlock() - case *PluginWithDeps: - p = pl.Plugin - p.mu.RLock() - L = p.LState - name = p.Name - p.mu.RUnlock() - default: - return - } - - if L == nil || p.IsClosed() { - return - } - - fn := L.GetGlobal("on_event") - if fn == lua.LNil { - return - } - - event.PluginName = name - - eventTable := L.NewTable() - eventTable.RawSetString("type", lua.LString(event.EventType)) - eventTable.RawSetString("plugin_name", lua.LString(event.PluginName)) - eventTable.RawSetString("timestamp", lua.LNumber(event.Timestamp)) - eventTable.RawSetString("timestamp_str", lua.LString(event.TimestampStr)) - eventTable.RawSetString("data", pm.goValueToLua(L, event.Data)) - - var callErr error - func() { - defer func() { - if r := recover(); r != nil { - callErr = fmt.Errorf("panic during on_event: %v", r) - } - }() - callErr = L.CallByParam(lua.P{ - Fn: fn, - NRet: 0, - Protect: true, - }, eventTable) - }() - - if callErr != nil && pm.logger != nil { - pm.logger.Error(fmt.Sprintf("Plugin %s on_event error: %v", name, callErr)) - } -} - -// ExecutePlugin выполняет пользовательскую функцию плагина -func (pm *PluginManager) ExecutePlugin(pluginName, funcName string, args ...interface{}) (interface{}, error) { - if !pm.enabled { - return nil, fmt.Errorf("plugin system is disabled") - } - - val, ok := pm.plugins.Load(pluginName) - if !ok { - return nil, fmt.Errorf("plugin not found: %s", pluginName) - } - - var L *lua.LState - var p *Plugin - - switch v := val.(type) { - case *Plugin: - p = v - p.mu.RLock() - L = p.LState - p.mu.RUnlock() - case *PluginWithDeps: - p = v.Plugin - p.mu.RLock() - L = p.LState - p.mu.RUnlock() - default: - return nil, fmt.Errorf("unknown plugin type") - } - - if PluginStatus(p.Status.Load()) != StatusRunning { - return nil, fmt.Errorf("plugin %s is not running", pluginName) - } - - if L == nil || p.IsClosed() { - return nil, fmt.Errorf("plugin %s has no Lua state or is closed", pluginName) - } - - fn := L.GetGlobal(funcName) - if fn == lua.LNil { - return nil, fmt.Errorf("function %s not found in plugin %s", funcName, pluginName) - } - - luaArgs := make([]lua.LValue, len(args)) - for i, arg := range args { - luaArgs[i] = pm.goValueToLua(L, arg) - } - - startTime := time.Now() - var ret lua.LValue - var callErr error - func() { - defer func() { - if r := recover(); r != nil { - callErr = fmt.Errorf("panic during execution: %v", r) - } - }() - callErr = L.CallByParam(lua.P{ - Fn: fn, - NRet: 1, - Protect: true, - }, luaArgs...) - if callErr == nil { - ret = L.Get(-1) - L.Pop(1) - } - }() - - duration := time.Since(startTime) - - if callErr != nil { - pm.addPluginEventLog(val, funcName, args, callErr.Error()) - return nil, fmt.Errorf("plugin execution failed: %v", callErr) - } - - pm.addPluginEventLog(val, funcName, args, "") - - if pm.logger != nil { - pm.logger.Debug(fmt.Sprintf("Plugin %s executed %s in %dms", pluginName, funcName, duration.Milliseconds())) - } - - return pm.luaValueToGo(ret), nil -} - -// UnloadPlugin выгружает плагин и возвращает состояние в пул -func (pm *PluginManager) UnloadPlugin(name string) error { - if !pm.enabled { - return fmt.Errorf("plugin system is disabled") - } - - val, ok := pm.plugins.Load(name) - if !ok { - return fmt.Errorf("plugin not found: %s", name) - } - - if err := pm.callPluginFunction(val, "on_unload"); err != nil { - if pm.logger != nil { - pm.logger.Warn(fmt.Sprintf("Plugin %s on_unload error: %v", name, err)) - } - pm.addPluginEventLog(val, "on_unload_error", nil, err.Error()) - } else { - pm.addPluginEventLog(val, "on_unload", nil, "") - } - - var L *lua.LState - var p *Plugin - var ls *InstrumentedLState - - switch v := val.(type) { - case *Plugin: - p = v - L = p.LState - p.Status.Store(int32(StatusStopped)) - p.closePlugin() - if lState, ok := interface{}(L).(*InstrumentedLState); ok { - ls = lState - } - case *PluginWithDeps: - p = v.Plugin - L = p.LState - p.Status.Store(int32(StatusStopped)) - p.closePlugin() - if lState, ok := interface{}(L).(*InstrumentedLState); ok { - ls = lState - } - } - - if ls != nil { - pm.statePool.Release(ls) - } else if L != nil { - L.Close() - pm.statePool.active.Add(-1) - } - - auditTimeMs, auditTimeStr := storage.GetCurrentTimestamp() - storage.LogAudit("PLUGIN_UNLOAD", "PLUGIN", name, map[string]interface{}{ - "unloaded_at": time.Now().UnixMilli(), - "unloaded_at_str": time.Now().Format("2006-01-02 15:04:05.000"), - "audit_time": auditTimeMs, - "audit_time_str": auditTimeStr, - "active_lua_states": pm.statePool.GetActiveCount(), - }) - - pm.plugins.Delete(name) - if pm.logger != nil { - pm.logger.Info(fmt.Sprintf("Plugin unloaded: %s", name)) - } - - return nil -} - -// StartPlugin запускает плагин -func (pm *PluginManager) StartPlugin(name string) error { - if !pm.enabled { - return fmt.Errorf("plugin system is disabled") - } - - val, ok := pm.plugins.Load(name) - if !ok { - return fmt.Errorf("plugin not found: %s", name) - } - - var p *Plugin - switch v := val.(type) { - case *Plugin: - p = v - case *PluginWithDeps: - p = v.Plugin - default: - return fmt.Errorf("unknown plugin type") - } - - if p.IsClosed() { - return fmt.Errorf("plugin is closed") - } - - p.Status.Store(int32(StatusRunning)) - - if err := pm.callPluginFunction(val, "on_start"); err != nil { - p.Status.Store(int32(StatusError)) - pm.addPluginEventLog(val, "on_start_error", nil, err.Error()) - return fmt.Errorf("failed to start plugin: %v", err) - } - - pm.addPluginEventLog(val, "on_start", nil, "") - - auditTimeMs, auditTimeStr := storage.GetCurrentTimestamp() - storage.LogAudit("PLUGIN_START", "PLUGIN", name, map[string]interface{}{ - "started_at": time.Now().UnixMilli(), - "started_at_str": time.Now().Format("2006-01-02 15:04:05.000"), - "audit_time": auditTimeMs, - "audit_time_str": auditTimeStr, - "active_lua_states": pm.statePool.GetActiveCount(), - }) - - if pm.logger != nil { - pm.logger.Info(fmt.Sprintf("Plugin started: %s", name)) - } - return nil -} - -// StopPlugin останавливает плагин -func (pm *PluginManager) StopPlugin(name string) error { - if !pm.enabled { - return fmt.Errorf("plugin system is disabled") - } - - val, ok := pm.plugins.Load(name) - if !ok { - return fmt.Errorf("plugin not found: %s", name) - } - - if err := pm.callPluginFunction(val, "on_stop"); err != nil { - if pm.logger != nil { - pm.logger.Warn(fmt.Sprintf("Plugin %s on_stop error: %v", name, err)) - } - pm.addPluginEventLog(val, "on_stop_error", nil, err.Error()) - } else { - pm.addPluginEventLog(val, "on_stop", nil, "") - } - - var p *Plugin - switch v := val.(type) { - case *Plugin: - p = v - case *PluginWithDeps: - p = v.Plugin - } - p.Status.Store(int32(StatusStopped)) - - auditTimeMs, auditTimeStr := storage.GetCurrentTimestamp() - storage.LogAudit("PLUGIN_STOP", "PLUGIN", name, map[string]interface{}{ - "stopped_at": time.Now().UnixMilli(), - "stopped_at_str": time.Now().Format("2006-01-02 15:04:05.000"), - "audit_time": auditTimeMs, - "audit_time_str": auditTimeStr, - "active_lua_states": pm.statePool.GetActiveCount(), - }) - - if pm.logger != nil { - pm.logger.Info(fmt.Sprintf("Plugin stopped: %s", name)) - } - - return nil -} - -// ListPlugins возвращает список всех загруженных плагинов -func (pm *PluginManager) ListPlugins() []*Plugin { - plugins := make([]*Plugin, 0) - pm.plugins.Range(func(key, value interface{}) bool { - switch v := value.(type) { - case *Plugin: - plugins = append(plugins, v) - case *PluginWithDeps: - plugins = append(plugins, v.Plugin) - } - return true - }) - return plugins -} - -// GetPlugin возвращает плагин по имени -func (pm *PluginManager) GetPlugin(name string) (*Plugin, error) { - val, ok := pm.plugins.Load(name) - if !ok { - return nil, fmt.Errorf("plugin not found: %s", name) - } - switch v := val.(type) { - case *Plugin: - return v, nil - case *PluginWithDeps: - return v.Plugin, nil - default: - return nil, fmt.Errorf("unknown plugin type") - } -} - -// GetPluginWithDeps возвращает плагин с зависимостями -func (pm *PluginManager) GetPluginWithDeps(name string) (*PluginWithDeps, error) { - val, ok := pm.plugins.Load(name) - if !ok { - return nil, fmt.Errorf("plugin not found: %s", name) - } - if withDeps, ok := val.(*PluginWithDeps); ok { - return withDeps, nil - } - return nil, fmt.Errorf("plugin %s does not support dependencies", name) -} - -// LoadPluginsInOrder загружает плагины в правильном порядке зависимостей -func (pm *PluginManager) LoadPluginsInOrder() error { - order, err := pm.depResolver.ResolveOrder() - if err != nil { - return err - } - - for _, plugin := range order { - if err := pm.StartPlugin(plugin.Name); err != nil { - return fmt.Errorf("failed to start plugin %s: %v", plugin.Name, err) - } - } - - return nil -} - -// GetHotReloadManager возвращает менеджер горячей перезагрузки -func (pm *PluginManager) GetHotReloadManager() *HotReloadManager { - return pm.hotReload -} - -// GetMarketplace возвращает маркетплейс плагинов -func (pm *PluginManager) GetMarketplace() *PluginMarketplace { - return pm.marketplace -} - -// EnablePluginMarketplace включает маркетплейс плагинов -func (pm *PluginManager) EnablePluginMarketplace(apiEndpoint string) { - pm.marketplace = NewPluginMarketplace(apiEndpoint, pm, pm.logger) -} - -// IsEnabled возвращает статус системы плагинов -func (pm *PluginManager) IsEnabled() bool { - return pm.enabled -} - -// GetPluginsDir возвращает директорию с плагинами -func (pm *PluginManager) GetPluginsDir() string { - return pm.pluginsDir -} - -// GetPluginEventLog возвращает лог событий плагина -func (p *Plugin) GetPluginEventLog() []PluginEventLog { - p.eventLogMu.RLock() - defer p.eventLogMu.RUnlock() - - result := make([]PluginEventLog, len(p.eventLog)) - copy(result, p.eventLog) - return result -} - -// ClearPluginEventLog очищает лог событий плагина -func (p *Plugin) ClearPluginEventLog() { - p.eventLogMu.Lock() - defer p.eventLogMu.Unlock() - p.eventLog = make([]PluginEventLog, 0) -} - -// Version возвращает версию плагина -func (p *Plugin) Version() string { - return p.version -} - -// Author возвращает автора плагина -func (p *Plugin) Author() string { - return p.author -} - -// Description возвращает описание плагина -func (p *Plugin) Description() string { - return p.description -} - -// LoadedAt возвращает время загрузки плагина -func (p *Plugin) LoadedAt() time.Time { - return p.loadedAt -} - -// LoadedAtMs возвращает время загрузки плагина в миллисекундах -func (p *Plugin) LoadedAtMs() int64 { - return p.loadedAtMs -} - -// LoadedAtStr возвращает строковое представление времени загрузки плагина -func (p *Plugin) LoadedAtStr() string { - return p.loadedAtStr -} - -// GetStatus возвращает статус плагина -func (p *Plugin) GetStatus() PluginStatus { - return PluginStatus(p.Status.Load()) -} - -// GetLuaStatePoolStats возвращает статистику пула Lua-состояний -func (pm *PluginManager) GetLuaStatePoolStats() map[string]interface{} { - return map[string]interface{}{ - "active_count": pm.statePool.GetActiveCount(), - "pool_size": pm.statePool.GetPoolSize(), - "max_allowed": MaxLuaStates, - "ttl_seconds": LuaStateTTL.Seconds(), - } -} - -// Close закрывает менеджер плагинов и все ресурсы -func (pm *PluginManager) Close() error { - if pm.hotReload != nil { - pm.hotReload.Stop() - } - - pm.plugins.Range(func(key, value interface{}) bool { - name := key.(string) - pm.UnloadPlugin(name) - return true - }) - - pm.statePool.Close() - close(pm.eventBus) - - return nil -}