// NextNVR v0.1.0 — Configuration management // Handles config.yaml parsing with sensible defaults package main import ( "fmt" "os" "path/filepath" "gopkg.in/yaml.v3" ) // Config represents the top-level application configuration. type Config struct { Server ServerConfig `yaml:"server" json:"server"` Storage StorageConfig `yaml:"storage" json:"storage"` Cameras []CameraConfig `yaml:"cameras" json:"cameras"` Go2RTC Go2RTCConfig `yaml:"go2rtc" json:"go2rtc"` } // ServerConfig holds HTTP server settings. type ServerConfig struct { Port string `yaml:"port"` BindHost string `yaml:"bind_host"` } // StorageConfig holds recording and retention settings. type StorageConfig struct { RecordingsPath string `yaml:"recordings_path"` RetentionDays int `yaml:"retention_days"` CleanupIntervalMins int `yaml:"cleanup_interval_mins"` } // CameraConfig represents a single camera's configuration. type CameraConfig struct { ID string `yaml:"id" json:"id"` Name string `yaml:"name" json:"name"` IP string `yaml:"ip" json:"ip"` Username string `yaml:"username" json:"username"` Password string `yaml:"password" json:"password"` ONVIFPort int `yaml:"onvif_port" json:"onvif_port"` RTSPMain string `yaml:"rtsp_main" json:"rtsp_main"` RTSPSub string `yaml:"rtsp_sub" json:"rtsp_sub"` Description string `yaml:"description" json:"description"` Enabled bool `yaml:"enabled" json:"enabled"` Record bool `yaml:"record" json:"record"` } // Go2RTCConfig holds go2rtc child process settings. type Go2RTCConfig struct { Enabled bool `yaml:"enabled"` Port string `yaml:"port"` Binary string `yaml:"binary"` } // APIResponse wraps all JSON API responses. type APIResponse struct { Success bool `json:"success"` Data interface{} `json:"data,omitempty"` Error string `json:"error,omitempty"` } // CameraStatus extends CameraConfig with runtime information. type CameraStatus struct { CameraConfig Online bool `json:"online"` Uptime string `json:"uptime"` Streams int `json:"streams"` } // DefaultConfig returns a Config with sensible defaults. func DefaultConfig() Config { return Config{ Server: ServerConfig{ Port: ":8080", BindHost: "0.0.0.0", }, Storage: StorageConfig{ RecordingsPath: "/mnt/recordings", RetentionDays: 18, CleanupIntervalMins: 60, }, Go2RTC: Go2RTCConfig{ Enabled: true, Port: ":1984", Binary: "go2rtc", }, } } // LoadConfig reads and parses a YAML configuration file. // If the file does not exist, it returns DefaultConfig(). func LoadConfig(path string) (Config, error) { cfg := DefaultConfig() data, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return cfg, fmt.Errorf("config file not found: %s", path) } return cfg, fmt.Errorf("reading config: %w", err) } if err := yaml.Unmarshal(data, &cfg); err != nil { return cfg, fmt.Errorf("parsing config: %w", err) } // Apply path defaults relative to config file location. configDir := filepath.Dir(path) if cfg.Storage.RecordingsPath == "" { cfg.Storage.RecordingsPath = "/mnt/recordings" } _ = configDir // reserved for relative path resolution return cfg, nil } // SaveConfig writes the configuration to a YAML file. func SaveConfig(cfg Config, path string) error { data, err := yaml.Marshal(&cfg) if err != nil { return fmt.Errorf("marshaling config: %w", err) } header := []byte("# NextNVR Configuration\n# Generated by NextNVR setup\n\n") out := append(header, data...) if err := os.WriteFile(path, out, 0644); err != nil { return fmt.Errorf("writing config: %w", err) } return nil }