fix: Address medium severity code quality issues

- Fix deprecated strings.Title usage with golang.org/x/text/cases
- Replace log.Fatalf in service startup with channel-based error handling
- Add graceful SIGTERM before SIGKILL in Stop() for proper cleanup
- Add mutex protection for LogBuffer access in GetLogs()
- Add instance name sanitization with regex to prevent injection
- Add error logging in updateInstallationCache for failed operations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
snider 2025-12-31 01:15:35 +00:00
parent 68c0033c55
commit f2afdeeb82
6 changed files with 82 additions and 95 deletions

View file

@ -12,6 +12,8 @@ import (
"github.com/Snider/Mining/pkg/mining"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
var (
@ -121,7 +123,7 @@ var serveCmd = &cobra.Command{
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting miner stats: %v\n", err)
} else {
fmt.Printf("Miner Status for %s:\n", strings.Title(minerName))
fmt.Printf("Miner Status for %s:\n", cases.Title(language.English).String(minerName))
fmt.Printf(" Hash Rate: %d H/s\n", stats.Hashrate)
fmt.Printf(" Shares: %d\n", stats.Shares)
fmt.Printf(" Rejected: %d\n", stats.Rejected)

View file

@ -2,9 +2,10 @@ package cmd
import (
"fmt"
"strings"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
// statusCmd represents the status command
@ -27,7 +28,7 @@ var statusCmd = &cobra.Command{
return fmt.Errorf("failed to get miner stats: %w", err)
}
fmt.Printf("Miner Status for %s:\n", strings.Title(minerName))
fmt.Printf("Miner Status for %s:\n", cases.Title(language.English).String(minerName))
fmt.Printf(" Hash Rate: %d H/s\n", stats.Hashrate)
fmt.Printf(" Shares: %d\n", stats.Shares)
fmt.Printf(" Rejected: %d\n", stats.Rejected)

View file

@ -139,94 +139,33 @@ nav:
- Home: index.md
- Getting Started:
- Introduction: getting-started/introduction.md
- Installation: getting-started/installation.md
- Quick Start: getting-started/quickstart.md
- Configuration: getting-started/configuration.md
- First Mining Session: getting-started/first-session.md
- Installation: getting-started/index.md
- Quick Start: getting-started/quick-start.md
- CLI Reference:
- Overview: cli/overview.md
- serve: cli/serve.md
- start: cli/start.md
- stop: cli/stop.md
- status: cli/status.md
- install: cli/install.md
- uninstall: cli/uninstall.md
- update: cli/update.md
- list: cli/list.md
- doctor: cli/doctor.md
- profiles: cli/profiles.md
- User Guide:
- CLI: user-guide/cli.md
- Web Dashboard: user-guide/web-dashboard.md
- Desktop App: user-guide/desktop-app.md
- API Reference:
- Overview: api/overview.md
- Authentication: api/authentication.md
- System Endpoints: api/system.md
- Miner Endpoints: api/miners.md
- Profile Endpoints: api/profiles.md
- Stats Endpoints: api/stats.md
- Swagger UI: api/swagger.md
- Error Handling: api/errors.md
- Overview: api/index.md
- Endpoints: api/endpoints.md
- Swagger Docs: swagger.json
- Web Dashboard:
- Overview: dashboard/overview.md
- Installation: dashboard/installation.md
- Web Component: dashboard/web-component.md
- Features: dashboard/features.md
- Configuration: dashboard/configuration.md
- Customization: dashboard/customization.md
- Desktop Application:
- Overview: desktop/overview.md
- Installation: desktop/installation.md
- Building from Source: desktop/building.md
- Platform Support: desktop/platforms.md
- Configuration: desktop/configuration.md
- Troubleshooting: desktop/troubleshooting.md
- Development Guide:
- Setup: development/setup.md
- Project Structure: development/structure.md
- Building: development/building.md
- Testing: development/testing.md
- E2E Tests: development/e2e-tests.md
- Code Style: development/code-style.md
- Development:
- Setup: development/index.md
- Architecture: development/architecture.md
- Contributing: development/contributing.md
- Release Process: development/releases.md
- Architecture:
- Overview: architecture/overview.md
- Go Backend: architecture/backend.md
- Miner Interface: architecture/miner-interface.md
- Manager System: architecture/manager.md
- REST Service: architecture/rest-service.md
- XMRig Implementation: architecture/xmrig.md
- Profile System: architecture/profiles.md
- Stats Collection: architecture/stats.md
- XDG Directories: architecture/xdg.md
- Reference:
- Mining Pools: reference/pools.md
- Algorithms: reference/algorithms.md
- Pool Integration:
- Research Overview: pools/research.md
- Supported Algorithms: pools/algorithms.md
- Stratum Protocol: pools/stratum.md
- Pool Configuration: pools/configuration.md
- ETChash Pools: pools/etchash.md
- ProgPowZ Pools: pools/progpowz.md
- Blake3DCR Pools: pools/blake3dcr.md
- Custom Pools: pools/custom.md
- Miners:
- Overview: miners/overview.md
- XMRig: miners/xmrig.md
- Adding New Miners: miners/adding-miners.md
- Troubleshooting:
- Common Issues: troubleshooting/common-issues.md
- Logs: troubleshooting/logs.md
- Performance: troubleshooting/performance.md
- GPU Issues: troubleshooting/gpu.md
- Network Issues: troubleshooting/network.md
- FAQ: faq.md
- Changelog: changelog.md
- License: license.md
- Legacy Docs:
- Start Here: 00-START-HERE.md
- Quick Reference: QUICK-REFERENCE.md
- Pool Research: pool-research.md
- Pool Integration: pool-integration-guide.md
- Pool README: POOL-RESEARCH-README.md
- Files Index: FILES-INDEX.md
- Manifest: MANIFEST.md

View file

@ -4,6 +4,7 @@ import (
"fmt"
"log"
"net"
"regexp"
"strconv"
"strings"
"sync"
@ -12,6 +13,9 @@ import (
"github.com/Snider/Mining/pkg/database"
)
// sanitizeInstanceName ensures the instance name only contains safe characters.
var instanceNameRegex = regexp.MustCompile(`[^a-zA-Z0-9_/-]`)
// ManagerInterface defines the contract for a miner manager.
type ManagerInterface interface {
StartMiner(minerType string, config *Config) (Miner, error)
@ -203,7 +207,9 @@ func (m *Manager) StartMiner(minerType string, config *Config) (Miner, error) {
instanceName := miner.GetName()
if config.Algo != "" {
instanceName = fmt.Sprintf("%s-%s", instanceName, config.Algo)
// Sanitize algo to prevent directory traversal or invalid filenames
sanitizedAlgo := instanceNameRegex.ReplaceAllString(config.Algo, "_")
instanceName = fmt.Sprintf("%s-%s", instanceName, sanitizedAlgo)
} else {
instanceName = fmt.Sprintf("%s-%d", instanceName, time.Now().UnixNano()%1000)
}

View file

@ -18,6 +18,7 @@ import (
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/adrg/xdg"
@ -127,7 +128,8 @@ func (b *BaseMiner) GetBinaryPath() string {
return b.MinerBinary
}
// Stop terminates the miner process.
// Stop terminates the miner process gracefully.
// It first tries SIGTERM to allow cleanup, then SIGKILL if needed.
func (b *BaseMiner) Stop() error {
b.mu.Lock()
defer b.mu.Unlock()
@ -142,6 +144,25 @@ func (b *BaseMiner) Stop() error {
b.stdinPipe = nil
}
// Try graceful shutdown with SIGTERM first (Unix only)
if runtime.GOOS != "windows" {
if err := b.cmd.Process.Signal(syscall.SIGTERM); err == nil {
// Wait up to 3 seconds for graceful shutdown
done := make(chan error, 1)
go func() {
_, err := b.cmd.Process.Wait()
done <- err
}()
select {
case <-done:
return nil
case <-time.After(3 * time.Second):
// Process didn't exit, force kill
}
}
}
return b.cmd.Process.Kill()
}
@ -371,10 +392,14 @@ func (b *BaseMiner) GetLowResHistoryLength() int {
// GetLogs returns the captured log output from the miner process.
func (b *BaseMiner) GetLogs() []string {
if b.LogBuffer == nil {
b.mu.RLock()
logBuffer := b.LogBuffer
b.mu.RUnlock()
if logBuffer == nil {
return []string{}
}
return b.LogBuffer.GetLines()
return logBuffer.GetLines()
}
// ReduceHashrateHistory aggregates and trims hashrate data.

View file

@ -109,9 +109,13 @@ func (s *Service) ServiceStartup(ctx context.Context) error {
s.InitRouter()
s.Server.Handler = s.Router
// Channel to capture server startup errors
errChan := make(chan error, 1)
go func() {
if err := s.Server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("could not listen on %s: %v\n", s.Server.Addr, err)
log.Printf("Server error on %s: %v", s.Server.Addr, err)
errChan <- err
}
}()
@ -121,11 +125,18 @@ func (s *Service) ServiceStartup(ctx context.Context) error {
ctxShutdown, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := s.Server.Shutdown(ctxShutdown); err != nil {
log.Fatalf("server shutdown failed: %+v", err)
log.Printf("Server shutdown error: %v", err)
}
}()
return nil
// Give the server a moment to start and check for immediate errors
select {
case err := <-errChan:
return fmt.Errorf("failed to start server: %w", err)
case <-time.After(100 * time.Millisecond):
// Server started successfully
return nil
}
}
// SetupRoutes configures all API routes on the Gin router.
@ -230,7 +241,10 @@ func (s *Service) updateInstallationCache() (*SystemInfo, error) {
default:
continue
}
details, _ := miner.CheckInstallation()
details, err := miner.CheckInstallation()
if err != nil {
log.Printf("Warning: failed to check installation for %s: %v", availableMiner.Name, err)
}
systemInfo.InstalledMinersInfo = append(systemInfo.InstalledMinersInfo, details)
}