Mining/pkg/mining/config_manager.go
Claude 9e4bb376e8
Some checks failed
Test / test (push) Waiting to run
Security Scan / security (push) Has been cancelled
ax(mining): replace prose struct-field comments with usage examples
DatabaseConfig field comments restated the type signature in prose
(AX §2 violation). Replaced with concrete assignment examples showing
real values and edge-case behaviour (0 → defaults to 30).

Co-Authored-By: Charon <charon@lethean.io>
2026-04-02 11:17:57 +01:00

159 lines
4.4 KiB
Go

package mining
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"github.com/adrg/xdg"
)
// configMu protects concurrent access to config file operations
var configMu sync.RWMutex
// MinerAutostartConfig{MinerType: "xmrig", Autostart: true, Config: &cfg}
type MinerAutostartConfig struct {
MinerType string `json:"minerType"`
Autostart bool `json:"autostart"`
Config *Config `json:"config,omitempty"` // Store the last used config
}
// DatabaseConfig{Enabled: true, RetentionDays: 30}
type DatabaseConfig struct {
// cfg.Enabled = false // disable hashrate persistence entirely
Enabled bool `json:"enabled"`
// cfg.RetentionDays = 90 // purge rows older than 90 days (0 → defaults to 30)
RetentionDays int `json:"retentionDays,omitempty"`
}
// defaultDatabaseConfig() // DatabaseConfig{Enabled: true, RetentionDays: 30}
func defaultDatabaseConfig() DatabaseConfig {
return DatabaseConfig{
Enabled: true,
RetentionDays: 30,
}
}
// MinersConfig{Miners: []MinerAutostartConfig{}, Database: defaultDatabaseConfig()}
type MinersConfig struct {
Miners []MinerAutostartConfig `json:"miners"`
Database DatabaseConfig `json:"database"`
}
// getMinersConfigPath() // "~/.config/lethean-desktop/miners/config.json"
func getMinersConfigPath() (string, error) {
return xdg.ConfigFile("lethean-desktop/miners/config.json")
}
// cfg, err := LoadMinersConfig()
// if err != nil { return err }
// cfg.Database.Enabled = false
func LoadMinersConfig() (*MinersConfig, error) {
configMu.RLock()
defer configMu.RUnlock()
configPath, err := getMinersConfigPath()
if err != nil {
return nil, fmt.Errorf("could not determine miners config path: %w", err)
}
data, err := os.ReadFile(configPath)
if err != nil {
if os.IsNotExist(err) {
// Return empty config with defaults if file doesn't exist
return &MinersConfig{
Miners: []MinerAutostartConfig{},
Database: defaultDatabaseConfig(),
}, nil
}
return nil, fmt.Errorf("failed to read miners config file: %w", err)
}
var configuration MinersConfig
if err := json.Unmarshal(data, &configuration); err != nil {
return nil, fmt.Errorf("failed to unmarshal miners config: %w", err)
}
// Apply default database config if not set (for backwards compatibility)
if configuration.Database.RetentionDays == 0 {
configuration.Database = defaultDatabaseConfig()
}
return &configuration, nil
}
// cfg.Database.RetentionDays = 60
// if err := SaveMinersConfig(cfg); err != nil { return err }
func SaveMinersConfig(configuration *MinersConfig) error {
configMu.Lock()
defer configMu.Unlock()
configPath, err := getMinersConfigPath()
if err != nil {
return fmt.Errorf("could not determine miners config path: %w", err)
}
dir := filepath.Dir(configPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}
data, err := json.MarshalIndent(configuration, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal miners config: %w", err)
}
return AtomicWriteFile(configPath, data, 0600)
}
// UpdateMinersConfig(func(c *MinersConfig) error { c.Miners = append(c.Miners, entry); return nil })
func UpdateMinersConfig(modifier func(*MinersConfig) error) error {
configMu.Lock()
defer configMu.Unlock()
configPath, err := getMinersConfigPath()
if err != nil {
return fmt.Errorf("could not determine miners config path: %w", err)
}
// Load current config
var configuration MinersConfig
data, err := os.ReadFile(configPath)
if err != nil {
if os.IsNotExist(err) {
configuration = MinersConfig{
Miners: []MinerAutostartConfig{},
Database: defaultDatabaseConfig(),
}
} else {
return fmt.Errorf("failed to read miners config file: %w", err)
}
} else {
if err := json.Unmarshal(data, &configuration); err != nil {
return fmt.Errorf("failed to unmarshal miners config: %w", err)
}
if configuration.Database.RetentionDays == 0 {
configuration.Database = defaultDatabaseConfig()
}
}
// Apply the modification
if err := modifier(&configuration); err != nil {
return err
}
// Save atomically
dir := filepath.Dir(configPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}
newData, err := json.MarshalIndent(configuration, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal miners config: %w", err)
}
return AtomicWriteFile(configPath, newData, 0600)
}