package main import ( "encoding/json" "flag" "fmt" "os" "path/filepath" ) type Config struct { Port int `json:"port"` DataDir string `json:"data_dir"` Pool PoolConfig `json:"pool"` Wallet WalletConfig `json:"wallet"` DefaultAgent AgentDefaults `json:"default_agent_config"` Background BackgroundConfig `json:"background"` Alerts AlertsConfig `json:"alerts"` } type PoolConfig struct { Host string `json:"host"` Port int `json:"port"` UseTLS bool `json:"use_tls"` Password string `json:"password"` } type WalletConfig struct { Address string `json:"address"` PaymentID string `json:"payment_id"` } type AgentDefaults struct { Threads int `json:"threads"` CPUPriority string `json:"cpu_priority"` MaxCPUUsagePct int `json:"max_cpu_usage_pct"` MinFreeRAMMB int `json:"min_free_ram_mb"` MiningMode string `json:"mining_mode"` IdleThresholdPct int `json:"idle_threshold_pct"` IdleDurationMinutes int `json:"idle_duration_minutes"` ScheduleStart string `json:"schedule_start"` ScheduleEnd string `json:"schedule_end"` } type BackgroundConfig struct { SilentMode bool `json:"silent_mode"` RunAs string `json:"run_as"` AutoStart bool `json:"auto_start"` MinimizeToTray bool `json:"minimize_to_tray"` } type AlertsConfig struct { OfflineThresholdMinutes int `json:"offline_threshold_minutes"` HashrateDropThresholdPct int `json:"hashrate_drop_threshold_pct"` RejectionRateThresholdPct int `json:"rejection_rate_threshold_pct"` } func DefaultConfig() *Config { return &Config{ Port: 8989, DataDir: "data", Pool: PoolConfig{ Host: "pool.supportxmr.com", Port: 3333, UseTLS: true, Password: "x", }, Wallet: WalletConfig{ Address: "", PaymentID: "", }, DefaultAgent: AgentDefaults{ Threads: 4, CPUPriority: "below_normal", MaxCPUUsagePct: 80, MinFreeRAMMB: 1024, MiningMode: "always", IdleThresholdPct: 20, IdleDurationMinutes: 5, ScheduleStart: "21:00", ScheduleEnd: "06:00", }, Background: BackgroundConfig{ SilentMode: true, RunAs: "service", AutoStart: true, MinimizeToTray: true, }, Alerts: AlertsConfig{ OfflineThresholdMinutes: 5, HashrateDropThresholdPct: 50, RejectionRateThresholdPct: 5, }, } } func LoadConfig() *Config { cfg := DefaultConfig() // Parse CLI flags port := flag.Int("port", 8989, "Server port") dataDir := flag.String("data", "data", "Data directory") flag.Parse() cfg.Port = *port cfg.DataDir = *dataDir // Try to load from config file configPath := filepath.Join(cfg.DataDir, "config.json") if data, err := os.ReadFile(configPath); err == nil { var fileCfg Config if err := json.Unmarshal(data, &fileCfg); err == nil { // Merge file config over defaults (only non-zero values) mergeConfig(cfg, &fileCfg) } } return cfg } func mergeConfig(dst, src *Config) { if src.Port != 0 { dst.Port = src.Port } if src.DataDir != "" { dst.DataDir = src.DataDir } if src.Pool.Host != "" { dst.Pool.Host = src.Pool.Host } if src.Pool.Port != 0 { dst.Pool.Port = src.Pool.Port } dst.Pool.UseTLS = src.Pool.UseTLS if src.Pool.Password != "" { dst.Pool.Password = src.Pool.Password } if src.Wallet.Address != "" { dst.Wallet.Address = src.Wallet.Address } if src.Wallet.PaymentID != "" { dst.Wallet.PaymentID = src.Wallet.PaymentID } if src.DefaultAgent.Threads != 0 { dst.DefaultAgent.Threads = src.DefaultAgent.Threads } if src.DefaultAgent.CPUPriority != "" { dst.DefaultAgent.CPUPriority = src.DefaultAgent.CPUPriority } if src.DefaultAgent.MaxCPUUsagePct != 0 { dst.DefaultAgent.MaxCPUUsagePct = src.DefaultAgent.MaxCPUUsagePct } if src.DefaultAgent.MinFreeRAMMB != 0 { dst.DefaultAgent.MinFreeRAMMB = src.DefaultAgent.MinFreeRAMMB } if src.DefaultAgent.MiningMode != "" { dst.DefaultAgent.MiningMode = src.DefaultAgent.MiningMode } if src.DefaultAgent.IdleThresholdPct != 0 { dst.DefaultAgent.IdleThresholdPct = src.DefaultAgent.IdleThresholdPct } if src.DefaultAgent.IdleDurationMinutes != 0 { dst.DefaultAgent.IdleDurationMinutes = src.DefaultAgent.IdleDurationMinutes } if src.DefaultAgent.ScheduleStart != "" { dst.DefaultAgent.ScheduleStart = src.DefaultAgent.ScheduleStart } if src.DefaultAgent.ScheduleEnd != "" { dst.DefaultAgent.ScheduleEnd = src.DefaultAgent.ScheduleEnd } dst.Background.SilentMode = src.Background.SilentMode if src.Background.RunAs != "" { dst.Background.RunAs = src.Background.RunAs } dst.Background.AutoStart = src.Background.AutoStart dst.Background.MinimizeToTray = src.Background.MinimizeToTray if src.Alerts.OfflineThresholdMinutes != 0 { dst.Alerts.OfflineThresholdMinutes = src.Alerts.OfflineThresholdMinutes } if src.Alerts.HashrateDropThresholdPct != 0 { dst.Alerts.HashrateDropThresholdPct = src.Alerts.HashrateDropThresholdPct } if src.Alerts.RejectionRateThresholdPct != 0 { dst.Alerts.RejectionRateThresholdPct = src.Alerts.RejectionRateThresholdPct } } func (c *Config) Save() error { configPath := filepath.Join(c.DataDir, "config.json") data, err := json.MarshalIndent(c, "", " ") if err != nil { return fmt.Errorf("failed to marshal config: %w", err) } return os.WriteFile(configPath, data, 0644) } func (c *Config) PoolURL() string { proto := "stratum+tcp" if c.Pool.UseTLS { proto = "stratum+ssl" } return fmt.Sprintf("%s://%s:%d", proto, c.Pool.Host, c.Pool.Port) }