// Copyright 2026 MarketAlly. All rights reserved. // SPDX-License-Identifier: MIT package plugins import ( "errors" "fmt" "os" "path/filepath" "plugin" "code.gitcaddy.com/server/v3/modules/log" "code.gitcaddy.com/server/v3/modules/setting" ) // LoadAll loads all plugins from the configured plugin directory func LoadAll() error { if !setting.Plugins.Enabled { log.Info("Plugin system is disabled") return nil } pluginDir := setting.Plugins.Path if pluginDir == "" { pluginDir = filepath.Join(setting.AppDataPath, "plugins") } // Check if plugin directory exists info, err := os.Stat(pluginDir) if os.IsNotExist(err) { log.Info("Plugin directory does not exist: %s", pluginDir) return nil } if err != nil { return fmt.Errorf("failed to stat plugin directory: %w", err) } if !info.IsDir() { return fmt.Errorf("plugin path is not a directory: %s", pluginDir) } log.Info("Loading plugins from: %s", pluginDir) // Find all .so files in the plugin directory entries, err := os.ReadDir(pluginDir) if err != nil { return fmt.Errorf("failed to read plugin directory: %w", err) } loadedCount := 0 for _, entry := range entries { if entry.IsDir() { continue } name := entry.Name() if filepath.Ext(name) != ".so" { continue } pluginPath := filepath.Join(pluginDir, name) if err := loadPlugin(pluginPath); err != nil { log.Error("Failed to load plugin %s: %v", name, err) // Continue loading other plugins continue } loadedCount++ } log.Info("Loaded %d plugin(s)", loadedCount) return nil } // loadPlugin loads a single plugin from a .so file func loadPlugin(path string) error { log.Info("Loading plugin: %s", path) // Open the plugin p, err := plugin.Open(path) if err != nil { return fmt.Errorf("failed to open plugin: %w", err) } // Look for the Plugin symbol sym, err := p.Lookup("Plugin") if err != nil { return fmt.Errorf("plugin does not export 'Plugin' symbol: %w", err) } // The Plugin symbol should be a pointer to a Plugin interface implementation plug, ok := sym.(*Plugin) if !ok { // Try to see if it's the interface directly plugDirect, ok := sym.(Plugin) if !ok { return fmt.Errorf("Plugin symbol is not a plugins.Plugin (got %T)", sym) } Register(plugDirect) return nil } if plug == nil || *plug == nil { return errors.New("plugin symbol is nil") } Register(*plug) return nil }