diff --git a/pkg/mining/config_manager.go b/pkg/mining/config_manager.go index 78acbec..ebbdcb4 100644 --- a/pkg/mining/config_manager.go +++ b/pkg/mining/config_manager.go @@ -9,8 +9,8 @@ import ( "github.com/adrg/xdg" ) -// configMutex protects concurrent access to config file operations -var configMutex sync.RWMutex +// minersConfigMutex protects concurrent access to miners config file operations. +var minersConfigMutex sync.RWMutex // MinerAutostartConfig{MinerType: "xmrig", Autostart: true, Config: &config} type MinerAutostartConfig struct { @@ -21,9 +21,9 @@ type MinerAutostartConfig struct { // DatabaseConfig{Enabled: true, RetentionDays: 30} type DatabaseConfig struct { - // configuration.Enabled = false // disable hashrate persistence entirely + // minersConfig.Database.Enabled = false // disable hashrate persistence entirely Enabled bool `json:"enabled"` - // configuration.RetentionDays = 90 // purge rows older than 90 days (0 → defaults to 30) + // minersConfig.Database.RetentionDays = 90 // purge rows older than 90 days (0 → defaults to 30) RetentionDays int `json:"retentionDays,omitempty"` } @@ -41,24 +41,24 @@ type MinersConfig struct { Database DatabaseConfig `json:"database"` } -// getMinersConfigPath() // "~/.config/lethean-desktop/miners/config.json" +// minersConfigPath, err := getMinersConfigPath() // "/home/alice/.config/lethean-desktop/miners/config.json" func getMinersConfigPath() (string, error) { return xdg.ConfigFile("lethean-desktop/miners/config.json") } -// configuration, err := LoadMinersConfig() +// minersConfig, err := LoadMinersConfig() // if err != nil { return err } -// configuration.Database.Enabled = false +// minersConfig.Database.Enabled = false func LoadMinersConfig() (*MinersConfig, error) { - configMutex.RLock() - defer configMutex.RUnlock() + minersConfigMutex.RLock() + defer minersConfigMutex.RUnlock() - configPath, err := getMinersConfigPath() + minersConfigPath, err := getMinersConfigPath() if err != nil { return nil, ErrInternal("could not determine miners config path").WithCause(err) } - data, err := os.ReadFile(configPath) + data, err := os.ReadFile(minersConfigPath) if err != nil { if os.IsNotExist(err) { // Return empty config with defaults if file doesn't exist @@ -70,59 +70,59 @@ func LoadMinersConfig() (*MinersConfig, error) { return nil, ErrInternal("failed to read miners config file").WithCause(err) } - var configuration MinersConfig - if err := json.Unmarshal(data, &configuration); err != nil { + var minersConfig MinersConfig + if err := json.Unmarshal(data, &minersConfig); err != nil { return nil, ErrInternal("failed to unmarshal miners config").WithCause(err) } // Apply default database config if not set (for backwards compatibility) - if configuration.Database.RetentionDays == 0 { - configuration.Database = defaultDatabaseConfig() + if minersConfig.Database.RetentionDays == 0 { + minersConfig.Database = defaultDatabaseConfig() } - return &configuration, nil + return &minersConfig, nil } -// configuration.Database.RetentionDays = 60 -// if err := SaveMinersConfig(configuration); err != nil { return err } -func SaveMinersConfig(configuration *MinersConfig) error { - configMutex.Lock() - defer configMutex.Unlock() +// minersConfig.Database.RetentionDays = 60 +// if err := SaveMinersConfig(minersConfig); err != nil { return err } +func SaveMinersConfig(minersConfig *MinersConfig) error { + minersConfigMutex.Lock() + defer minersConfigMutex.Unlock() - configPath, err := getMinersConfigPath() + minersConfigPath, err := getMinersConfigPath() if err != nil { return ErrInternal("could not determine miners config path").WithCause(err) } - dir := filepath.Dir(configPath) + dir := filepath.Dir(minersConfigPath) if err := os.MkdirAll(dir, 0755); err != nil { return ErrInternal("failed to create config directory").WithCause(err) } - data, err := json.MarshalIndent(configuration, "", " ") + data, err := json.MarshalIndent(minersConfig, "", " ") if err != nil { return ErrInternal("failed to marshal miners config").WithCause(err) } - return AtomicWriteFile(configPath, data, 0600) + return AtomicWriteFile(minersConfigPath, data, 0600) } -// UpdateMinersConfig(func(c *MinersConfig) error { c.Miners = append(c.Miners, entry); return nil }) +// UpdateMinersConfig(func(minersConfig *MinersConfig) error { minersConfig.Miners = append(minersConfig.Miners, entry); return nil }) func UpdateMinersConfig(modifier func(*MinersConfig) error) error { - configMutex.Lock() - defer configMutex.Unlock() + minersConfigMutex.Lock() + defer minersConfigMutex.Unlock() - configPath, err := getMinersConfigPath() + minersConfigPath, err := getMinersConfigPath() if err != nil { return ErrInternal("could not determine miners config path").WithCause(err) } // Load current config - var configuration MinersConfig - data, err := os.ReadFile(configPath) + var minersConfig MinersConfig + data, err := os.ReadFile(minersConfigPath) if err != nil { if os.IsNotExist(err) { - configuration = MinersConfig{ + minersConfig = MinersConfig{ Miners: []MinerAutostartConfig{}, Database: defaultDatabaseConfig(), } @@ -130,29 +130,29 @@ func UpdateMinersConfig(modifier func(*MinersConfig) error) error { return ErrInternal("failed to read miners config file").WithCause(err) } } else { - if err := json.Unmarshal(data, &configuration); err != nil { + if err := json.Unmarshal(data, &minersConfig); err != nil { return ErrInternal("failed to unmarshal miners config").WithCause(err) } - if configuration.Database.RetentionDays == 0 { - configuration.Database = defaultDatabaseConfig() + if minersConfig.Database.RetentionDays == 0 { + minersConfig.Database = defaultDatabaseConfig() } } // Apply the modification - if err := modifier(&configuration); err != nil { + if err := modifier(&minersConfig); err != nil { return err } // Save atomically - dir := filepath.Dir(configPath) + dir := filepath.Dir(minersConfigPath) if err := os.MkdirAll(dir, 0755); err != nil { return ErrInternal("failed to create config directory").WithCause(err) } - newData, err := json.MarshalIndent(configuration, "", " ") + newData, err := json.MarshalIndent(minersConfig, "", " ") if err != nil { return ErrInternal("failed to marshal miners config").WithCause(err) } - return AtomicWriteFile(configPath, newData, 0600) + return AtomicWriteFile(minersConfigPath, newData, 0600) } diff --git a/pkg/mining/manager.go b/pkg/mining/manager.go index 5c39e04..81241f1 100644 --- a/pkg/mining/manager.go +++ b/pkg/mining/manager.go @@ -124,14 +124,14 @@ func NewManagerForSimulation() *Manager { // manager.initDatabase() loads `miners.json` and enables database persistence when `Database.Enabled` is true. func (manager *Manager) initDatabase() { - minersConfiguration, err := LoadMinersConfig() + minersConfig, err := LoadMinersConfig() if err != nil { logging.Warn("could not load config for database init", logging.Fields{"error": err}) return } - manager.databaseEnabled = minersConfiguration.Database.Enabled - manager.databaseRetention = minersConfiguration.Database.RetentionDays + manager.databaseEnabled = minersConfig.Database.Enabled + manager.databaseRetention = minersConfig.Database.RetentionDays if manager.databaseRetention == 0 { manager.databaseRetention = 30 } @@ -229,17 +229,17 @@ func (manager *Manager) syncMinersConfig() { // manager.autostartMiners() starts entries with `Autostart: true` from `context.Background()`. func (manager *Manager) autostartMiners() { - minersConfiguration, err := LoadMinersConfig() + minersConfig, err := LoadMinersConfig() if err != nil { logging.Warn("could not load miners config for autostart", logging.Fields{"error": err}) return } - for _, autostartEntry := range minersConfiguration.Miners { - if autostartEntry.Autostart && autostartEntry.Config != nil { - logging.Info("autostarting miner", logging.Fields{"type": autostartEntry.MinerType}) - if _, err := manager.StartMiner(context.Background(), autostartEntry.MinerType, autostartEntry.Config); err != nil { - logging.Error("failed to autostart miner", logging.Fields{"type": autostartEntry.MinerType, "error": err}) + for _, minerConfig := range minersConfig.Miners { + if minerConfig.Autostart && minerConfig.Config != nil { + logging.Info("autostarting miner", logging.Fields{"type": minerConfig.MinerType}) + if _, err := manager.StartMiner(context.Background(), minerConfig.MinerType, minerConfig.Config); err != nil { + logging.Error("failed to autostart miner", logging.Fields{"type": minerConfig.MinerType, "error": err}) } } } @@ -401,33 +401,33 @@ func (manager *Manager) UninstallMiner(ctx context.Context, minerType string) er return ErrInternal("failed to uninstall miner files").WithCause(err) } - return UpdateMinersConfig(func(configuration *MinersConfig) error { + return UpdateMinersConfig(func(minersConfig *MinersConfig) error { var updatedMiners []MinerAutostartConfig - for _, autostartEntry := range configuration.Miners { - if !equalFold(autostartEntry.MinerType, minerType) { - updatedMiners = append(updatedMiners, autostartEntry) + for _, minerConfig := range minersConfig.Miners { + if !equalFold(minerConfig.MinerType, minerType) { + updatedMiners = append(updatedMiners, minerConfig) } } - configuration.Miners = updatedMiners + minersConfig.Miners = updatedMiners return nil }) } // manager.updateMinerConfig("xmrig", true, config) // saves Autostart=true and the last-used config back to miners.json func (manager *Manager) updateMinerConfig(minerType string, autostart bool, config *Config) error { - return UpdateMinersConfig(func(configuration *MinersConfig) error { + return UpdateMinersConfig(func(minersConfig *MinersConfig) error { found := false - for i, autostartEntry := range configuration.Miners { - if equalFold(autostartEntry.MinerType, minerType) { - configuration.Miners[i].Autostart = autostart - configuration.Miners[i].Config = config + for i, minerConfig := range minersConfig.Miners { + if equalFold(minerConfig.MinerType, minerType) { + minersConfig.Miners[i].Autostart = autostart + minersConfig.Miners[i].Config = config found = true break } } if !found { - configuration.Miners = append(configuration.Miners, MinerAutostartConfig{ + minersConfig.Miners = append(minersConfig.Miners, MinerAutostartConfig{ MinerType: minerType, Autostart: autostart, Config: config, diff --git a/pkg/mining/repository.go b/pkg/mining/repository.go index 6dbcdc8..3fdadd6 100644 --- a/pkg/mining/repository.go +++ b/pkg/mining/repository.go @@ -23,9 +23,9 @@ type Repository[T any] interface { // repo := NewFileRepository[MinersConfig]("/home/alice/.config/lethean-desktop/miners.json", WithDefaults(defaultMinersConfig)) // data, err := repo.Load() type FileRepository[T any] struct { - mutex sync.RWMutex - path string - defaults func() T + mutex sync.RWMutex + filePath string + defaultValueProvider func() T } // repo := NewFileRepository[MinersConfig]("/home/alice/.config/lethean-desktop/miners.json", WithDefaults(defaultMinersConfig), myOption) @@ -34,14 +34,14 @@ type FileRepositoryOption[T any] func(*FileRepository[T]) // repo := NewFileRepository[MinersConfig]("/home/alice/.config/lethean-desktop/miners.json", WithDefaults(defaultMinersConfig)) func WithDefaults[T any](defaultsProvider func() T) FileRepositoryOption[T] { return func(repo *FileRepository[T]) { - repo.defaults = defaultsProvider + repo.defaultValueProvider = defaultsProvider } } // repo := NewFileRepository[MinersConfig]("/home/alice/.config/lethean-desktop/miners.json", WithDefaults(defaultMinersConfig)) -func NewFileRepository[T any](path string, options ...FileRepositoryOption[T]) *FileRepository[T] { +func NewFileRepository[T any](filePath string, options ...FileRepositoryOption[T]) *FileRepository[T] { repo := &FileRepository[T]{ - path: path, + filePath: filePath, } for _, option := range options { option(repo) @@ -57,11 +57,11 @@ func (repository *FileRepository[T]) Load() (T, error) { var result T - data, err := os.ReadFile(repository.path) + data, err := os.ReadFile(repository.filePath) if err != nil { if os.IsNotExist(err) { - if repository.defaults != nil { - return repository.defaults(), nil + if repository.defaultValueProvider != nil { + return repository.defaultValueProvider(), nil } return result, nil } @@ -85,7 +85,7 @@ func (repository *FileRepository[T]) Save(data T) error { // repository.saveUnlocked(updatedConfig) // used by Save() and Update() while the mutex is already held func (repository *FileRepository[T]) saveUnlocked(data T) error { - dir := filepath.Dir(repository.path) + dir := filepath.Dir(repository.filePath) if err := os.MkdirAll(dir, 0755); err != nil { return ErrInternal("failed to create directory").WithCause(err) } @@ -95,7 +95,7 @@ func (repository *FileRepository[T]) saveUnlocked(data T) error { return ErrInternal("failed to marshal data").WithCause(err) } - return AtomicWriteFile(repository.path, jsonData, 0600) + return AtomicWriteFile(repository.filePath, jsonData, 0600) } // repo.Update(func(configuration *MinersConfig) error { @@ -108,11 +108,11 @@ func (repository *FileRepository[T]) Update(modifier func(*T) error) error { // Load current data var data T - fileData, err := os.ReadFile(repository.path) + fileData, err := os.ReadFile(repository.filePath) if err != nil { if os.IsNotExist(err) { - if repository.defaults != nil { - data = repository.defaults() + if repository.defaultValueProvider != nil { + data = repository.defaultValueProvider() } } else { return ErrInternal("failed to read file").WithCause(err) @@ -132,9 +132,9 @@ func (repository *FileRepository[T]) Update(modifier func(*T) error) error { return repository.saveUnlocked(data) } -// path := repo.Path() // path == "/home/alice/.config/lethean-desktop/miners.json" +// filePath := repo.Path() // filePath == "/home/alice/.config/lethean-desktop/miners.json" func (repository *FileRepository[T]) Path() string { - return repository.path + return repository.filePath } // if !repo.Exists() { return defaults, nil } @@ -142,7 +142,7 @@ func (repository *FileRepository[T]) Exists() bool { repository.mutex.RLock() defer repository.mutex.RUnlock() - _, err := os.Stat(repository.path) + _, err := os.Stat(repository.filePath) return err == nil } @@ -151,7 +151,7 @@ func (repository *FileRepository[T]) Delete() error { repository.mutex.Lock() defer repository.mutex.Unlock() - err := os.Remove(repository.path) + err := os.Remove(repository.filePath) if os.IsNotExist(err) { return nil } diff --git a/pkg/mining/service.go b/pkg/mining/service.go index 46ae6dc..e49be53 100644 --- a/pkg/mining/service.go +++ b/pkg/mining/service.go @@ -558,15 +558,15 @@ func (service *Service) ServiceStartup(ctx context.Context) error { service.InitRouter() service.Server.Handler = service.Router - // serverErrorChannel captures ListenAndServe failures without blocking startup - serverErrorChannel := make(chan error, 1) + // serverErrors captures ListenAndServe failures without blocking startup. + serverErrors := make(chan error, 1) go func() { if err := service.Server.ListenAndServe(); err != nil && err != http.ErrServerClosed { logging.Error("server error", logging.Fields{"addr": service.Server.Addr, "error": err}) - serverErrorChannel <- err + serverErrors <- err } - close(serverErrorChannel) // prevent goroutine leak + close(serverErrors) // prevent goroutine leak }() go func() { @@ -584,7 +584,7 @@ func (service *Service) ServiceStartup(ctx context.Context) error { maxRetries := 50 // 50 * 100ms = 5 seconds max for i := 0; i < maxRetries; i++ { select { - case err := <-serverErrorChannel: + case err := <-serverErrors: if err != nil { return ErrInternal("failed to start server").WithCause(err) } @@ -606,64 +606,64 @@ func (service *Service) ServiceStartup(ctx context.Context) error { // service.InitRouter() // service.SetupRoutes() // re-call after adding middleware manually func (service *Service) SetupRoutes() { - apiRouterGroup := service.Router.Group(service.APIBasePath) + apiRoutes := service.Router.Group(service.APIBasePath) // Health endpoints (no auth required for orchestration/monitoring) - apiRouterGroup.GET("/health", service.handleHealth) - apiRouterGroup.GET("/ready", service.handleReady) + apiRoutes.GET("/health", service.handleHealth) + apiRoutes.GET("/ready", service.handleReady) // Apply authentication middleware if enabled if service.auth != nil { - apiRouterGroup.Use(service.auth.Middleware()) + apiRoutes.Use(service.auth.Middleware()) } { - apiRouterGroup.GET("/info", service.handleGetInfo) - apiRouterGroup.GET("/metrics", service.handleMetrics) - apiRouterGroup.POST("/doctor", service.handleDoctor) - apiRouterGroup.POST("/update", service.handleUpdateCheck) + apiRoutes.GET("/info", service.handleGetInfo) + apiRoutes.GET("/metrics", service.handleMetrics) + apiRoutes.POST("/doctor", service.handleDoctor) + apiRoutes.POST("/update", service.handleUpdateCheck) - minersRouterGroup := apiRouterGroup.Group("/miners") + minersRoutes := apiRoutes.Group("/miners") { - minersRouterGroup.GET("", service.handleListMiners) - minersRouterGroup.GET("/available", service.handleListAvailableMiners) - minersRouterGroup.POST("/:miner_name/install", service.handleInstallMiner) - minersRouterGroup.DELETE("/:miner_name/uninstall", service.handleUninstallMiner) - minersRouterGroup.DELETE("/:miner_name", service.handleStopMiner) - minersRouterGroup.GET("/:miner_name/stats", service.handleGetMinerStats) - minersRouterGroup.GET("/:miner_name/hashrate-history", service.handleGetMinerHashrateHistory) - minersRouterGroup.GET("/:miner_name/logs", service.handleGetMinerLogs) - minersRouterGroup.POST("/:miner_name/stdin", service.handleMinerStdin) + minersRoutes.GET("", service.handleListMiners) + minersRoutes.GET("/available", service.handleListAvailableMiners) + minersRoutes.POST("/:miner_name/install", service.handleInstallMiner) + minersRoutes.DELETE("/:miner_name/uninstall", service.handleUninstallMiner) + minersRoutes.DELETE("/:miner_name", service.handleStopMiner) + minersRoutes.GET("/:miner_name/stats", service.handleGetMinerStats) + minersRoutes.GET("/:miner_name/hashrate-history", service.handleGetMinerHashrateHistory) + minersRoutes.GET("/:miner_name/logs", service.handleGetMinerLogs) + minersRoutes.POST("/:miner_name/stdin", service.handleMinerStdin) } // Historical data endpoints (database-backed) - historyRouterGroup := apiRouterGroup.Group("/history") + historyRoutes := apiRoutes.Group("/history") { - historyRouterGroup.GET("/status", service.handleHistoryStatus) - historyRouterGroup.GET("/miners", service.handleAllMinersHistoricalStats) - historyRouterGroup.GET("/miners/:miner_name", service.handleMinerHistoricalStats) - historyRouterGroup.GET("/miners/:miner_name/hashrate", service.handleMinerHistoricalHashrate) + historyRoutes.GET("/status", service.handleHistoryStatus) + historyRoutes.GET("/miners", service.handleAllMinersHistoricalStats) + historyRoutes.GET("/miners/:miner_name", service.handleMinerHistoricalStats) + historyRoutes.GET("/miners/:miner_name/hashrate", service.handleMinerHistoricalHashrate) } - profilesRouterGroup := apiRouterGroup.Group("/profiles") + profilesRoutes := apiRoutes.Group("/profiles") { - profilesRouterGroup.GET("", service.handleListProfiles) - profilesRouterGroup.POST("", service.handleCreateProfile) - profilesRouterGroup.GET("/:id", service.handleGetProfile) - profilesRouterGroup.PUT("/:id", service.handleUpdateProfile) - profilesRouterGroup.DELETE("/:id", service.handleDeleteProfile) - profilesRouterGroup.POST("/:id/start", service.handleStartMinerWithProfile) + profilesRoutes.GET("", service.handleListProfiles) + profilesRoutes.POST("", service.handleCreateProfile) + profilesRoutes.GET("/:id", service.handleGetProfile) + profilesRoutes.PUT("/:id", service.handleUpdateProfile) + profilesRoutes.DELETE("/:id", service.handleDeleteProfile) + profilesRoutes.POST("/:id/start", service.handleStartMinerWithProfile) } // WebSocket endpoint for real-time events - websocketRouterGroup := apiRouterGroup.Group("/ws") + websocketRoutes := apiRoutes.Group("/ws") { - websocketRouterGroup.GET("/events", service.handleWebSocketEvents) + websocketRoutes.GET("/events", service.handleWebSocketEvents) } // Add P2P node endpoints if node service is available if service.NodeService != nil { - service.NodeService.SetupRoutes(apiRouterGroup) + service.NodeService.SetupRoutes(apiRoutes) } }