mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
217 lines
5.3 KiB
Go
217 lines
5.3 KiB
Go
package nginx
|
|
|
|
import (
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/elliotchance/orderedmap/v3"
|
|
)
|
|
|
|
const (
|
|
ModuleStream = "stream"
|
|
)
|
|
|
|
type Module struct {
|
|
Name string `json:"name"`
|
|
Params string `json:"params,omitempty"`
|
|
Dynamic bool `json:"dynamic"`
|
|
Loaded bool `json:"loaded"`
|
|
}
|
|
|
|
// modulesCache stores the cached modules list and related metadata
|
|
var (
|
|
modulesCache = orderedmap.NewOrderedMap[string, Module]()
|
|
modulesCacheLock sync.RWMutex
|
|
lastPIDPath string
|
|
lastPIDModTime time.Time
|
|
lastPIDSize int64
|
|
)
|
|
|
|
// clearModulesCache clears the modules cache
|
|
func clearModulesCache() {
|
|
modulesCacheLock.Lock()
|
|
defer modulesCacheLock.Unlock()
|
|
|
|
modulesCache = orderedmap.NewOrderedMap[string, Module]()
|
|
lastPIDPath = ""
|
|
lastPIDModTime = time.Time{}
|
|
lastPIDSize = 0
|
|
}
|
|
|
|
// isPIDFileChanged checks if the PID file has changed since the last check
|
|
func isPIDFileChanged() bool {
|
|
pidPath := GetPIDPath()
|
|
|
|
// If PID path has changed, consider it changed
|
|
if pidPath != lastPIDPath {
|
|
return true
|
|
}
|
|
|
|
// If Nginx is not running, consider PID changed
|
|
if !IsNginxRunning() {
|
|
return true
|
|
}
|
|
|
|
// Check if PID file has changed (modification time or size)
|
|
fileInfo, err := os.Stat(pidPath)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
|
|
modTime := fileInfo.ModTime()
|
|
size := fileInfo.Size()
|
|
|
|
return modTime != lastPIDModTime || size != lastPIDSize
|
|
}
|
|
|
|
// updatePIDFileInfo updates the stored PID file information
|
|
func updatePIDFileInfo() {
|
|
pidPath := GetPIDPath()
|
|
|
|
if fileInfo, err := os.Stat(pidPath); err == nil {
|
|
modulesCacheLock.Lock()
|
|
defer modulesCacheLock.Unlock()
|
|
|
|
lastPIDPath = pidPath
|
|
lastPIDModTime = fileInfo.ModTime()
|
|
lastPIDSize = fileInfo.Size()
|
|
}
|
|
}
|
|
|
|
// updateDynamicModulesStatus checks which dynamic modules are actually loaded in the running Nginx
|
|
func updateDynamicModulesStatus() {
|
|
modulesCacheLock.Lock()
|
|
defer modulesCacheLock.Unlock()
|
|
|
|
// If cache is empty, there's nothing to update
|
|
if modulesCache.Len() == 0 {
|
|
return
|
|
}
|
|
|
|
// Get nginx -T output to check for loaded modules
|
|
out := getNginxT()
|
|
if out == "" {
|
|
return
|
|
}
|
|
|
|
// Regular expression to find loaded dynamic modules in nginx -T output
|
|
// Look for lines like "load_module modules/ngx_http_image_filter_module.so;"
|
|
loadModuleRe := regexp.MustCompile(`load_module\s+(?:modules/|/.*/)([a-zA-Z0-9_-]+)\.so;`)
|
|
matches := loadModuleRe.FindAllStringSubmatch(out, -1)
|
|
|
|
// Create a map of loaded dynamic modules
|
|
loadedDynamicModules := make(map[string]bool)
|
|
for _, match := range matches {
|
|
if len(match) > 1 {
|
|
// Extract the module name without path and suffix
|
|
moduleName := match[1]
|
|
// Some normalization to match format in GetModules
|
|
moduleName = strings.TrimPrefix(moduleName, "ngx_")
|
|
moduleName = strings.TrimSuffix(moduleName, "_module")
|
|
loadedDynamicModules[moduleName] = true
|
|
}
|
|
}
|
|
|
|
// Update the status for each module in the cache
|
|
for key := range modulesCache.Keys() {
|
|
// If the module is already marked as dynamic, check if it's actually loaded
|
|
if loadedDynamicModules[key] {
|
|
modulesCache.Set(key, Module{
|
|
Name: key,
|
|
Dynamic: true,
|
|
Loaded: true,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func GetModules() *orderedmap.OrderedMap[string, Module] {
|
|
modulesCacheLock.RLock()
|
|
cachedModules := modulesCache
|
|
modulesCacheLock.RUnlock()
|
|
|
|
// If we have cached modules and PID file hasn't changed, return cached modules
|
|
if cachedModules.Len() > 0 && !isPIDFileChanged() {
|
|
return cachedModules
|
|
}
|
|
|
|
// If PID has changed or we don't have cached modules, get fresh modules
|
|
out := getNginxV()
|
|
|
|
// Regular expression to find built-in modules in nginx -V output
|
|
builtinRe := regexp.MustCompile(`--with-([a-zA-Z0-9_-]+)(_module)?`)
|
|
builtinMatches := builtinRe.FindAllStringSubmatch(out, -1)
|
|
|
|
// Extract built-in module names from matches and put in map for quick lookup
|
|
moduleMap := make(map[string]bool)
|
|
for _, match := range builtinMatches {
|
|
if len(match) > 1 {
|
|
module := match[1]
|
|
moduleMap[module] = true
|
|
}
|
|
}
|
|
|
|
// Regular expression to find dynamic modules in nginx -V output
|
|
dynamicRe := regexp.MustCompile(`--with-([a-zA-Z0-9_-]+)(_module)?=dynamic`)
|
|
dynamicMatches := dynamicRe.FindAllStringSubmatch(out, -1)
|
|
|
|
// Extract dynamic module names from matches
|
|
for _, match := range dynamicMatches {
|
|
if len(match) > 1 {
|
|
module := match[1]
|
|
// Only add if not already in list (to avoid duplicates)
|
|
if !moduleMap[module] {
|
|
moduleMap[module] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update cache
|
|
modulesCacheLock.Lock()
|
|
modulesCache = orderedmap.NewOrderedMap[string, Module]()
|
|
for module := range moduleMap {
|
|
// Mark modules as built-in (loaded) or dynamic (potentially not loaded)
|
|
if strings.Contains(out, "--with-"+module+"=dynamic") {
|
|
modulesCache.Set(module, Module{
|
|
Name: module,
|
|
Dynamic: true,
|
|
Loaded: true,
|
|
})
|
|
} else {
|
|
modulesCache.Set(module, Module{
|
|
Name: module,
|
|
Dynamic: true,
|
|
})
|
|
}
|
|
}
|
|
modulesCacheLock.Unlock()
|
|
|
|
// Update dynamic modules status by checking if they're actually loaded
|
|
updateDynamicModulesStatus()
|
|
|
|
// Update PID file info
|
|
updatePIDFileInfo()
|
|
|
|
return modulesCache
|
|
}
|
|
|
|
// IsModuleLoaded checks if a module is loaded in Nginx
|
|
func IsModuleLoaded(module string) bool {
|
|
// Ensure modules are in the cache
|
|
if modulesCache.Len() == 0 {
|
|
GetModules()
|
|
}
|
|
|
|
modulesCacheLock.RLock()
|
|
defer modulesCacheLock.RUnlock()
|
|
|
|
status, exists := modulesCache.Get(module)
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
return status.Loaded
|
|
}
|