2
0
Files
gitcaddy-server/modules/plugins/loader.go
logikonline 12f4ea03a8
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Trigger Vault Plugin Rebuild / Trigger Vault Rebuild (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m48s
Build and Release / Lint (push) Failing after 5m2s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin, linux-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 / Unit Tests (push) Successful in 5m37s
refactor: add /v3 suffix to module path for proper Go semver
Go's semantic import versioning requires v2+ modules to include the
major version in the module path. This enables using proper version
tags (v3.x.x) instead of pseudo-versions.

Updated module path: code.gitcaddy.com/server/v3
2026-01-17 17:53:59 -05:00

109 lines
2.4 KiB
Go

// 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
}