// NextNVR — MIT License // Copyright (c) 2026 NextNVR Contributors // SPDX-License-Identifier: MIT // // config.go — JSON configuration management. Zero external dependencies. package main import ( "encoding/json" "fmt" "os" ) // Config represents the top-level application configuration. type Config struct { Server ServerConfig `json:"server"` Storage StorageConfig `json:"storage"` Auth AuthConfig `json:"auth"` Cameras []CameraConfig `json:"cameras"` Go2RTC Go2RTCConfig `json:"go2rtc"` } type ServerConfig struct { Port string `json:"port"` BindHost string `json:"bind_host"` ViewerPort string `json:"viewer_port"` } type StorageConfig struct { RecordingsPath string `json:"recordings_path"` RetentionDays int `json:"retention_days"` CleanupIntervalMins int `json:"cleanup_interval_mins"` } type CameraConfig struct { ID string `json:"id"` Name string `json:"name"` IP string `json:"ip"` Username string `json:"username"` Password string `json:"password"` ONVIFPort int `json:"onvif_port,string"` RTSPMain string `json:"rtsp_main"` RTSPSub string `json:"rtsp_sub"` Description string `json:"description"` Enabled bool `json:"enabled"` Record bool `json:"record"` } type Go2RTCConfig struct { Enabled bool `json:"enabled"` Port string `json:"port"` Binary string `json:"binary"` } type AuthConfig struct { Enabled bool `json:"enabled"` Master UserConfig `json:"master"` Viewer UserConfig `json:"viewer"` } type UserConfig struct { Username string `json:"username"` Password string `json:"password"` Enabled bool `json:"enabled"` } // 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", ViewerPort: ":8090", }, Storage: StorageConfig{ RecordingsPath: "/home/master/nvr_data", RetentionDays: 7, CleanupIntervalMins: 60, }, Auth: AuthConfig{ Enabled: false, Viewer: UserConfig{Enabled: true}, }, Go2RTC: Go2RTCConfig{ Enabled: true, Port: ":1984", Binary: "/opt/nextnvr/go2rtc", }, } } // LoadConfig reads and parses a JSON configuration file. 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 := json.Unmarshal(data, &cfg); err != nil { return cfg, fmt.Errorf("parsing config: %w", err) } return cfg, nil } // SaveConfig writes the configuration to a JSON file. func SaveConfig(cfg Config, path string) error { data, err := json.MarshalIndent(&cfg, "", " ") if err != nil { return fmt.Errorf("marshaling config: %w", err) } data = append(data, '\n') if err := os.WriteFile(path, data, 0600); err != nil { return fmt.Errorf("writing config: %w", err) } return nil }