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
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
234 lines
5.3 KiB
Go
234 lines
5.3 KiB
Go
// Copyright 2026 MarketAlly. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package plugins
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
"code.gitcaddy.com/server/v3/modules/log"
|
|
|
|
"xorm.io/xorm"
|
|
)
|
|
|
|
var (
|
|
registry = make(map[string]Plugin)
|
|
registryLock sync.RWMutex
|
|
)
|
|
|
|
// Register adds a plugin to the registry
|
|
func Register(p Plugin) {
|
|
registryLock.Lock()
|
|
defer registryLock.Unlock()
|
|
|
|
name := p.Name()
|
|
if _, exists := registry[name]; exists {
|
|
log.Warn("Plugin %s is already registered, skipping", name)
|
|
return
|
|
}
|
|
|
|
registry[name] = p
|
|
log.Info("Plugin registered: %s v%s", name, p.Version())
|
|
}
|
|
|
|
// Get returns a plugin by name
|
|
func Get(name string) Plugin {
|
|
registryLock.RLock()
|
|
defer registryLock.RUnlock()
|
|
return registry[name]
|
|
}
|
|
|
|
// All returns all registered plugins
|
|
func All() []Plugin {
|
|
registryLock.RLock()
|
|
defer registryLock.RUnlock()
|
|
|
|
plugins := make([]Plugin, 0, len(registry))
|
|
for _, p := range registry {
|
|
plugins = append(plugins, p)
|
|
}
|
|
return plugins
|
|
}
|
|
|
|
// InitAll initializes all registered plugins
|
|
func InitAll(ctx context.Context) error {
|
|
registryLock.RLock()
|
|
defer registryLock.RUnlock()
|
|
|
|
for name, p := range registry {
|
|
log.Info("Initializing plugin: %s", name)
|
|
|
|
// Check license for licensed plugins
|
|
if lp, ok := p.(LicensedPlugin); ok {
|
|
if err := lp.ValidateLicense(ctx); err != nil {
|
|
log.Warn("Plugin %s license validation failed: %v", name, err)
|
|
// Continue but features may be limited
|
|
} else {
|
|
info := lp.LicenseInfo()
|
|
if info != nil && info.Valid {
|
|
log.Info("Plugin %s licensed: tier=%s", name, info.Tier)
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := p.Init(ctx); err != nil {
|
|
log.Error("Failed to initialize plugin %s: %v", name, err)
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ShutdownAll shuts down all registered plugins
|
|
func ShutdownAll(ctx context.Context) {
|
|
registryLock.RLock()
|
|
defer registryLock.RUnlock()
|
|
|
|
for name, p := range registry {
|
|
log.Info("Shutting down plugin: %s", name)
|
|
if err := p.Shutdown(ctx); err != nil {
|
|
log.Error("Failed to shutdown plugin %s: %v", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// RegisterModels registers database models from all plugins
|
|
func RegisterModels() []any {
|
|
registryLock.RLock()
|
|
defer registryLock.RUnlock()
|
|
|
|
var models []any
|
|
for _, p := range registry {
|
|
if dp, ok := p.(DatabasePlugin); ok {
|
|
models = append(models, dp.RegisterModels()...)
|
|
}
|
|
}
|
|
return models
|
|
}
|
|
|
|
// MigrateAll runs migrations for all database plugins
|
|
func MigrateAll(ctx context.Context, x *xorm.Engine) error {
|
|
registryLock.RLock()
|
|
defer registryLock.RUnlock()
|
|
|
|
for name, p := range registry {
|
|
if dp, ok := p.(DatabasePlugin); ok {
|
|
log.Info("Running migrations for plugin: %s", name)
|
|
if err := dp.Migrate(ctx, x); err != nil {
|
|
log.Error("Migration failed for plugin %s: %v", name, err)
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RegisterWebRoutes registers web UI routes from all plugins
|
|
func RegisterWebRoutes(m any) {
|
|
registryLock.RLock()
|
|
defer registryLock.RUnlock()
|
|
|
|
for name, p := range registry {
|
|
if wp, ok := p.(WebRoutesPlugin); ok {
|
|
log.Debug("Registering web routes for plugin: %s", name)
|
|
wp.RegisterWebRoutes(m)
|
|
}
|
|
}
|
|
}
|
|
|
|
// RegisterAPIRoutes registers API routes from all plugins
|
|
func RegisterAPIRoutes(m any) {
|
|
registryLock.RLock()
|
|
defer registryLock.RUnlock()
|
|
|
|
for name, p := range registry {
|
|
if ap, ok := p.(APIRoutesPlugin); ok {
|
|
log.Debug("Registering API routes for plugin: %s", name)
|
|
ap.RegisterAPIRoutes(m)
|
|
}
|
|
}
|
|
}
|
|
|
|
// RegisterRepoWebRoutes registers per-repository web routes from all plugins
|
|
func RegisterRepoWebRoutes(m any) {
|
|
registryLock.RLock()
|
|
defer registryLock.RUnlock()
|
|
|
|
for name, p := range registry {
|
|
if rp, ok := p.(RepoRoutesPlugin); ok {
|
|
log.Debug("Registering repo web routes for plugin: %s", name)
|
|
rp.RegisterRepoWebRoutes(m)
|
|
}
|
|
}
|
|
}
|
|
|
|
// RegisterRepoAPIRoutes registers per-repository API routes from all plugins
|
|
func RegisterRepoAPIRoutes(m any) {
|
|
registryLock.RLock()
|
|
defer registryLock.RUnlock()
|
|
|
|
for name, p := range registry {
|
|
if rp, ok := p.(RepoRoutesPlugin); ok {
|
|
log.Debug("Registering repo API routes for plugin: %s", name)
|
|
rp.RegisterRepoAPIRoutes(m)
|
|
}
|
|
}
|
|
}
|
|
|
|
// IsLicensed checks if a plugin is properly licensed
|
|
// For licensed plugins without a valid license, this returns true because
|
|
// we default to Solo tier (free) when no license is present
|
|
func IsLicensed(name string) bool {
|
|
registryLock.RLock()
|
|
defer registryLock.RUnlock()
|
|
|
|
p, exists := registry[name]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
_, ok := p.(LicensedPlugin)
|
|
if !ok {
|
|
return true // Non-licensed plugins are always "licensed"
|
|
}
|
|
|
|
// Licensed plugins default to Solo tier (free) even without a license file
|
|
// So they are always considered "licensed" if registered
|
|
return true
|
|
}
|
|
|
|
// GetLicenseInfo returns license info for a plugin
|
|
// If the plugin doesn't have a license set, returns the default Solo license
|
|
func GetLicenseInfo(name string) *LicenseInfo {
|
|
registryLock.RLock()
|
|
defer registryLock.RUnlock()
|
|
|
|
p, exists := registry[name]
|
|
if !exists {
|
|
return nil
|
|
}
|
|
|
|
lp, ok := p.(LicensedPlugin)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
info := lp.LicenseInfo()
|
|
if info == nil || !info.Valid {
|
|
// Default to Solo tier when no valid license
|
|
return DefaultSoloLicense()
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
// GetLicenseLimits returns the license limits for a plugin
|
|
func GetLicenseLimits(name string) *LicenseLimits {
|
|
info := GetLicenseInfo(name)
|
|
if info == nil {
|
|
return nil
|
|
}
|
|
return GetLimitsForTier(info.Tier)
|
|
}
|