OrangePi3588Media/agent/internal/config/config.go

174 lines
5.6 KiB
Go

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"`
ResourcesDir string `json:"resources_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",
ResourcesDir: "/opt/rk3588sys/resources",
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.ResourcesDir) == "" {
return errors.New("agent.resources_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
}