feat(config): improve config reload handling and introduce async management save hook

- Refactored `ConfigReloadHook` to use `reloadConfigFromWatcher` for consistency.
- Added async `reloadConfigAfterManagementSaveAsync` to handle post-save operations.
- Introduced `ReloadConfigIfChanged` in watcher for manual trigger support.
- Enhanced config reload paths to separate auth synthesis from standard updates.
- Updated `applyConfigUpdate` logic to allow more granular reload behaviors.

Closes: #3235
This commit is contained in:
Luis Pater
2026-06-19 03:35:09 +08:00
parent 052f1934b8
commit 1d0551a991
6 changed files with 49 additions and 4 deletions
@@ -411,7 +411,13 @@ func (h *Handler) persistLocked(c *gin.Context) bool {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to save config: %v", err)})
return false
}
snapshot := h.reloadSnapshotConfigLocked()
c.JSON(http.StatusOK, gin.H{"status": "ok"})
var reqCtx context.Context
if c != nil && c.Request != nil {
reqCtx = c.Request.Context()
}
h.reloadConfigAfterManagementSaveAsync(reqCtx, snapshot)
return true
}
+8
View File
@@ -40,6 +40,14 @@ func (w *Watcher) scheduleConfigReload() {
})
}
// ReloadConfigIfChanged runs the same config reload path used by filesystem events.
func (w *Watcher) ReloadConfigIfChanged() {
if w == nil {
return
}
w.reloadConfigIfChanged()
}
func (w *Watcher) reloadConfigIfChanged() {
data, err := os.ReadFile(w.configPath)
if err != nil {
+2 -2
View File
@@ -289,8 +289,8 @@ func (b *Builder) Build() (*Service, error) {
service.serverOptions = append(service.serverOptions,
api.WithPostAuthPersistHook(service.runtimeAuthSyncHook()),
api.WithPluginHost(pluginHost),
api.WithConfigReloadHook(func(ctx context.Context, cfg *config.Config) {
service.applyConfigUpdate(cfg)
api.WithConfigReloadHook(func(_ context.Context, _ *config.Config) {
service.reloadConfigFromWatcher()
}),
)
return service, nil
+20 -2
View File
@@ -1097,6 +1097,14 @@ func (s *Service) tryRegisterPluginModelsForAuth(ctx context.Context, a *coreaut
}
func (s *Service) applyConfigUpdate(newCfg *config.Config) {
s.applyConfigUpdateWithAuthSynthesis(newCfg, true)
}
func (s *Service) applyWatcherConfigUpdate(newCfg *config.Config) {
s.applyConfigUpdateWithAuthSynthesis(newCfg, false)
}
func (s *Service) applyConfigUpdateWithAuthSynthesis(newCfg *config.Config, synthesizeConfigAuths bool) {
if s == nil {
return
}
@@ -1190,10 +1198,19 @@ func (s *Service) applyConfigUpdate(newCfg *config.Config) {
auths: auths,
})
ctx := coreauth.WithSkipPersist(context.Background())
s.registerConfigAPIKeyAuths(ctx, newCfg)
if synthesizeConfigAuths {
s.registerConfigAPIKeyAuths(ctx, newCfg)
}
s.syncPluginRuntime(ctx)
}
func (s *Service) reloadConfigFromWatcher() bool {
if s == nil || s.watcher == nil {
return false
}
return s.watcher.ReloadConfigIfChanged()
}
func (s *Service) registerConfigAPIKeyAuths(ctx context.Context, cfg *config.Config) {
if s == nil || s.coreManager == nil || cfg == nil {
return
@@ -1442,6 +1459,7 @@ func (s *Service) Run(ctx context.Context) error {
if errLoad := s.coreManager.Load(ctx); errLoad != nil {
log.Warnf("failed to load auth store: %v", errLoad)
}
s.registerConfigAPIKeyAuths(coreauth.WithSkipPersist(ctx), s.cfg)
}
if !homeEnabled {
@@ -1532,7 +1550,7 @@ func (s *Service) Run(ctx context.Context) error {
if !homeEnabled {
var watcherWrapper *WatcherWrapper
reloadCallback := func(newCfg *config.Config) { s.applyConfigUpdate(newCfg) }
reloadCallback := func(newCfg *config.Config) { s.applyWatcherConfigUpdate(newCfg) }
watcherWrapper, errCreate := s.watcherFactory(s.configPath, s.cfg.AuthDir, reloadCallback)
if errCreate != nil {
+10
View File
@@ -97,6 +97,7 @@ type WatcherWrapper struct {
dispatchRuntimeUpdate func(update watcher.AuthUpdate) bool
dispatchPersistedAuth func(update watcher.AuthUpdate) bool
setPluginAuthParser func(parser PluginAuthParser)
reloadConfigIfChanged func()
}
// Start proxies to the underlying watcher Start implementation.
@@ -123,6 +124,15 @@ func (w *WatcherWrapper) SetConfig(cfg *config.Config) {
w.setConfig(cfg)
}
// ReloadConfigIfChanged asks the underlying watcher to reload config from disk.
func (w *WatcherWrapper) ReloadConfigIfChanged() bool {
if w == nil || w.reloadConfigIfChanged == nil {
return false
}
w.reloadConfigIfChanged()
return true
}
// SetPluginAuthParser updates the plugin auth parser used by the watcher.
func (w *WatcherWrapper) SetPluginAuthParser(parser PluginAuthParser) {
if w == nil || w.setPluginAuthParser == nil {
+3
View File
@@ -37,5 +37,8 @@ func defaultWatcherFactory(configPath, authDir string, reload func(*config.Confi
setPluginAuthParser: func(parser PluginAuthParser) {
w.SetPluginAuthParser(parser)
},
reloadConfigIfChanged: func() {
w.ReloadConfigIfChanged()
},
}, nil
}