mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 04:20:50 +08:00
136 lines
4.0 KiB
Go
136 lines
4.0 KiB
Go
// config_reload.go implements debounced configuration hot reload.
|
|
// It detects material changes and reloads clients when the config changes.
|
|
package watcher
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"os"
|
|
"reflect"
|
|
"time"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/diff"
|
|
"gopkg.in/yaml.v3"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func (w *Watcher) stopConfigReloadTimer() {
|
|
w.configReloadMu.Lock()
|
|
if w.configReloadTimer != nil {
|
|
w.configReloadTimer.Stop()
|
|
w.configReloadTimer = nil
|
|
}
|
|
w.configReloadMu.Unlock()
|
|
}
|
|
|
|
func (w *Watcher) scheduleConfigReload() {
|
|
w.configReloadMu.Lock()
|
|
defer w.configReloadMu.Unlock()
|
|
if w.configReloadTimer != nil {
|
|
w.configReloadTimer.Stop()
|
|
}
|
|
w.configReloadTimer = time.AfterFunc(configReloadDebounce, func() {
|
|
w.configReloadMu.Lock()
|
|
w.configReloadTimer = nil
|
|
w.configReloadMu.Unlock()
|
|
w.reloadConfigIfChanged()
|
|
})
|
|
}
|
|
|
|
func (w *Watcher) reloadConfigIfChanged() {
|
|
data, err := os.ReadFile(w.configPath)
|
|
if err != nil {
|
|
log.Errorf("failed to read config file for hash check: %v", err)
|
|
return
|
|
}
|
|
if len(data) == 0 {
|
|
log.Debugf("ignoring empty config file write event")
|
|
return
|
|
}
|
|
sum := sha256.Sum256(data)
|
|
newHash := hex.EncodeToString(sum[:])
|
|
|
|
w.clientsMutex.RLock()
|
|
currentHash := w.lastConfigHash
|
|
w.clientsMutex.RUnlock()
|
|
|
|
if currentHash != "" && currentHash == newHash {
|
|
log.Debugf("config file content unchanged (hash match), skipping reload")
|
|
return
|
|
}
|
|
log.Infof("config file changed, reloading: %s", w.configPath)
|
|
if w.reloadConfig() {
|
|
finalHash := newHash
|
|
if updatedData, errRead := os.ReadFile(w.configPath); errRead == nil && len(updatedData) > 0 {
|
|
sumUpdated := sha256.Sum256(updatedData)
|
|
finalHash = hex.EncodeToString(sumUpdated[:])
|
|
} else if errRead != nil {
|
|
log.WithError(errRead).Debug("failed to compute updated config hash after reload")
|
|
}
|
|
w.clientsMutex.Lock()
|
|
w.lastConfigHash = finalHash
|
|
w.clientsMutex.Unlock()
|
|
w.persistConfigAsync()
|
|
}
|
|
}
|
|
|
|
func (w *Watcher) reloadConfig() bool {
|
|
log.Debug("=========================== CONFIG RELOAD ============================")
|
|
log.Debugf("starting config reload from: %s", w.configPath)
|
|
|
|
newConfig, errLoadConfig := config.LoadConfig(w.configPath)
|
|
if errLoadConfig != nil {
|
|
log.Errorf("failed to reload config: %v", errLoadConfig)
|
|
return false
|
|
}
|
|
|
|
if w.mirroredAuthDir != "" {
|
|
newConfig.AuthDir = w.mirroredAuthDir
|
|
} else {
|
|
if resolvedAuthDir, errResolveAuthDir := util.ResolveAuthDir(newConfig.AuthDir); errResolveAuthDir != nil {
|
|
log.Errorf("failed to resolve auth directory from config: %v", errResolveAuthDir)
|
|
} else {
|
|
newConfig.AuthDir = resolvedAuthDir
|
|
}
|
|
}
|
|
|
|
w.clientsMutex.Lock()
|
|
var oldConfig *config.Config
|
|
_ = yaml.Unmarshal(w.oldConfigYaml, &oldConfig)
|
|
w.oldConfigYaml, _ = yaml.Marshal(newConfig)
|
|
w.config = newConfig
|
|
w.clientsMutex.Unlock()
|
|
|
|
var affectedOAuthProviders []string
|
|
if oldConfig != nil {
|
|
_, affectedOAuthProviders = diff.DiffOAuthExcludedModelChanges(oldConfig.OAuthExcludedModels, newConfig.OAuthExcludedModels)
|
|
}
|
|
|
|
util.SetLogLevel(newConfig)
|
|
if oldConfig != nil && oldConfig.Debug != newConfig.Debug {
|
|
log.Debugf("log level updated - debug mode changed from %t to %t", oldConfig.Debug, newConfig.Debug)
|
|
}
|
|
|
|
if oldConfig != nil {
|
|
details := diff.BuildConfigChangeDetails(oldConfig, newConfig)
|
|
if len(details) > 0 {
|
|
log.Debugf("config changes detected:")
|
|
for _, d := range details {
|
|
log.Debugf(" %s", d)
|
|
}
|
|
} else {
|
|
log.Debugf("no material config field changes detected")
|
|
}
|
|
}
|
|
|
|
authDirChanged := oldConfig == nil || oldConfig.AuthDir != newConfig.AuthDir
|
|
forceAuthRefresh := oldConfig != nil && (oldConfig.ForceModelPrefix != newConfig.ForceModelPrefix || !reflect.DeepEqual(oldConfig.OAuthModelAlias, newConfig.OAuthModelAlias))
|
|
|
|
log.Infof("config successfully reloaded, triggering client reload")
|
|
w.reloadClients(authDirChanged, affectedOAuthProviders, forceAuthRefresh)
|
|
return true
|
|
}
|