2
0
Files
gitcaddy-server/modules/plugins/registry.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

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