Apply AX naming and comment cleanup
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run

This commit is contained in:
Virgil 2026-04-04 05:36:07 +00:00
parent 68c826a3d8
commit 4e8283c84f
4 changed files with 115 additions and 115 deletions

View file

@ -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)
}

View file

@ -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,

View file

@ -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
}

View file

@ -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)
}
}