package config import ( "fmt" "log" "os" "sync" "provisioning/internal/model" "github.com/fsnotify/fsnotify" "gopkg.in/yaml.v3" ) type serverTypesFile struct { ServerTypes map[string]model.ServerType `yaml:"server_types"` } type ServerTypeRegistry struct { mu sync.RWMutex types map[string]model.ServerType path string } func LoadServerTypes(path string) (*ServerTypeRegistry, error) { r := &ServerTypeRegistry{path: path} if err := r.load(); err != nil { return nil, err } return r, nil } func (r *ServerTypeRegistry) Get(key string) (model.ServerType, bool) { r.mu.RLock() defer r.mu.RUnlock() st, ok := r.types[key] return st, ok } func (r *ServerTypeRegistry) List() []model.ServerType { r.mu.RLock() defer r.mu.RUnlock() out := make([]model.ServerType, 0, len(r.types)) for _, st := range r.types { out = append(out, st) } return out } func (r *ServerTypeRegistry) Keys() []string { r.mu.RLock() defer r.mu.RUnlock() out := make([]string, 0, len(r.types)) for k := range r.types { out = append(out, k) } return out } func (r *ServerTypeRegistry) load() error { data, err := os.ReadFile(r.path) if err != nil { return fmt.Errorf("read server types: %w", err) } var f serverTypesFile if err := yaml.Unmarshal(data, &f); err != nil { return fmt.Errorf("parse server types: %w", err) } if len(f.ServerTypes) == 0 { return fmt.Errorf("server types file contains no types") } for k, st := range f.ServerTypes { st.Key = k f.ServerTypes[k] = st } r.mu.Lock() r.types = f.ServerTypes r.mu.Unlock() return nil } func (r *ServerTypeRegistry) Watch(stop <-chan struct{}) { watcher, err := fsnotify.NewWatcher() if err != nil { log.Printf("server-types: fsnotify unavailable: %v", err) return } if err := watcher.Add(r.path); err != nil { log.Printf("server-types: watch failed: %v", err) _ = watcher.Close() return } go func() { defer watcher.Close() for { select { case <-stop: return case ev, ok := <-watcher.Events: if !ok { return } if ev.Op&(fsnotify.Write|fsnotify.Create) != 0 { if err := r.load(); err != nil { log.Printf("server-types: hot-reload failed: %v (keeping previous config)", err) } else { log.Printf("server-types: reloaded %d types", len(r.types)) } } case err, ok := <-watcher.Errors: if !ok { return } log.Printf("server-types: watch error: %v", err) } } }() }