package settings import ( "encoding/json" "errors" "fmt" "os" "path/filepath" ) // MaxNodeAuditEvents is the retention cap for node-audit.json. const MaxNodeAuditEvents = 1000 // LoadNodeAudit reads and parses node-audit.json from dir. func LoadNodeAudit(dir string) (NodeAuditStore, error) { path := filepath.Join(dir, NodeAuditFileName) payload, err := os.ReadFile(path) if err != nil { return NodeAuditStore{}, err } var store NodeAuditStore if err := json.Unmarshal(payload, &store); err != nil { return NodeAuditStore{}, fmt.Errorf("parse node audit: %w", err) } if store.Events == nil { store.Events = []NodeAuditEvent{} } return store, nil } // LoadNodeAuditOrEmpty returns an empty store when node-audit.json is missing. func LoadNodeAuditOrEmpty(dir string) (NodeAuditStore, error) { store, err := LoadNodeAudit(dir) if err != nil { if errors.Is(err, os.ErrNotExist) { return NodeAuditStore{Events: []NodeAuditEvent{}}, nil } return NodeAuditStore{}, err } return store, nil } // SaveNodeAudit writes node-audit.json to dir with mode 0640. func SaveNodeAudit(dir string, store NodeAuditStore) error { if err := ensureConfigDir(dir); err != nil { return err } if store.Events == nil { store.Events = []NodeAuditEvent{} } payload, err := json.MarshalIndent(store, "", " ") if err != nil { return fmt.Errorf("encode node audit: %w", err) } payload = append(payload, '\n') path := filepath.Join(dir, NodeAuditFileName) if err := os.WriteFile(path, payload, 0o640); err != nil { return fmt.Errorf("write node audit: %w", err) } return nil } // AppendNodeAuditEvent appends event and trims to MaxNodeAuditEvents (newest kept). func AppendNodeAuditEvent(dir string, event NodeAuditEvent) error { store, err := LoadNodeAuditOrEmpty(dir) if err != nil { return err } store.Events = append(store.Events, event) if len(store.Events) > MaxNodeAuditEvents { store.Events = store.Events[len(store.Events)-MaxNodeAuditEvents:] } return SaveNodeAudit(dir, store) }