package data import ( "path/filepath" "sync" "time" "github.com/fsnotify/fsnotify" ) // watchConfig refreshes the in-memory config snapshot on file changes, while // /system/reloadSystem remains responsible // for rebuilding database, Redis, storage, and scheduled tasks. func (d *Data) watchConfig() func() { logger := d.logger() configPath := d.runtime.ConfigPath() if configPath == "" { return func() {} } absolute, err := filepath.Abs(configPath) if err != nil { logger.Error("resolve config watch path", "mod", "system", "error", err) return func() {} } watcher, err := fsnotify.NewWatcher() if err != nil { logger.Error("create config watcher", "mod", "system", "error", err) return func() {} } if err = watcher.Add(filepath.Dir(absolute)); err != nil { logger.Error("watch config directory", "mod", "system", "error", err) _ = watcher.Close() return func() {} } done := make(chan struct{}) var once sync.Once go func() { var timer *time.Timer for { select { case event, ok := <-watcher.Events: if !ok || filepath.Clean(event.Name) != absolute || event.Op&(fsnotify.Write|fsnotify.Create|fsnotify.Rename) == 0 { continue } if timer != nil { timer.Stop() } timer = time.AfterFunc(100*time.Millisecond, func() { next, loadErr := readBootstrap(absolute) if loadErr != nil { logger.Error("reload changed config", "mod", "system", "error", loadErr) return } if next.Data == nil || next.Admin == nil { logger.Error("reload changed config: data and admin configuration are required", "mod", "system") return } if current := d.runtime.Admin(); current != nil { next.Admin.Storage = current.Storage next.Admin.Email = current.Email next.Admin.Mq = current.Mq next.Admin.Websocket = current.Websocket } next.Admin.ConfigPath = absolute d.runtime.Replace(next.Data, next.Admin) logger.Info("config file changed", "mod", "system", "path", absolute) }) case watchErr, ok := <-watcher.Errors: if ok { logger.Error("config watcher error", "mod", "system", "error", watchErr) } case <-done: if timer != nil { timer.Stop() } return } } }() return func() { once.Do(func() { close(done) _ = watcher.Close() }) } }