Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Lint (push) Failing after 4m23s
Build and Release / Build Binaries (amd64, darwin, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (arm64, linux, linux-latest) (push) Has been skipped
Build and Release / Integration Tests (PostgreSQL) (push) Has been cancelled
Build and Release / Unit Tests (push) Has been cancelled
- Add modules/plugins/loader.go to load .so plugins from plugins directory - Add modules/setting/plugins.go for [plugins] configuration section - Wire up plugin loading before DB init so plugins can register models - Wire up plugin migrations and initialization after DB init - Register plugin routes in web.go Plugins can now be loaded at runtime by placing .so files in the plugins directory and enabling [plugins] ENABLED = true in app.ini. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
108 lines
2.3 KiB
Go
108 lines
2.3 KiB
Go
// Copyright 2026 MarketAlly. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package plugins
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"plugin"
|
|
|
|
"code.gitcaddy.com/server/modules/log"
|
|
"code.gitcaddy.com/server/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 fmt.Errorf("Plugin symbol is nil")
|
|
}
|
|
|
|
Register(*plug)
|
|
return nil
|
|
}
|