package config import ( "encoding/json" "errors" "fmt" "net" "net/url" "os" "strings" ) type Config struct { Agent AgentConfig `json:"agent"` } type AgentConfig struct { Listen string `json:"listen"` Token string `json:"token"` RequireTokenForRead bool `json:"require_token_for_read"` DiscoveryEnable bool `json:"discovery_enable"` DiscoveryPort int `json:"discovery_port"` DeviceName string `json:"device_name"` DeviceIDPath string `json:"device_id_path"` ModelsDir string `json:"models_dir"` MaxUploadMB int `json:"max_upload_mb"` ConfigPath string `json:"config_path"` MediaServerProcess MediaServerProcessConfig `json:"media_server_process"` MediaServerBaseURL string `json:"media_server_base_url"` MediaServerTimeout int `json:"media_server_timeout_ms"` MediaServerRetry RetryConfig `json:"media_server_retry"` } type MediaServerProcessConfig struct { Enable bool `json:"enable"` ExecPath string `json:"exec_path"` WorkDir string `json:"work_dir"` ConfigsDir string `json:"configs_dir"` PidFile string `json:"pid_file"` GracefulTimeoutMS int `json:"graceful_timeout_ms"` } type RetryConfig struct { MaxAttempts int `json:"max_attempts"` BackoffMS []int `json:"backoff_ms"` } func Default() Config { return Config{ Agent: AgentConfig{ Listen: "0.0.0.0:9100", RequireTokenForRead: false, DiscoveryEnable: true, DiscoveryPort: 35688, DeviceIDPath: "/var/lib/rk3588-agent/device_id", ModelsDir: "/opt/rk3588sys/models", MaxUploadMB: 200, ConfigPath: "/etc/rk3588sys/config.json", MediaServerProcess: MediaServerProcessConfig{ Enable: false, PidFile: "/var/run/rk3588sys-media-server.pid", GracefulTimeoutMS: 5000, }, MediaServerBaseURL: "http://127.0.0.1:9000", MediaServerTimeout: 3000, MediaServerRetry: RetryConfig{ MaxAttempts: 3, BackoffMS: []int{200, 500}, }, }, } } func Load(path string) (Config, error) { if strings.TrimSpace(path) == "" { return Config{}, errors.New("config path is empty") } b, err := os.ReadFile(path) if err != nil { return Config{}, fmt.Errorf("read config: %w", err) } cfg := Default() if err := json.Unmarshal(b, &cfg); err != nil { return Config{}, fmt.Errorf("parse config json: %w", err) } if err := cfg.Validate(); err != nil { return Config{}, err } return cfg, nil } func (c Config) Validate() error { a := c.Agent if strings.TrimSpace(a.Listen) == "" { return errors.New("agent.listen is required") } if strings.TrimSpace(a.Token) == "" { return errors.New("agent.token is required") } if a.DiscoveryPort <= 0 || a.DiscoveryPort > 65535 { return fmt.Errorf("agent.discovery_port invalid: %d", a.DiscoveryPort) } if a.MaxUploadMB <= 0 { return fmt.Errorf("agent.max_upload_mb invalid: %d", a.MaxUploadMB) } if strings.TrimSpace(a.ModelsDir) == "" { return errors.New("agent.models_dir is required") } if strings.TrimSpace(a.ConfigPath) == "" { return errors.New("agent.config_path is required") } if a.MediaServerProcess.Enable { p := a.MediaServerProcess if strings.TrimSpace(p.ExecPath) == "" { return errors.New("agent.media_server_process.exec_path is required") } if strings.TrimSpace(p.WorkDir) == "" { return errors.New("agent.media_server_process.work_dir is required") } if strings.TrimSpace(p.ConfigsDir) == "" { return errors.New("agent.media_server_process.configs_dir is required") } if strings.TrimSpace(p.PidFile) == "" { return errors.New("agent.media_server_process.pid_file is required") } if p.GracefulTimeoutMS <= 0 { return fmt.Errorf("agent.media_server_process.graceful_timeout_ms invalid: %d", p.GracefulTimeoutMS) } } if strings.TrimSpace(a.DeviceIDPath) == "" { return errors.New("agent.device_id_path is required") } if strings.TrimSpace(a.MediaServerBaseURL) == "" { return errors.New("agent.media_server_base_url is required") } u, err := url.Parse(a.MediaServerBaseURL) if err != nil { return fmt.Errorf("agent.media_server_base_url invalid: %w", err) } if u.Scheme != "http" && u.Scheme != "https" { return fmt.Errorf("agent.media_server_base_url scheme must be http/https: %q", u.Scheme) } host := u.Hostname() if host != "127.0.0.1" && host != "localhost" { return fmt.Errorf("agent.media_server_base_url must use 127.0.0.1/localhost, got host=%q", host) } if p := u.Port(); p != "" { port, err := net.LookupPort("tcp", p) if err != nil || port <= 0 || port > 65535 { return fmt.Errorf("agent.media_server_base_url port invalid: %q", p) } } if a.MediaServerTimeout <= 0 { return fmt.Errorf("agent.media_server_timeout_ms invalid: %d", a.MediaServerTimeout) } if a.MediaServerRetry.MaxAttempts <= 0 { return fmt.Errorf("agent.media_server_retry.max_attempts invalid: %d", a.MediaServerRetry.MaxAttempts) } if len(a.MediaServerRetry.BackoffMS) == 0 { return errors.New("agent.media_server_retry.backoff_ms is required") } for i, v := range a.MediaServerRetry.BackoffMS { if v < 0 { return fmt.Errorf("agent.media_server_retry.backoff_ms[%d] invalid: %d", i, v) } } return nil }