package main import ( "encoding/json" "os" "path/filepath" ) // Config holds the daemon configuration type Config struct { // Mode: "cloud" (server settings priority) or "lan" (local settings priority) Mode string `json:"mode"` // Server settings APIBase string `json:"api_base"` ConfigPollingInterval int `json:"cfg_polling_timeout"` // in seconds, default: 30 // Device identity (persisted) DeviceID string `json:"device_id,omitempty"` DeviceToken string `json:"device_token,omitempty"` // BLE settings BLE struct { Enabled bool `json:"enabled"` BatchIntervalMs int `json:"batch_interval_ms"` UploadEndpoint string `json:"upload_endpoint,omitempty"` } `json:"ble"` // WiFi settings WiFi struct { MonitorEnabled bool `json:"monitor_enabled"` ClientEnabled bool `json:"client_enabled"` SSID string `json:"ssid"` PSK string `json:"psk"` BatchIntervalMs int `json:"batch_interval_ms"` UploadEndpoint string `json:"upload_endpoint,omitempty"` } `json:"wifi"` // SSH Tunnel settings (for terminal access) SSHTunnel struct { Enabled bool `json:"enabled"` Server string `json:"server"` Port int `json:"port"` User string `json:"user"` KeyPath string `json:"key_path"` RemotePort int `json:"remote_port"` KeepaliveInterval int `json:"keepalive_interval"` ReconnectDelay int `json:"reconnect_delay"` } `json:"ssh_tunnel"` // Dashboard Tunnel settings (for web dashboard access) DashboardTunnel struct { Enabled bool `json:"enabled"` Server string `json:"server"` Port int `json:"port"` User string `json:"user"` RemotePort int `json:"remote_port"` KeepaliveInterval int `json:"keepalive_interval"` ReconnectDelay int `json:"reconnect_delay"` } `json:"dashboard_tunnel"` // Network settings (eth0 is ALWAYS local, never from server) Network struct { NTPServers []string `json:"ntp_servers"` Eth0 struct { Static bool `json:"static"` Address string `json:"address"` Gateway string `json:"gateway"` DNS string `json:"dns"` } `json:"eth0"` } `json:"network"` // AP Fallback settings (when no network available for 120s) APFallback struct { Password string `json:"password"` // Default: "mybeacon123" } `json:"ap_fallback"` // Dashboard settings Dashboard struct { Enabled bool `json:"enabled"` } `json:"dashboard"` // Local-only settings (never from server) ZMQAddrBLE string `json:"zmq_addr_ble"` ZMQAddrWiFi string `json:"zmq_addr_wifi"` SpoolDir string `json:"spool_dir"` WiFiIface string `json:"wifi_iface"` Debug bool `json:"debug"` } // DefaultConfig returns a configuration with default values func DefaultConfig() *Config { cfg := &Config{ Mode: "cloud", APIBase: "http://localhost:5000/api/v1", ConfigPollingInterval: 30, ZMQAddrBLE: "tcp://127.0.0.1:5555", ZMQAddrWiFi: "tcp://127.0.0.1:5556", SpoolDir: "/var/spool/mybeacon", } cfg.BLE.Enabled = true cfg.BLE.BatchIntervalMs = 2500 cfg.WiFi.MonitorEnabled = false cfg.WiFi.BatchIntervalMs = 10000 cfg.SSHTunnel.Port = 22 cfg.SSHTunnel.KeepaliveInterval = 30 cfg.SSHTunnel.ReconnectDelay = 5 cfg.DashboardTunnel.Port = 22 cfg.DashboardTunnel.KeepaliveInterval = 30 cfg.DashboardTunnel.ReconnectDelay = 5 cfg.Network.NTPServers = []string{"pool.ntp.org"} cfg.APFallback.Password = "mybeacon" cfg.Dashboard.Enabled = true return cfg } // LoadConfig loads configuration from a JSON file func LoadConfig(path string) (*Config, error) { cfg := DefaultConfig() data, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return cfg, nil // Use defaults } return nil, err } if err := json.Unmarshal(data, cfg); err != nil { return nil, err } return cfg, nil } // SaveConfig saves configuration to a JSON file func SaveConfig(path string, cfg *Config) error { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0755); err != nil { return err } data, err := json.MarshalIndent(cfg, "", " ") if err != nil { return err } return os.WriteFile(path, data, 0600) } // DeviceState holds persistent device state type DeviceState struct { DeviceID string `json:"device_id"` DeviceToken string `json:"device_token"` DevicePassword string `json:"device_password,omitempty"` } // LoadDeviceState loads device state from file func LoadDeviceState(path string) (*DeviceState, error) { state := &DeviceState{} data, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return state, nil } return nil, err } if err := json.Unmarshal(data, state); err != nil { return nil, err } return state, nil } // SaveDeviceState saves device state to file func SaveDeviceState(path string, state *DeviceState) error { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0755); err != nil { return err } data, err := json.MarshalIndent(state, "", " ") if err != nil { return err } return os.WriteFile(path, data, 0600) }