mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-01 13:17:21 +02:00
Added #653
- Added dev mode plugin auto-reload - Optimized struct in plugin manager options
This commit is contained in:
parent
b9c609e413
commit
877692695e
@ -238,6 +238,10 @@ func RegisterPluginAPIs(authRouter *auth.RouterDef) {
|
|||||||
authRouter.HandleFunc("/api/plugins/store/resync", pluginManager.HandleResyncPluginList)
|
authRouter.HandleFunc("/api/plugins/store/resync", pluginManager.HandleResyncPluginList)
|
||||||
authRouter.HandleFunc("/api/plugins/store/install", pluginManager.HandleInstallPlugin)
|
authRouter.HandleFunc("/api/plugins/store/install", pluginManager.HandleInstallPlugin)
|
||||||
authRouter.HandleFunc("/api/plugins/store/uninstall", pluginManager.HandleUninstallPlugin)
|
authRouter.HandleFunc("/api/plugins/store/uninstall", pluginManager.HandleUninstallPlugin)
|
||||||
|
|
||||||
|
// Developer options
|
||||||
|
authRouter.HandleFunc("/api/plugins/developer/enableAutoReload", pluginManager.HandleEnableHotReload)
|
||||||
|
authRouter.HandleFunc("/api/plugins/developer/setAutoReloadInterval", pluginManager.HandleSetHotReloadInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the APIs for Auth functions, due to scoping issue some functions are defined here
|
// Register the APIs for Auth functions, due to scoping issue some functions are defined here
|
||||||
|
214
src/mod/plugins/development.go
Normal file
214
src/mod/plugins/development.go
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartHotReloadTicker starts the hot reload ticker
|
||||||
|
func (m *Manager) StartHotReloadTicker() error {
|
||||||
|
if m.pluginReloadTicker != nil {
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Hot reload ticker already started", nil)
|
||||||
|
return errors.New("hot reload ticker already started")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.pluginReloadTicker = time.NewTicker(time.Duration(m.Options.HotReloadInterval) * time.Second)
|
||||||
|
m.pluginReloadStop = make(chan bool)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.pluginReloadTicker.C:
|
||||||
|
err := m.UpdatePluginHashList(false)
|
||||||
|
if err != nil {
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Failed to update plugin hash list", err)
|
||||||
|
}
|
||||||
|
case <-m.pluginReloadStop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Hot reload ticker started", nil)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopHotReloadTicker stops the hot reload ticker
|
||||||
|
func (m *Manager) StopHotReloadTicker() error {
|
||||||
|
if m.pluginReloadTicker != nil {
|
||||||
|
m.pluginReloadStop <- true
|
||||||
|
m.pluginReloadTicker.Stop()
|
||||||
|
m.pluginReloadTicker = nil
|
||||||
|
m.pluginReloadStop = nil
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Hot reload ticker stopped", nil)
|
||||||
|
} else {
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Hot reload ticker already stopped", nil)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) InitPluginHashList() error {
|
||||||
|
return m.UpdatePluginHashList(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the plugin hash list and if there are change, reload the plugin
|
||||||
|
func (m *Manager) UpdatePluginHashList(noReload bool) error {
|
||||||
|
for pluginId, plugin := range m.LoadedPlugins {
|
||||||
|
//Get the plugin Entry point
|
||||||
|
pluginEntryPoint, err := m.GetPluginEntryPoint(plugin.RootDir)
|
||||||
|
if err != nil {
|
||||||
|
//Unable to get the entry point of the plugin
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(pluginEntryPoint)
|
||||||
|
if err != nil {
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Failed to open plugin entry point: "+pluginEntryPoint, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
//Calculate the hash of the file
|
||||||
|
hasher := sha256.New()
|
||||||
|
if _, err := file.Seek(0, 0); err != nil {
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Failed to seek plugin entry point: "+pluginEntryPoint, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(hasher, file); err != nil {
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Failed to copy plugin entry point: "+pluginEntryPoint, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hash := hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
m.pluginCheckMutex.Lock()
|
||||||
|
if m.PluginHash[pluginId] != hash {
|
||||||
|
m.PluginHash[pluginId] = hash
|
||||||
|
m.pluginCheckMutex.Unlock()
|
||||||
|
if !noReload {
|
||||||
|
//Plugin file changed, reload the plugin
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Plugin file changed, reloading plugin: "+pluginId, nil)
|
||||||
|
err := m.HotReloadPlugin(pluginId)
|
||||||
|
if err != nil {
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Failed to reload plugin: "+pluginId, err)
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Plugin reloaded: "+pluginId, nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Plugin hash generated for: "+pluginId, nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.pluginCheckMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload the plugin from file system
|
||||||
|
func (m *Manager) HotReloadPlugin(pluginId string) error {
|
||||||
|
//Check if the plugin is currently running
|
||||||
|
thisPlugin, err := m.GetPluginByID(pluginId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if thisPlugin.IsRunning() {
|
||||||
|
err = m.StopPlugin(pluginId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove the plugin from the loaded plugins list
|
||||||
|
m.loadedPluginsMutex.Lock()
|
||||||
|
if _, ok := m.LoadedPlugins[pluginId]; ok {
|
||||||
|
delete(m.LoadedPlugins, pluginId)
|
||||||
|
} else {
|
||||||
|
m.loadedPluginsMutex.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.loadedPluginsMutex.Unlock()
|
||||||
|
|
||||||
|
//Reload the plugin from disk, it should reload the plugin from latest version
|
||||||
|
m.ReloadPluginFromDisk()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Request handlers for developer options
|
||||||
|
*/
|
||||||
|
func (m *Manager) HandleEnableHotReload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
//Return the current status of hot reload
|
||||||
|
js, _ := json.Marshal(m.Options.EnableHotReload)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled, err := utils.PostBool(r, "enabled")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "enabled not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Options.EnableHotReload = enabled
|
||||||
|
if enabled {
|
||||||
|
//Start the hot reload ticker
|
||||||
|
err := m.StartHotReloadTicker()
|
||||||
|
if err != nil {
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Failed to start hot reload ticker", err)
|
||||||
|
utils.SendErrorResponse(w, "Failed to start hot reload ticker")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Hot reload enabled", nil)
|
||||||
|
} else {
|
||||||
|
//Stop the hot reload ticker
|
||||||
|
err := m.StopHotReloadTicker()
|
||||||
|
if err != nil {
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Failed to stop hot reload ticker", err)
|
||||||
|
utils.SendErrorResponse(w, "Failed to stop hot reload ticker")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Hot reload disabled", nil)
|
||||||
|
}
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) HandleSetHotReloadInterval(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
//Return the current status of hot reload
|
||||||
|
js, _ := json.Marshal(m.Options.HotReloadInterval)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
interval, err := utils.PostInt(r, "interval")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "interval not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if interval < 1 {
|
||||||
|
utils.SendErrorResponse(w, "interval must be at least 1 second")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Options.HotReloadInterval = interval
|
||||||
|
|
||||||
|
//Restart the hot reload ticker
|
||||||
|
if m.pluginReloadTicker != nil {
|
||||||
|
m.StopHotReloadTicker()
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
//Start the hot reload ticker again
|
||||||
|
m.StartHotReloadTicker()
|
||||||
|
}
|
||||||
|
m.Options.Logger.PrintAndLog("plugin-manager", "Hot reload interval set to "+strconv.Itoa(interval)+" sec", nil)
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
@ -11,11 +11,11 @@ import (
|
|||||||
// ListPluginGroups returns a map of plugin groups
|
// ListPluginGroups returns a map of plugin groups
|
||||||
func (m *Manager) ListPluginGroups() map[string][]string {
|
func (m *Manager) ListPluginGroups() map[string][]string {
|
||||||
pluginGroup := map[string][]string{}
|
pluginGroup := map[string][]string{}
|
||||||
m.Options.pluginGroupsMutex.RLock()
|
m.pluginGroupsMutex.RLock()
|
||||||
for k, v := range m.Options.PluginGroups {
|
for k, v := range m.Options.PluginGroups {
|
||||||
pluginGroup[k] = append([]string{}, v...)
|
pluginGroup[k] = append([]string{}, v...)
|
||||||
}
|
}
|
||||||
m.Options.pluginGroupsMutex.RUnlock()
|
m.pluginGroupsMutex.RUnlock()
|
||||||
return pluginGroup
|
return pluginGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,26 +32,26 @@ func (m *Manager) AddPluginToGroup(tag, pluginID string) error {
|
|||||||
return errors.New("plugin is not a router type plugin")
|
return errors.New("plugin is not a router type plugin")
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Options.pluginGroupsMutex.Lock()
|
m.pluginGroupsMutex.Lock()
|
||||||
//Check if the tag exists
|
//Check if the tag exists
|
||||||
_, ok = m.Options.PluginGroups[tag]
|
_, ok = m.Options.PluginGroups[tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
m.Options.PluginGroups[tag] = []string{pluginID}
|
m.Options.PluginGroups[tag] = []string{pluginID}
|
||||||
m.Options.pluginGroupsMutex.Unlock()
|
m.pluginGroupsMutex.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add the plugin to the group
|
//Add the plugin to the group
|
||||||
m.Options.PluginGroups[tag] = append(m.Options.PluginGroups[tag], pluginID)
|
m.Options.PluginGroups[tag] = append(m.Options.PluginGroups[tag], pluginID)
|
||||||
|
|
||||||
m.Options.pluginGroupsMutex.Unlock()
|
m.pluginGroupsMutex.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemovePluginFromGroup removes a plugin from a group
|
// RemovePluginFromGroup removes a plugin from a group
|
||||||
func (m *Manager) RemovePluginFromGroup(tag, pluginID string) error {
|
func (m *Manager) RemovePluginFromGroup(tag, pluginID string) error {
|
||||||
m.Options.pluginGroupsMutex.Lock()
|
m.pluginGroupsMutex.Lock()
|
||||||
defer m.Options.pluginGroupsMutex.Unlock()
|
defer m.pluginGroupsMutex.Unlock()
|
||||||
//Check if the tag exists
|
//Check if the tag exists
|
||||||
_, ok := m.Options.PluginGroups[tag]
|
_, ok := m.Options.PluginGroups[tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -72,8 +72,8 @@ func (m *Manager) RemovePluginFromGroup(tag, pluginID string) error {
|
|||||||
|
|
||||||
// RemovePluginGroup removes a plugin group
|
// RemovePluginGroup removes a plugin group
|
||||||
func (m *Manager) RemovePluginGroup(tag string) error {
|
func (m *Manager) RemovePluginGroup(tag string) error {
|
||||||
m.Options.pluginGroupsMutex.Lock()
|
m.pluginGroupsMutex.Lock()
|
||||||
defer m.Options.pluginGroupsMutex.Unlock()
|
defer m.pluginGroupsMutex.Unlock()
|
||||||
_, ok := m.Options.PluginGroups[tag]
|
_, ok := m.Options.PluginGroups[tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("tag not found")
|
return errors.New("tag not found")
|
||||||
@ -84,12 +84,12 @@ func (m *Manager) RemovePluginGroup(tag string) error {
|
|||||||
|
|
||||||
// SavePluginGroupsFromFile loads plugin groups from a file
|
// SavePluginGroupsFromFile loads plugin groups from a file
|
||||||
func (m *Manager) SavePluginGroupsToFile() error {
|
func (m *Manager) SavePluginGroupsToFile() error {
|
||||||
m.Options.pluginGroupsMutex.RLock()
|
m.pluginGroupsMutex.RLock()
|
||||||
pluginGroupsCopy := make(map[string][]string)
|
pluginGroupsCopy := make(map[string][]string)
|
||||||
for k, v := range m.Options.PluginGroups {
|
for k, v := range m.Options.PluginGroups {
|
||||||
pluginGroupsCopy[k] = append([]string{}, v...)
|
pluginGroupsCopy[k] = append([]string{}, v...)
|
||||||
}
|
}
|
||||||
m.Options.pluginGroupsMutex.RUnlock()
|
m.pluginGroupsMutex.RUnlock()
|
||||||
|
|
||||||
//Write to file
|
//Write to file
|
||||||
js, _ := json.Marshal(pluginGroupsCopy)
|
js, _ := json.Marshal(pluginGroupsCopy)
|
||||||
|
@ -47,15 +47,26 @@ func NewPluginManager(options *ManagerOptions) *Manager {
|
|||||||
//Create database table
|
//Create database table
|
||||||
options.Database.NewTable("plugins")
|
options.Database.NewTable("plugins")
|
||||||
|
|
||||||
return &Manager{
|
thisManager := &Manager{
|
||||||
LoadedPlugins: make(map[string]*Plugin),
|
LoadedPlugins: make(map[string]*Plugin),
|
||||||
tagPluginMap: sync.Map{},
|
tagPluginMap: sync.Map{},
|
||||||
tagPluginListMutex: sync.RWMutex{},
|
tagPluginListMutex: sync.RWMutex{},
|
||||||
tagPluginList: make(map[string][]*Plugin),
|
tagPluginList: make(map[string][]*Plugin),
|
||||||
Options: options,
|
Options: options,
|
||||||
|
PluginHash: make(map[string]string),
|
||||||
/* Internal */
|
/* Internal */
|
||||||
loadedPluginsMutex: sync.RWMutex{},
|
loadedPluginsMutex: sync.RWMutex{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check if hot reload is enabled
|
||||||
|
if options.EnableHotReload {
|
||||||
|
err := thisManager.StartHotReloadTicker()
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("plugin-manager", "Failed to start hot reload ticker", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return thisManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload all plugins from disk
|
// Reload all plugins from disk
|
||||||
@ -104,11 +115,16 @@ func (m *Manager) ReloadPluginFromDisk() {
|
|||||||
m.loadedPluginsMutex.Lock()
|
m.loadedPluginsMutex.Lock()
|
||||||
m.LoadedPlugins[thisPlugin.Spec.ID] = thisPlugin
|
m.LoadedPlugins[thisPlugin.Spec.ID] = thisPlugin
|
||||||
m.loadedPluginsMutex.Unlock()
|
m.loadedPluginsMutex.Unlock()
|
||||||
m.Log("Added new plugin: "+thisPlugin.Spec.Name, nil)
|
versionNumber := strconv.Itoa(thisPlugin.Spec.VersionMajor) + "." + strconv.Itoa(thisPlugin.Spec.VersionMinor) + "." + strconv.Itoa(thisPlugin.Spec.VersionPatch)
|
||||||
|
//Check if the plugin is enabled
|
||||||
|
m.Log("Found plugin: "+thisPlugin.Spec.Name+" (v"+versionNumber+")", nil)
|
||||||
|
|
||||||
// The default state of the plugin is disabled, so no need to start it
|
// The default state of the plugin is disabled, so no need to start it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Generate a hash list for plugins
|
||||||
|
m.InitPluginHashList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadPluginsFromDisk loads all plugins from the plugin directory
|
// LoadPluginsFromDisk loads all plugins from the plugin directory
|
||||||
@ -156,6 +172,8 @@ func (m *Manager) LoadPluginsFromDisk() error {
|
|||||||
//Generate the static forwarder radix tree
|
//Generate the static forwarder radix tree
|
||||||
m.UpdateTagsToPluginMaps()
|
m.UpdateTagsToPluginMaps()
|
||||||
|
|
||||||
|
//Generate a hash list for plugins
|
||||||
|
m.InitPluginHashList()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@ import (
|
|||||||
// This will only load the plugin tags to option.PluginGroups map
|
// This will only load the plugin tags to option.PluginGroups map
|
||||||
// to push the changes to runtime, call UpdateTagsToPluginMaps()
|
// to push the changes to runtime, call UpdateTagsToPluginMaps()
|
||||||
func (m *Manager) LoadPluginGroupsFromConfig() error {
|
func (m *Manager) LoadPluginGroupsFromConfig() error {
|
||||||
m.Options.pluginGroupsMutex.RLock()
|
m.pluginGroupsMutex.RLock()
|
||||||
defer m.Options.pluginGroupsMutex.RUnlock()
|
defer m.pluginGroupsMutex.RUnlock()
|
||||||
|
|
||||||
//Read the config file
|
//Read the config file
|
||||||
rawConfig, err := os.ReadFile(m.Options.PluginGroupsConfig)
|
rawConfig, err := os.ReadFile(m.Options.PluginGroupsConfig)
|
||||||
@ -39,8 +39,8 @@ func (m *Manager) LoadPluginGroupsFromConfig() error {
|
|||||||
|
|
||||||
// AddPluginToTag adds a plugin to a tag
|
// AddPluginToTag adds a plugin to a tag
|
||||||
func (m *Manager) AddPluginToTag(tag string, pluginID string) error {
|
func (m *Manager) AddPluginToTag(tag string, pluginID string) error {
|
||||||
m.Options.pluginGroupsMutex.RLock()
|
m.pluginGroupsMutex.RLock()
|
||||||
defer m.Options.pluginGroupsMutex.RUnlock()
|
defer m.pluginGroupsMutex.RUnlock()
|
||||||
|
|
||||||
//Check if the plugin exists
|
//Check if the plugin exists
|
||||||
_, err := m.GetPluginByID(pluginID)
|
_, err := m.GetPluginByID(pluginID)
|
||||||
@ -66,8 +66,8 @@ func (m *Manager) AddPluginToTag(tag string, pluginID string) error {
|
|||||||
// RemovePluginFromTag removes a plugin from a tag
|
// RemovePluginFromTag removes a plugin from a tag
|
||||||
func (m *Manager) RemovePluginFromTag(tag string, pluginID string) error {
|
func (m *Manager) RemovePluginFromTag(tag string, pluginID string) error {
|
||||||
// Check if the plugin exists in Options.PluginGroups
|
// Check if the plugin exists in Options.PluginGroups
|
||||||
m.Options.pluginGroupsMutex.RLock()
|
m.pluginGroupsMutex.RLock()
|
||||||
defer m.Options.pluginGroupsMutex.RUnlock()
|
defer m.pluginGroupsMutex.RUnlock()
|
||||||
pluginList, ok := m.Options.PluginGroups[tag]
|
pluginList, ok := m.Options.PluginGroups[tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
@ -91,8 +91,8 @@ func (m *Manager) RemovePluginFromTag(tag string, pluginID string) error {
|
|||||||
|
|
||||||
// savePluginTagMap saves the plugin tag map to the config file
|
// savePluginTagMap saves the plugin tag map to the config file
|
||||||
func (m *Manager) savePluginTagMap() error {
|
func (m *Manager) savePluginTagMap() error {
|
||||||
m.Options.pluginGroupsMutex.RLock()
|
m.pluginGroupsMutex.RLock()
|
||||||
defer m.Options.pluginGroupsMutex.RUnlock()
|
defer m.pluginGroupsMutex.RUnlock()
|
||||||
|
|
||||||
js, _ := json.Marshal(m.Options.PluginGroups)
|
js, _ := json.Marshal(m.Options.PluginGroups)
|
||||||
return os.WriteFile(m.Options.PluginGroupsConfig, js, 0644)
|
return os.WriteFile(m.Options.PluginGroupsConfig, js, 0644)
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
@ -45,8 +46,9 @@ type ManagerOptions struct {
|
|||||||
Database *database.Database `json:"-"`
|
Database *database.Database `json:"-"`
|
||||||
Logger *logger.Logger `json:"-"`
|
Logger *logger.Logger `json:"-"`
|
||||||
|
|
||||||
/* Internal */
|
/* Development */
|
||||||
pluginGroupsMutex sync.RWMutex //Mutex for the pluginGroups
|
EnableHotReload bool //Check if the plugin file is changed and reload the plugin automatically
|
||||||
|
HotReloadInterval int //The interval for checking the plugin file change, in seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@ -56,6 +58,12 @@ type Manager struct {
|
|||||||
tagPluginList map[string][]*Plugin //Storing the plugin list for each tag, only concurrent READ is allowed
|
tagPluginList map[string][]*Plugin //Storing the plugin list for each tag, only concurrent READ is allowed
|
||||||
Options *ManagerOptions
|
Options *ManagerOptions
|
||||||
|
|
||||||
|
PluginHash map[string]string //The hash of the plugin file, used to check if the plugin file is changed
|
||||||
|
|
||||||
/* Internal */
|
/* Internal */
|
||||||
loadedPluginsMutex sync.RWMutex //Mutex for the loadedPlugins
|
loadedPluginsMutex sync.RWMutex //Mutex for the loadedPlugins
|
||||||
|
pluginGroupsMutex sync.RWMutex //Mutex for the pluginGroups
|
||||||
|
pluginCheckMutex sync.RWMutex //Mutex for the plugin hash
|
||||||
|
pluginReloadTicker *time.Ticker //Ticker for the plugin reload
|
||||||
|
pluginReloadStop chan bool //Channel to stop the plugin reload ticker
|
||||||
}
|
}
|
||||||
|
23
src/start.go
23
src/start.go
@ -307,21 +307,26 @@ func startupSequence() {
|
|||||||
pluginFolder := *path_plugin
|
pluginFolder := *path_plugin
|
||||||
pluginFolder = strings.TrimSuffix(pluginFolder, "/")
|
pluginFolder = strings.TrimSuffix(pluginFolder, "/")
|
||||||
pluginManager = plugins.NewPluginManager(&plugins.ManagerOptions{
|
pluginManager = plugins.NewPluginManager(&plugins.ManagerOptions{
|
||||||
PluginDir: pluginFolder,
|
PluginDir: pluginFolder,
|
||||||
SystemConst: &zoraxy_plugin.RuntimeConstantValue{
|
|
||||||
ZoraxyVersion: SYSTEM_VERSION,
|
|
||||||
ZoraxyUUID: nodeUUID,
|
|
||||||
DevelopmentBuild: *development_build,
|
|
||||||
},
|
|
||||||
PluginStoreURLs: []string{
|
|
||||||
"https://raw.githubusercontent.com/aroz-online/zoraxy-official-plugins/refs/heads/main/directories/index.json",
|
|
||||||
},
|
|
||||||
Database: sysdb,
|
Database: sysdb,
|
||||||
Logger: SystemWideLogger,
|
Logger: SystemWideLogger,
|
||||||
PluginGroupsConfig: CONF_PLUGIN_GROUPS,
|
PluginGroupsConfig: CONF_PLUGIN_GROUPS,
|
||||||
CSRFTokenGen: func(r *http.Request) string {
|
CSRFTokenGen: func(r *http.Request) string {
|
||||||
return csrf.Token(r)
|
return csrf.Token(r)
|
||||||
},
|
},
|
||||||
|
SystemConst: &zoraxy_plugin.RuntimeConstantValue{
|
||||||
|
ZoraxyVersion: SYSTEM_VERSION,
|
||||||
|
ZoraxyUUID: nodeUUID,
|
||||||
|
DevelopmentBuild: *development_build,
|
||||||
|
},
|
||||||
|
/* Plugin Store URLs */
|
||||||
|
PluginStoreURLs: []string{
|
||||||
|
"https://raw.githubusercontent.com/aroz-online/zoraxy-official-plugins/refs/heads/main/directories/index.json",
|
||||||
|
//TO BE ADDED
|
||||||
|
},
|
||||||
|
/* Developer Options */
|
||||||
|
EnableHotReload: *development_build, //Default to true if development build
|
||||||
|
HotReloadInterval: 5, //seconds
|
||||||
})
|
})
|
||||||
|
|
||||||
//Sync latest plugin list from the plugin store
|
//Sync latest plugin list from the plugin store
|
||||||
|
@ -185,6 +185,33 @@
|
|||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<div class="ui basic segment advanceoptions">
|
||||||
|
<div class="ui accordion advanceSettings">
|
||||||
|
<div class="title">
|
||||||
|
<i class="dropdown icon"></i>
|
||||||
|
Developer Settings
|
||||||
|
</div>
|
||||||
|
<div class="content ui form">
|
||||||
|
<div class="ui inverted message" style="margin-top: 0.6em;">
|
||||||
|
<div class="header">Developer Only</div>
|
||||||
|
<p>These functions are intended for developers only. Enabling them may add latency to plugin loading & routing. Proceed with caution.<br>
|
||||||
|
<b>Tips: You can start zoraxy with -dev=true to enable auto-reload when start</b></p>
|
||||||
|
</div>
|
||||||
|
<div id="enablePluginAutoReload" class="ui toggle notloopbackOnly tlsEnabledOnly checkbox" style="margin-top: 0.6em;">
|
||||||
|
<input id="enable_plugin_auto_reload" type="checkbox">
|
||||||
|
<label>Enable Plugin Auto Reload<br>
|
||||||
|
<small>Automatic reload plugin when the plugin binary changed</small></label>
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
<div class="field" style="max-width: 50%;margin-bottom: 0px;">
|
||||||
|
<label>Check Interval</label>
|
||||||
|
<input type="number" id="autoreload-interval" placeholder="Check Interval" min="1" max="60" step="1" value="1">
|
||||||
|
</div>
|
||||||
|
<small>Specify the interval (in seconds) for checking plugin changes. <br>Minimum is 1 second, maximum is 60 seconds.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<button class="ui basic violet button" onclick="openPluginStore();"><i class="download icon"></i>Plugin Store (Experimental)</button>
|
<button class="ui basic violet button" onclick="openPluginStore();"><i class="download icon"></i>Plugin Store (Experimental)</button>
|
||||||
</div>
|
</div>
|
||||||
@ -592,6 +619,95 @@ function uninstallPlugin(pluginId, pluginName, btn=undefined) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Developer Settings */
|
||||||
|
|
||||||
|
function initDeveloperSettings() {
|
||||||
|
// Fetch the auto reload status
|
||||||
|
$.get('/api/plugins/developer/enableAutoReload', function(data) {
|
||||||
|
if (data.error != undefined) {
|
||||||
|
msgbox(data.error, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the checkbox for Plugin Auto Reload
|
||||||
|
if (data == true) {
|
||||||
|
$("#enablePluginAutoReload").checkbox('set checked');
|
||||||
|
} else {
|
||||||
|
$("#enablePluginAutoReload").checkbox('set unchecked');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the auto reload interval
|
||||||
|
$.get('/api/plugins/developer/setAutoReloadInterval', function(data) {
|
||||||
|
if (data.error != undefined) {
|
||||||
|
msgbox(data.error, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the input value for Auto Reload Interval
|
||||||
|
if (data) {
|
||||||
|
$("#autoreload-interval").val(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEventsToDeveloperSettings();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindEventsToDeveloperSettings(){
|
||||||
|
$("#enablePluginAutoReload").checkbox({
|
||||||
|
onChecked: function() {
|
||||||
|
$.cjax({
|
||||||
|
url: '/api/plugins/developer/enableAutoReload',
|
||||||
|
type: 'POST',
|
||||||
|
data: { "enabled": true },
|
||||||
|
success: function(data) {
|
||||||
|
if (data.error != undefined) {
|
||||||
|
msgbox(data.error, false);
|
||||||
|
} else {
|
||||||
|
msgbox("Plugin Auto Reload enabled", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onUnchecked: function() {
|
||||||
|
$.cjax({
|
||||||
|
url: '/api/plugins/developer/enableAutoReload',
|
||||||
|
type: 'POST',
|
||||||
|
data: { "enabled": false },
|
||||||
|
success: function(data) {
|
||||||
|
if (data.error != undefined) {
|
||||||
|
msgbox(data.error, false);
|
||||||
|
} else {
|
||||||
|
msgbox("Plugin Auto Reload disabled", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#autoreload-interval").on("change", function() {
|
||||||
|
const interval = $(this).val();
|
||||||
|
if (interval < 1 || interval > 60) {
|
||||||
|
msgbox("Interval must be between 1 and 60 seconds", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.cjax({
|
||||||
|
url: '/api/plugins/developer/setAutoReloadInterval',
|
||||||
|
type: 'POST',
|
||||||
|
data: { "interval": interval },
|
||||||
|
success: function(data) {
|
||||||
|
if (data.error != undefined) {
|
||||||
|
msgbox(data.error, false);
|
||||||
|
} else {
|
||||||
|
msgbox("Auto Reload Interval updated to " + interval + " seconds", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initDeveloperSettings();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,28 +58,28 @@
|
|||||||
</div>
|
</div>
|
||||||
<button class="ui basic button" onclick="forceResyncPlugins();"><i class="ui green refresh icon"></i> Update Plugin List</button>
|
<button class="ui basic button" onclick="forceResyncPlugins();"><i class="ui green refresh icon"></i> Update Plugin List</button>
|
||||||
<!-- <div class="ui divider"></div>
|
<!-- <div class="ui divider"></div>
|
||||||
<div class="ui basic segment advanceoptions">
|
<div class="ui basic segment advanceoptions">
|
||||||
<div class="ui accordion advanceSettings">
|
<div class="ui accordion advanceSettings">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<i class="dropdown icon"></i>
|
<i class="dropdown icon"></i>
|
||||||
Advance Settings
|
Advance Settings
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<p>Plugin Store URLs</p>
|
<p>Plugin Store URLs</p>
|
||||||
<div class="ui form">
|
<div class="ui form">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<textarea id="pluginStoreURLs" rows="5"></textarea>
|
<textarea id="pluginStoreURLs" rows="5"></textarea>
|
||||||
<label>Enter plugin store URLs, separating each URL with a new line</label>
|
<label>Enter plugin store URLs, separating each URL with a new line</label>
|
||||||
</div>
|
</div>
|
||||||
<button class="ui basic button" onclick="savePluginStoreURLs()">
|
<button class="ui basic button" onclick="savePluginStoreURLs()">
|
||||||
<i class="ui green save icon"></i>Save
|
<i class="ui green save icon"></i>Save
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
-->
|
||||||
-->
|
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div class="field" >
|
<div class="field" >
|
||||||
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user