package proxy import ( "encoding/json" "errors" "os" "strings" "time" ) // LoadConfig reads a JSON config file and validates the result. // // cfg, errorValue := proxy.LoadConfig("config.json") // if errorValue != nil { // return // } func LoadConfig(path string) (*Config, error) { data, errorValue := os.ReadFile(path) if errorValue != nil { return nil, errorValue } config := &Config{} if errorValue = json.Unmarshal(data, config); errorValue != nil { return nil, errorValue } config.sourcePath = path if errorValue = config.Validate(); errorValue != nil { return nil, errorValue } return config, nil } // Validate checks that `bind` and `pools` are present and every enabled pool has a URL. // // cfg := &proxy.Config{ // Bind: []proxy.BindAddr{{Host: "127.0.0.1", Port: 3333}}, // Pools: []proxy.PoolConfig{{URL: "pool-a:3333", Enabled: true}}, // } // if errorValue := cfg.Validate(); errorValue != nil { // return // } func (c *Config) Validate() error { if c == nil { return errors.New("config is nil") } if strings.TrimSpace(c.Mode) == "" { return errors.New("mode is empty") } if c.Mode != "nicehash" && c.Mode != "simple" { return errors.New("mode is invalid") } if strings.TrimSpace(string(c.Workers)) == "" { return errors.New("workers mode is empty") } if c.Workers != WorkersByRigID && c.Workers != WorkersByUser && c.Workers != WorkersByPass && c.Workers != WorkersByPassword && c.Workers != WorkersByAgent && c.Workers != WorkersByIP && c.Workers != WorkersDisabled { return errors.New("workers mode is invalid") } if len(c.Bind) == 0 { return errors.New("bind list is empty") } if len(c.Pools) == 0 { return errors.New("pool list is empty") } for _, poolConfig := range c.Pools { if poolConfig.Enabled && strings.TrimSpace(poolConfig.URL) == "" { return errors.New("enabled pool URL is empty") } } return nil } // NewConfigWatcher watches a config file and reloads the proxy on modification. // // w := proxy.NewConfigWatcher("config.json", func(cfg *proxy.Config) { // p.Reload(cfg) // }) func NewConfigWatcher(path string, onChange func(*Config)) *ConfigWatcher { return newConfigWatcher(path, onChange, true) } func newConfigWatcher(path string, onChange func(*Config), enabled bool) *ConfigWatcher { return &ConfigWatcher{ path: path, onChange: onChange, enabled: enabled, done: make(chan struct{}), } } // Start begins 1-second polling for `config.json`. // // w.Start() func (w *ConfigWatcher) Start() { if w == nil || !w.enabled { return } if info, errorValue := os.Stat(w.path); errorValue == nil { w.lastModifiedAt = info.ModTime() } go func() { ticker := time.NewTicker(time.Second) defer ticker.Stop() for { select { case <-ticker.C: info, errorValue := os.Stat(w.path) if errorValue != nil { continue } if !info.ModTime().After(w.lastModifiedAt) { continue } w.lastModifiedAt = info.ModTime() config, errorValue := LoadConfig(w.path) if errorValue == nil && w.onChange != nil { w.onChange(config) } case <-w.done: return } } }() } // Stop ends polling so the watcher can be shut down with `p.Stop()`. // // w.Stop() func (w *ConfigWatcher) Stop() { if w == nil || w.done == nil { return } select { case <-w.done: default: close(w.done) } }