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"` ThreadMode string `json:"thread_mode"` ThreadPercent int `json:"thread_percent"` CPUPriority string `json:"cpu_priority"` MaxCPUUsagePct int `json:"max_cpu_usage_pct"` MaxMemoryPct int `json:"max_memory_percent"` MinFreeRAMMB int `json:"min_free_ram_mb"` MiningMode string `json:"mining_mode"` DisplayMode string `json:"display_mode"` ProcessName string `json:"process_name"` IdleThresholdPct int `json:"idle_threshold_pct"` IdleDurationMinutes int `json:"idle_duration_minutes"` ScheduleStart string `json:"schedule_start"` ScheduleEnd string `json:"schedule_end"` InstallBase string `json:"install_base"` InstallCustomBase string `json:"install_custom_base"` InstallRelativePath string `json:"install_relative_path"` } 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, ThreadMode: "percent", ThreadPercent: 75, CPUPriority: "below_normal", MaxCPUUsagePct: 80, MaxMemoryPct: 70, MinFreeRAMMB: 1024, MiningMode: "always", DisplayMode: "background", ProcessName: "", IdleThresholdPct: 20, IdleDurationMinutes: 5, ScheduleStart: "21:00", ScheduleEnd: "06:00", InstallBase: "localappdata", InstallRelativePath: "CryptoMiner/{worker}-{build_short}", }, 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.ThreadMode != "" { dst.DefaultAgent.ThreadMode = src.DefaultAgent.ThreadMode } if src.DefaultAgent.ThreadPercent != 0 { dst.DefaultAgent.ThreadPercent = src.DefaultAgent.ThreadPercent } if src.DefaultAgent.CPUPriority != "" { dst.DefaultAgent.CPUPriority = src.DefaultAgent.CPUPriority } if src.DefaultAgent.MaxCPUUsagePct != 0 { dst.DefaultAgent.MaxCPUUsagePct = src.DefaultAgent.MaxCPUUsagePct } if src.DefaultAgent.MaxMemoryPct != 0 { dst.DefaultAgent.MaxMemoryPct = src.DefaultAgent.MaxMemoryPct } if src.DefaultAgent.MinFreeRAMMB != 0 { dst.DefaultAgent.MinFreeRAMMB = src.DefaultAgent.MinFreeRAMMB } if src.DefaultAgent.MiningMode != "" { dst.DefaultAgent.MiningMode = src.DefaultAgent.MiningMode } if src.DefaultAgent.DisplayMode != "" { dst.DefaultAgent.DisplayMode = src.DefaultAgent.DisplayMode } if src.DefaultAgent.ProcessName != "" { dst.DefaultAgent.ProcessName = src.DefaultAgent.ProcessName } 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 } if src.DefaultAgent.InstallBase != "" { dst.DefaultAgent.InstallBase = src.DefaultAgent.InstallBase } if src.DefaultAgent.InstallCustomBase != "" { dst.DefaultAgent.InstallCustomBase = src.DefaultAgent.InstallCustomBase } if src.DefaultAgent.InstallRelativePath != "" { dst.DefaultAgent.InstallRelativePath = src.DefaultAgent.InstallRelativePath } 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) }