package plugins import ( "fmt" "log" "os" "path/filepath" lua "github.com/yuin/gopher-lua" ) // ActivatePlugin fully activates a plugin: creates Lua VM, loads main.lua, starts scheduler. // Only works if plugin is installed and enabled but not yet active. // Returns error if VM creation, script loading, scheduler setup, or on_init hook fails. // on_init failure is fatal — the plugin cannot run without proper initialization. func (m *Manager) ActivatePlugin(name string) error { for i := range m.plugins { p := &m.plugins[i] if p.Meta.Name != name || p.Active { continue } if !p.Enabled { return fmt.Errorf("plugin %q is not enabled", name) } if p.HasInstall && !p.Installed { return fmt.Errorf("plugin %q is not installed", name) } vm, err := NewLuaVM(p) if err != nil { return fmt.Errorf("create VM for %q: %w", name, err) } p.vm = vm if m.Services != nil { vm.SetServices(m.Services) } mainPath := filepath.Join(p.Dir, "main.lua") if _, err := os.Stat(mainPath); err == nil { if err := vm.LoadScript("main.lua"); err != nil { p.vm.Close() p.vm = nil return fmt.Errorf("load main.lua for %q: %w", name, err) } } p.scheduler = NewScheduler(p, vm) for _, bg := range p.Meta.Background { if err := p.scheduler.AddTask(bg); err != nil { p.vm.Close() p.vm = nil p.scheduler = nil return fmt.Errorf("add task %s for %q: %w", bg.ID, name, err) } } p.scheduler.Start() if hookName, ok := p.Meta.Hooks["on_init"]; ok { if err := vm.CallHook(hookName); err != nil { // on_init failure is fatal — rollback activation p.scheduler.Stop() p.scheduler = nil p.vm.Close() p.vm = nil return fmt.Errorf("on_init for %q: %w", name, err) } } p.Active = true log.Printf("[plugins] %s: activated", name) return nil } return fmt.Errorf("plugin %q not found", name) } // DeactivatePlugin stops a plugin's runtime without removing it from enabled list. func (m *Manager) DeactivatePlugin(name string) { for i := range m.plugins { p := &m.plugins[i] if p.Meta.Name != name || !p.Active { continue } if p.scheduler != nil { p.scheduler.Stop() p.scheduler = nil } if hookName, ok := p.Meta.Hooks["on_shutdown"]; ok && p.vm != nil { _ = p.vm.CallHook(hookName) } if p.vm != nil { p.vm.Close() p.vm = nil } p.Active = false log.Printf("[plugins] %s: deactivated", name) return } } // CallPluginHook calls a named Lua function on a specific plugin. func (m *Manager) CallPluginHook(name, hookName string, args ...lua.LValue) (lua.LValue, error) { for i := range m.plugins { if m.plugins[i].Meta.Name == name && m.plugins[i].Active && m.plugins[i].vm != nil { if fn, ok := m.plugins[i].Meta.Hooks[hookName]; ok { return m.plugins[i].vm.CallHookWithResult(fn, args...) } return m.plugins[i].vm.CallHookWithResult(hookName, args...) } } return lua.LNil, nil }