package network import ( "encoding/json" "os" "path/filepath" "sync" ) var defaultMetadataPath = "/etc/proxmox-router/metadata.json" // MetadataManager manages user annotations for interfaces. type MetadataManager struct { mu sync.RWMutex filePath string store MetadataStore } func NewMetadataManager(customPath string) *MetadataManager { p := defaultMetadataPath if customPath != "" { p = customPath } mm := &MetadataManager{ filePath: p, store: MetadataStore{ Interfaces: make(map[string]InterfaceMetadata), }, } mm.load() return mm } func (mm *MetadataManager) load() { mm.mu.Lock() defer mm.mu.Unlock() data, err := os.ReadFile(mm.filePath) if err != nil { return } _ = json.Unmarshal(data, &mm.store) if mm.store.Interfaces == nil { mm.store.Interfaces = make(map[string]InterfaceMetadata) } } func (mm *MetadataManager) save() error { dir := filepath.Dir(mm.filePath) if err := os.MkdirAll(dir, 0755); err != nil { return err } data, err := json.MarshalIndent(mm.store, "", " ") if err != nil { return err } return os.WriteFile(mm.filePath, data, 0644) } func (mm *MetadataManager) Get(name string) InterfaceMetadata { mm.mu.RLock() defer mm.mu.RUnlock() return mm.store.Interfaces[name] } func (mm *MetadataManager) Set(name string, role, desc string) error { mm.mu.Lock() defer mm.mu.Unlock() meta := mm.store.Interfaces[name] meta.Role = role meta.Description = desc mm.store.Interfaces[name] = meta return mm.save() } func (mm *MetadataManager) AddIP(name, cidr string) error { mm.mu.Lock() defer mm.mu.Unlock() meta := mm.store.Interfaces[name] for _, ip := range meta.IPAddresses { if ip == cidr { return nil } } meta.IPAddresses = append(meta.IPAddresses, cidr) meta.AutoUp = true mm.store.Interfaces[name] = meta return mm.save() } func (mm *MetadataManager) RemoveIP(name, cidr string) error { mm.mu.Lock() defer mm.mu.Unlock() meta := mm.store.Interfaces[name] var updated []string for _, ip := range meta.IPAddresses { if ip != cidr { updated = append(updated, ip) } } meta.IPAddresses = updated mm.store.Interfaces[name] = meta return mm.save() } func (mm *MetadataManager) GetAllWithIPs() map[string][]string { mm.mu.RLock() defer mm.mu.RUnlock() res := make(map[string][]string) for name, meta := range mm.store.Interfaces { if len(meta.IPAddresses) > 0 { res[name] = append([]string(nil), meta.IPAddresses...) } } return res } func (mm *MetadataManager) Delete(name string) error { mm.mu.Lock() defer mm.mu.Unlock() delete(mm.store.Interfaces, name) return mm.save() } // Rename migrates interface metadata when an interface is renamed. func (mm *MetadataManager) Rename(oldName, newName string) error { mm.mu.Lock() defer mm.mu.Unlock() if meta, exists := mm.store.Interfaces[oldName]; exists { delete(mm.store.Interfaces, oldName) mm.store.Interfaces[newName] = meta return mm.save() } return nil }