package config import ( "encoding/json" "errors" "os" "strconv" "time" ) // Config contains all runtime, reporting, collection, and service settings. type Config struct { HostID string `json:"host_id"` HTTP HTTPConfig `json:"http"` Collect CollectConfig `json:"collect"` Report ReportConfig `json:"report"` Service ServiceConfig `json:"service"` } // HTTPConfig controls the local HTTP API listener. type HTTPConfig struct { ListenAddress string `json:"listen_address"` } // CollectConfig controls periodic host status collection. type CollectConfig struct { Interval Duration `json:"interval"` Timeout Duration `json:"timeout"` } // ReportConfig controls optional periodic status POST reporting. type ReportConfig struct { Enabled bool `json:"enabled"` Endpoint string `json:"endpoint"` Interval Duration `json:"interval"` Timeout Duration `json:"timeout"` } // ServiceConfig controls operating system service registration metadata. type ServiceConfig struct { Name string `json:"name"` DisplayName string `json:"display_name"` Description string `json:"description"` } // Duration wraps time.Duration so JSON config can use Go duration strings. type Duration time.Duration // Duration returns the standard library duration value. func (d Duration) Duration() time.Duration { return time.Duration(d) } // MarshalJSON writes the duration as a Go duration string such as "30s". func (d Duration) MarshalJSON() ([]byte, error) { return json.Marshal(time.Duration(d).String()) } // UnmarshalJSON parses a Go duration string from JSON. func (d *Duration) UnmarshalJSON(data []byte) error { var raw string if err := json.Unmarshal(data, &raw); err != nil { return err } parsed, err := time.ParseDuration(raw) if err != nil { return err } *d = Duration(parsed) return nil } // Default returns safe local defaults for running the agent. func Default() Config { return Config{ HTTP: HTTPConfig{ListenAddress: "127.0.0.1:8080"}, Collect: CollectConfig{ Interval: Duration(30 * time.Second), Timeout: Duration(10 * time.Second), }, Report: ReportConfig{ Enabled: false, Interval: Duration(60 * time.Second), Timeout: Duration(5 * time.Second), }, Service: ServiceConfig{ Name: "mfd-monitor-agent", DisplayName: "MFD Monitor Agent", Description: "Collects host status and exposes it over HTTP.", }, } } // Load reads optional JSON config, applies environment overrides, and validates required settings. func Load(path string) (Config, error) { cfg := Default() if path != "" { data, err := os.ReadFile(path) if err != nil { return cfg, err } if err := json.Unmarshal(data, &cfg); err != nil { return cfg, err } } applyEnv(&cfg) if cfg.Report.Enabled && cfg.Report.Endpoint == "" { return cfg, errors.New("report endpoint is required when reporting is enabled") } return cfg, nil } // applyEnv overlays supported MFD_AGENT_* environment variables onto cfg. func applyEnv(cfg *Config) { if value := os.Getenv("MFD_AGENT_HOST_ID"); value != "" { cfg.HostID = value } if value := os.Getenv("MFD_AGENT_LISTEN"); value != "" { cfg.HTTP.ListenAddress = value } if value := os.Getenv("MFD_AGENT_REPORT_ENABLED"); value != "" { if parsed, err := strconv.ParseBool(value); err == nil { cfg.Report.Enabled = parsed } } if value := os.Getenv("MFD_AGENT_REPORT_ENDPOINT"); value != "" { cfg.Report.Endpoint = value } if value := os.Getenv("MFD_AGENT_REPORT_INTERVAL"); value != "" { if parsed, err := time.ParseDuration(value); err == nil { cfg.Report.Interval = Duration(parsed) } } if value := os.Getenv("MFD_AGENT_REPORT_TIMEOUT"); value != "" { if parsed, err := time.ParseDuration(value); err == nil { cfg.Report.Timeout = Duration(parsed) } } if value := os.Getenv("MFD_AGENT_COLLECT_INTERVAL"); value != "" { if parsed, err := time.ParseDuration(value); err == nil { cfg.Collect.Interval = Duration(parsed) } } if value := os.Getenv("MFD_AGENT_COLLECT_TIMEOUT"); value != "" { if parsed, err := time.ParseDuration(value); err == nil { cfg.Collect.Timeout = Duration(parsed) } } }