From 9102b25f55e92769708e684ab5ba960f22772c64 Mon Sep 17 00:00:00 2001 From: Virgil Date: Sat, 4 Apr 2026 04:16:13 +0000 Subject: [PATCH] Apply AX naming and comment cleanup --- cmd/mining/cmd/doctor.go | 2 +- cmd/mining/cmd/install.go | 14 +- cmd/mining/cmd/list.go | 2 +- cmd/mining/cmd/node.go | 74 +++++----- cmd/mining/cmd/peer.go | 49 +++--- cmd/mining/cmd/remote.go | 92 ++++++------ cmd/mining/cmd/root.go | 10 +- cmd/mining/cmd/serve.go | 18 +-- cmd/mining/cmd/simulate.go | 28 ++-- cmd/mining/cmd/start.go | 2 +- cmd/mining/cmd/status.go | 2 +- cmd/mining/cmd/stop.go | 2 +- cmd/mining/cmd/uninstall.go | 7 +- cmd/mining/cmd/update.go | 12 +- pkg/mining/manager.go | 246 +++++++++++++++---------------- pkg/mining/service.go | 286 ++++++++++++++++++------------------ 16 files changed, 421 insertions(+), 425 deletions(-) diff --git a/cmd/mining/cmd/doctor.go b/cmd/mining/cmd/doctor.go index 84fab2a..5369886 100644 --- a/cmd/mining/cmd/doctor.go +++ b/cmd/mining/cmd/doctor.go @@ -29,7 +29,7 @@ func validateConfigPath(configPath string) error { return nil } -// doctorCmd represents the doctor command +// doctorCmd.Use == "doctor" and RunE refreshes the local installation cache. var doctorCmd = &cobra.Command{ Use: "doctor", Short: "Check and refresh the status of installed miners", diff --git a/cmd/mining/cmd/install.go b/cmd/mining/cmd/install.go index f8e0d4e..69b0045 100644 --- a/cmd/mining/cmd/install.go +++ b/cmd/mining/cmd/install.go @@ -5,12 +5,12 @@ import ( "runtime" "time" - "github.com/Masterminds/semver/v3" "forge.lthn.ai/Snider/Mining/pkg/mining" + "github.com/Masterminds/semver/v3" "github.com/spf13/cobra" ) -// installCmd represents the install command +// installCmd.Use == "install [miner_type]" and RunE installs or updates the miner. var installCmd = &cobra.Command{ Use: "install [miner_type]", Short: "Install or update a miner", @@ -27,7 +27,7 @@ var installCmd = &cobra.Command{ return fmt.Errorf("unknown miner type: %s", minerType) } - // Check if it's already installed and up-to-date + // miner.CheckInstallation() // returns the installed version before deciding whether to update details, err := miner.CheckInstallation() if err == nil && details.IsInstalled { latestVersionStr, err := miner.GetLatestVersion() @@ -50,7 +50,7 @@ var installCmd = &cobra.Command{ return fmt.Errorf("failed to install/update miner: %w", err) } - // Get fresh details after installation + // miner.CheckInstallation() // returns the post-install path and version for the success message finalDetails, err := miner.CheckInstallation() if err != nil { return fmt.Errorf("failed to verify installation: %w", err) @@ -58,7 +58,7 @@ var installCmd = &cobra.Command{ fmt.Printf("%s installed successfully to %s (version %s).\n", miner.GetName(), finalDetails.Path, finalDetails.Version) - // Update the cache after a successful installation + // updateDoctorCache() // refreshes the cached installation details after a successful install fmt.Println("Updating installation cache...") if err := updateDoctorCache(); err != nil { fmt.Printf("Warning: failed to update doctor cache: %v\n", err) @@ -68,7 +68,7 @@ var installCmd = &cobra.Command{ }, } -// updateDoctorCache runs the core logic of the doctor command to refresh the cache. +// updateDoctorCache() // refreshes the cache used by `mining doctor` and `mining update`. func updateDoctorCache() error { manager := getManager() availableMiners := manager.ListAvailableMiners() @@ -92,7 +92,7 @@ func updateDoctorCache() error { allDetails = append(allDetails, details) } - // Create the SystemInfo struct that the /info endpoint expects + // mining.SystemInfo{Timestamp: time.Now(), OS: runtime.GOOS} // matches the cache shape returned by the doctor command systemInfo := &mining.SystemInfo{ Timestamp: time.Now(), OS: runtime.GOOS, diff --git a/cmd/mining/cmd/list.go b/cmd/mining/cmd/list.go index 4fbe8ab..7a8b850 100644 --- a/cmd/mining/cmd/list.go +++ b/cmd/mining/cmd/list.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" ) -// listCmd represents the list command +// listCmd.Use == "list" and RunE prints running and available miners. var listCmd = &cobra.Command{ Use: "list", Short: "List running and available miners", diff --git a/cmd/mining/cmd/node.go b/cmd/mining/cmd/node.go index c122188..55b12b9 100644 --- a/cmd/mining/cmd/node.go +++ b/cmd/mining/cmd/node.go @@ -22,14 +22,14 @@ var ( peerRegistryErr error ) -// nodeCmd represents the node parent command +// nodeCmd.Use == "node" and RunE groups identity and P2P subcommands. var nodeCmd = &cobra.Command{ Use: "node", Short: "Manage P2P node identity and connections", Long: `Manage the node's identity, view status, and control P2P networking.`, } -// nodeInitCmd initializes a new node identity +// nodeInitCmd.Use == "init" and RunE creates a node identity. var nodeInitCmd = &cobra.Command{ Use: "init", Short: "Initialize node identity", @@ -43,12 +43,12 @@ This creates the node's cryptographic identity for secure P2P communication.`, return fmt.Errorf("--name is required") } - nm, err := node.NewNodeManager() + nodeManager, err := node.NewNodeManager() if err != nil { return fmt.Errorf("failed to create node manager: %w", err) } - if nm.HasIdentity() { + if nodeManager.HasIdentity() { return fmt.Errorf("node identity already exists. Use 'node reset' to create a new one") } @@ -64,11 +64,11 @@ This creates the node's cryptographic identity for secure P2P communication.`, return fmt.Errorf("invalid role: %s (use controller, worker, or dual)", role) } - if err := nm.GenerateIdentity(name, nodeRole); err != nil { + if err := nodeManager.GenerateIdentity(name, nodeRole); err != nil { return fmt.Errorf("failed to generate identity: %w", err) } - identity := nm.GetIdentity() + identity := nodeManager.GetIdentity() fmt.Println("Node identity created successfully!") fmt.Println() fmt.Printf(" ID: %s\n", identity.ID) @@ -81,24 +81,24 @@ This creates the node's cryptographic identity for secure P2P communication.`, }, } -// nodeInfoCmd shows current node identity +// nodeInfoCmd.Use == "info" and RunE prints the current node identity. var nodeInfoCmd = &cobra.Command{ Use: "info", Short: "Show node identity and status", Long: `Display the current node's identity, role, and connection status.`, RunE: func(cmd *cobra.Command, args []string) error { - nm, err := node.NewNodeManager() + nodeManager, err := node.NewNodeManager() if err != nil { return fmt.Errorf("failed to create node manager: %w", err) } - if !nm.HasIdentity() { + if !nodeManager.HasIdentity() { fmt.Println("No node identity found.") fmt.Println("Run 'node init --name ' to create one.") return nil } - identity := nm.GetIdentity() + identity := nodeManager.GetIdentity() fmt.Println("Node Identity:") fmt.Println() fmt.Printf(" ID: %s\n", identity.ID) @@ -107,12 +107,12 @@ var nodeInfoCmd = &cobra.Command{ fmt.Printf(" Public Key: %s\n", identity.PublicKey) fmt.Printf(" Created: %s\n", identity.CreatedAt.Format(time.RFC3339)) - // Show peer info if available - pr, err := node.NewPeerRegistry() + // node.NewPeerRegistry() // loads registered peers to print the connected count + peerRegistry, err := node.NewPeerRegistry() if err == nil { fmt.Println() - fmt.Printf(" Registered Peers: %d\n", pr.Count()) - connected := pr.GetConnectedPeers() + fmt.Printf(" Registered Peers: %d\n", peerRegistry.Count()) + connected := peerRegistry.GetConnectedPeers() fmt.Printf(" Connected Peers: %d\n", len(connected)) } @@ -120,7 +120,7 @@ var nodeInfoCmd = &cobra.Command{ }, } -// nodeServeCmd starts the P2P server +// nodeServeCmd.Use == "serve" and RunE starts the P2P server. var nodeServeCmd = &cobra.Command{ Use: "serve", Short: "Start P2P server for remote connections", @@ -129,16 +129,16 @@ This allows other nodes to connect, send commands, and receive stats.`, RunE: func(cmd *cobra.Command, args []string) error { listen, _ := cmd.Flags().GetString("listen") - nm, err := node.NewNodeManager() + nodeManager, err := node.NewNodeManager() if err != nil { return fmt.Errorf("failed to create node manager: %w", err) } - if !nm.HasIdentity() { + if !nodeManager.HasIdentity() { return fmt.Errorf("no node identity found. Run 'node init --name ' first") } - pr, err := node.NewPeerRegistry() + peerRegistry, err := node.NewPeerRegistry() if err != nil { return fmt.Errorf("failed to create peer registry: %w", err) } @@ -148,43 +148,43 @@ This allows other nodes to connect, send commands, and receive stats.`, config.ListenAddr = listen } - transport := node.NewTransport(nm, pr, config) + transport := node.NewTransport(nodeManager, peerRegistry, config) - // Create worker to handle incoming messages - worker := node.NewWorker(nm, transport) + // node.NewWorker(nodeManager, transport) // handles incoming remote commands + worker := node.NewWorker(nodeManager, transport) worker.RegisterWithTransport() if err := transport.Start(); err != nil { return fmt.Errorf("failed to start transport: %w", err) } - identity := nm.GetIdentity() + identity := nodeManager.GetIdentity() fmt.Printf("P2P server started on %s\n", config.ListenAddr) fmt.Printf("Node ID: %s (%s)\n", identity.ID, identity.Name) fmt.Printf("Role: %s\n", identity.Role) fmt.Println() fmt.Println("Press Ctrl+C to stop...") - // Set up signal handling for graceful shutdown (including SIGHUP for terminal disconnect) - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) + // signalChannel captures Ctrl+C and terminal disconnects for a clean shutdown. + signalChannel := make(chan os.Signal, 1) + signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) - // Wait for shutdown signal - sig := <-sigChan + // signalChannel <- os.Interrupt // blocks until shutdown is requested + sig := <-signalChannel fmt.Printf("\nReceived signal %v, shutting down...\n", sig) - // Graceful shutdown: stop transport and cleanup resources + // transport.Stop() // stops the socket listener before the peer registry is flushed if err := transport.Stop(); err != nil { fmt.Printf("Warning: error during transport shutdown: %v\n", err) - // Force cleanup on Stop() failure + // peerRegistry.GetConnectedPeers() // clears connected flags when transport shutdown fails fmt.Println("Forcing resource cleanup...") - for _, peer := range pr.GetConnectedPeers() { - pr.SetConnected(peer.ID, false) + for _, peer := range peerRegistry.GetConnectedPeers() { + peerRegistry.SetConnected(peer.ID, false) } } - // Ensure peer registry is flushed to disk - if err := pr.Close(); err != nil { + // peerRegistry.Close() // flushes peer state to disk during shutdown + if err := peerRegistry.Close(); err != nil { fmt.Printf("Warning: error closing peer registry: %v\n", err) } @@ -193,7 +193,7 @@ This allows other nodes to connect, send commands, and receive stats.`, }, } -// nodeResetCmd deletes the node identity +// nodeResetCmd.Use == "reset" and RunE deletes the node identity. var nodeResetCmd = &cobra.Command{ Use: "reset", Short: "Delete node identity and start fresh", @@ -201,12 +201,12 @@ var nodeResetCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { force, _ := cmd.Flags().GetBool("force") - nm, err := node.NewNodeManager() + nodeManager, err := node.NewNodeManager() if err != nil { return fmt.Errorf("failed to create node manager: %w", err) } - if !nm.HasIdentity() { + if !nodeManager.HasIdentity() { fmt.Println("No node identity to reset.") return nil } @@ -219,7 +219,7 @@ var nodeResetCmd = &cobra.Command{ return nil } - if err := nm.Delete(); err != nil { + if err := nodeManager.Delete(); err != nil { return fmt.Errorf("failed to delete identity: %w", err) } diff --git a/cmd/mining/cmd/peer.go b/cmd/mining/cmd/peer.go index ec49b5f..e3c87eb 100644 --- a/cmd/mining/cmd/peer.go +++ b/cmd/mining/cmd/peer.go @@ -8,16 +8,15 @@ import ( "github.com/spf13/cobra" ) -// Note: findPeerByPartialID is defined in remote.go and used for peer lookup - -// peerCmd represents the peer parent command +// findPeerByPartialID("a1b2c3") // defined in remote.go and used by peer subcommands +// peerCmd.Use == "peer" and RunE groups peer-management subcommands. var peerCmd = &cobra.Command{ Use: "peer", Short: "Manage peer nodes", Long: `Add, remove, and manage connections to peer nodes.`, } -// peerAddCmd adds a new peer +// peerAddCmd.Use == "add" and RunE registers a new peer by address. var peerAddCmd = &cobra.Command{ Use: "add", Short: "Add a peer node", @@ -31,16 +30,16 @@ to exchange public keys and establish a secure connection.`, return fmt.Errorf("--address is required") } - nm, err := getNodeManager() + nodeManager, err := getNodeManager() if err != nil { return fmt.Errorf("failed to get node manager: %w", err) } - if !nm.HasIdentity() { + if !nodeManager.HasIdentity() { return fmt.Errorf("no node identity found. Run 'node init' first") } - pr, err := getPeerRegistry() + peerRegistry, err := getPeerRegistry() if err != nil { return fmt.Errorf("failed to get peer registry: %w", err) } @@ -56,7 +55,7 @@ to exchange public keys and establish a secure connection.`, Score: 50, } - if err := pr.AddPeer(peer); err != nil { + if err := peerRegistry.AddPeer(peer); err != nil { return fmt.Errorf("failed to add peer: %w", err) } @@ -66,18 +65,18 @@ to exchange public keys and establish a secure connection.`, }, } -// peerListCmd lists all registered peers +// peerListCmd.Use == "list" and RunE prints registered peers. var peerListCmd = &cobra.Command{ Use: "list", Short: "List registered peers", Long: `Display all registered peers with their connection status.`, RunE: func(cmd *cobra.Command, args []string) error { - pr, err := getPeerRegistry() + peerRegistry, err := getPeerRegistry() if err != nil { return fmt.Errorf("failed to get peer registry: %w", err) } - peers := pr.ListPeers() + peers := peerRegistry.ListPeers() if len(peers) == 0 { fmt.Println("No peers registered.") fmt.Println("Use 'peer add --address --name ' to add one.") @@ -107,7 +106,7 @@ var peerListCmd = &cobra.Command{ }, } -// peerRemoveCmd removes a peer +// peerRemoveCmd.Use == "remove " and RunE removes the selected peer. var peerRemoveCmd = &cobra.Command{ Use: "remove ", Short: "Remove a peer from registry", @@ -121,12 +120,12 @@ var peerRemoveCmd = &cobra.Command{ return fmt.Errorf("peer not found: %s", peerID) } - pr, err := getPeerRegistry() + peerRegistry, err := getPeerRegistry() if err != nil { return fmt.Errorf("failed to get peer registry: %w", err) } - if err := pr.RemovePeer(peer.ID); err != nil { + if err := peerRegistry.RemovePeer(peer.ID); err != nil { return fmt.Errorf("failed to remove peer: %w", err) } @@ -135,7 +134,7 @@ var peerRemoveCmd = &cobra.Command{ }, } -// peerPingCmd pings a peer +// peerPingCmd.Use == "ping " and RunE prints a ping placeholder. var peerPingCmd = &cobra.Command{ Use: "ping ", Short: "Ping a peer and update metrics", @@ -160,7 +159,7 @@ var peerPingCmd = &cobra.Command{ }, } -// peerOptimalCmd shows the optimal peer based on metrics +// peerOptimalCmd.Use == "optimal" and RunE prints the best peer by score. var peerOptimalCmd = &cobra.Command{ Use: "optimal", Short: "Show the optimal peer based on metrics", @@ -169,18 +168,18 @@ ping latency, hop count, geographic distance, and reliability score.`, RunE: func(cmd *cobra.Command, args []string) error { count, _ := cmd.Flags().GetInt("count") - pr, err := getPeerRegistry() + peerRegistry, err := getPeerRegistry() if err != nil { return fmt.Errorf("failed to get peer registry: %w", err) } - if pr.Count() == 0 { + if peerRegistry.Count() == 0 { fmt.Println("No peers registered.") return nil } if count == 1 { - peer := pr.SelectOptimalPeer() + peer := peerRegistry.SelectOptimalPeer() if peer == nil { fmt.Println("No optimal peer found.") return nil @@ -194,7 +193,7 @@ ping latency, hop count, geographic distance, and reliability score.`, fmt.Printf(" Geo: %.1f km\n", peer.GeoKM) fmt.Printf(" Score: %.1f\n", peer.Score) } else { - peers := pr.SelectNearestPeers(count) + peers := peerRegistry.SelectNearestPeers(count) if len(peers) == 0 { fmt.Println("No peers found.") return nil @@ -215,21 +214,21 @@ ping latency, hop count, geographic distance, and reliability score.`, func init() { rootCmd.AddCommand(peerCmd) - // peer add + // rootCmd.AddCommand(peerAddCmd) // exposes `peer add --address 10.0.0.2:9090 --name worker-1` peerCmd.AddCommand(peerAddCmd) peerAddCmd.Flags().StringP("address", "a", "", "Peer address (host:port)") peerAddCmd.Flags().StringP("name", "n", "", "Peer name") - // peer list + // rootCmd.AddCommand(peerListCmd) // exposes `peer list` peerCmd.AddCommand(peerListCmd) - // peer remove + // rootCmd.AddCommand(peerRemoveCmd) // exposes `peer remove ` peerCmd.AddCommand(peerRemoveCmd) - // peer ping + // rootCmd.AddCommand(peerPingCmd) // exposes `peer ping ` peerCmd.AddCommand(peerPingCmd) - // peer optimal + // rootCmd.AddCommand(peerOptimalCmd) // exposes `peer optimal --count 4` peerCmd.AddCommand(peerOptimalCmd) peerOptimalCmd.Flags().IntP("count", "c", 1, "Number of optimal peers to show") } diff --git a/cmd/mining/cmd/remote.go b/cmd/mining/cmd/remote.go index c80ea64..acec93d 100644 --- a/cmd/mining/cmd/remote.go +++ b/cmd/mining/cmd/remote.go @@ -17,20 +17,20 @@ var ( controllerErr error ) -// remoteCmd represents the remote parent command +// remoteCmd.Use == "remote" and RunE groups remote node commands. var remoteCmd = &cobra.Command{ Use: "remote", Short: "Control remote mining nodes", Long: `Send commands to remote worker nodes and retrieve their status.`, } -// remoteStatusCmd shows stats from remote peers +// remoteStatusCmd.Use == "status [peer-id]" and RunE prints remote stats. var remoteStatusCmd = &cobra.Command{ Use: "status [peer-id]", Short: "Get mining status from remote peers", Long: `Display mining statistics from all connected peers or a specific peer.`, RunE: func(cmd *cobra.Command, args []string) error { - ctrl, err := getController() + controller, err := getController() if err != nil { return err } @@ -43,7 +43,7 @@ var remoteStatusCmd = &cobra.Command{ return fmt.Errorf("peer not found: %s", peerID) } - stats, err := ctrl.GetRemoteStats(peer.ID) + stats, err := controller.GetRemoteStats(peer.ID) if err != nil { return fmt.Errorf("failed to get stats: %w", err) } @@ -51,17 +51,17 @@ var remoteStatusCmd = &cobra.Command{ printPeerStats(peer, stats) } else { // Get stats from all peers - allStats := ctrl.GetAllStats() + allStats := controller.GetAllStats() if len(allStats) == 0 { fmt.Println("No connected peers.") return nil } - pr, _ := getPeerRegistry() + peerRegistry, _ := getPeerRegistry() var totalHashrate float64 for peerID, stats := range allStats { - peer := pr.GetPeer(peerID) + peer := peerRegistry.GetPeer(peerID) if peer != nil { printPeerStats(peer, stats) for _, miner := range stats.Miners { @@ -78,7 +78,7 @@ var remoteStatusCmd = &cobra.Command{ }, } -// remoteStartCmd starts a miner on a remote peer +// remoteStartCmd.Use == "start " and RunE starts a miner on a peer. var remoteStartCmd = &cobra.Command{ Use: "start ", Short: "Start miner on remote peer", @@ -97,13 +97,13 @@ var remoteStartCmd = &cobra.Command{ return fmt.Errorf("peer not found: %s", peerID) } - ctrl, err := getController() + controller, err := getController() if err != nil { return err } fmt.Printf("Starting %s miner on %s with profile %s...\n", minerType, peer.Name, profileID) - if err := ctrl.StartRemoteMiner(peer.ID, minerType, profileID, nil); err != nil { + if err := controller.StartRemoteMiner(peer.ID, minerType, profileID, nil); err != nil { return fmt.Errorf("failed to start miner: %w", err) } @@ -112,7 +112,7 @@ var remoteStartCmd = &cobra.Command{ }, } -// remoteStopCmd stops a miner on a remote peer +// remoteStopCmd.Use == "stop [miner-name]" and RunE stops a remote miner. var remoteStopCmd = &cobra.Command{ Use: "stop [miner-name]", Short: "Stop miner on remote peer", @@ -136,13 +136,13 @@ var remoteStopCmd = &cobra.Command{ return fmt.Errorf("miner name required (as argument or --miner flag)") } - ctrl, err := getController() + controller, err := getController() if err != nil { return err } fmt.Printf("Stopping miner %s on %s...\n", minerName, peer.Name) - if err := ctrl.StopRemoteMiner(peer.ID, minerName); err != nil { + if err := controller.StopRemoteMiner(peer.ID, minerName); err != nil { return fmt.Errorf("failed to stop miner: %w", err) } @@ -151,7 +151,7 @@ var remoteStopCmd = &cobra.Command{ }, } -// remoteLogsCmd gets logs from a remote miner +// remoteLogsCmd.Use == "logs " and RunE prints remote logs. var remoteLogsCmd = &cobra.Command{ Use: "logs ", Short: "Get console logs from remote miner", @@ -167,12 +167,12 @@ var remoteLogsCmd = &cobra.Command{ return fmt.Errorf("peer not found: %s", peerID) } - ctrl, err := getController() + controller, err := getController() if err != nil { return err } - logLines, err := ctrl.GetRemoteLogs(peer.ID, minerName, lines) + logLines, err := controller.GetRemoteLogs(peer.ID, minerName, lines) if err != nil { return fmt.Errorf("failed to get logs: %w", err) } @@ -187,7 +187,7 @@ var remoteLogsCmd = &cobra.Command{ }, } -// remoteConnectCmd connects to a peer +// remoteConnectCmd.Use == "connect " and RunE opens a peer connection. var remoteConnectCmd = &cobra.Command{ Use: "connect ", Short: "Connect to a remote peer", @@ -200,13 +200,13 @@ var remoteConnectCmd = &cobra.Command{ return fmt.Errorf("peer not found: %s", peerID) } - ctrl, err := getController() + controller, err := getController() if err != nil { return err } fmt.Printf("Connecting to %s at %s...\n", peer.Name, peer.Address) - if err := ctrl.ConnectToPeer(peer.ID); err != nil { + if err := controller.ConnectToPeer(peer.ID); err != nil { return fmt.Errorf("failed to connect: %w", err) } @@ -215,7 +215,7 @@ var remoteConnectCmd = &cobra.Command{ }, } -// remoteDisconnectCmd disconnects from a peer +// remoteDisconnectCmd.Use == "disconnect " and RunE closes a peer connection. var remoteDisconnectCmd = &cobra.Command{ Use: "disconnect ", Short: "Disconnect from a remote peer", @@ -228,13 +228,13 @@ var remoteDisconnectCmd = &cobra.Command{ return fmt.Errorf("peer not found: %s", peerID) } - ctrl, err := getController() + controller, err := getController() if err != nil { return err } fmt.Printf("Disconnecting from %s...\n", peer.Name) - if err := ctrl.DisconnectFromPeer(peer.ID); err != nil { + if err := controller.DisconnectFromPeer(peer.ID); err != nil { return fmt.Errorf("failed to disconnect: %w", err) } @@ -243,7 +243,7 @@ var remoteDisconnectCmd = &cobra.Command{ }, } -// remotePingCmd pings a peer +// remotePingCmd.Use == "ping " and RunE averages multiple ping samples. var remotePingCmd = &cobra.Command{ Use: "ping ", Short: "Ping a remote peer", @@ -258,7 +258,7 @@ var remotePingCmd = &cobra.Command{ return fmt.Errorf("peer not found: %s", peerID) } - ctrl, err := getController() + controller, err := getController() if err != nil { return err } @@ -269,7 +269,7 @@ var remotePingCmd = &cobra.Command{ var successful int for i := 0; i < count; i++ { - rtt, err := ctrl.PingPeer(peer.ID) + rtt, err := controller.PingPeer(peer.ID) if err != nil { fmt.Printf(" Ping %d: timeout\n", i+1) continue @@ -296,29 +296,29 @@ var remotePingCmd = &cobra.Command{ func init() { rootCmd.AddCommand(remoteCmd) - // remote status + // remoteCmd.AddCommand(remoteStatusCmd) // exposes `remote status ` remoteCmd.AddCommand(remoteStatusCmd) - // remote start + // remoteCmd.AddCommand(remoteStartCmd) // exposes `remote start --type xmrig --profile default` remoteCmd.AddCommand(remoteStartCmd) remoteStartCmd.Flags().StringP("profile", "p", "", "Profile ID to use for starting the miner") remoteStartCmd.Flags().StringP("type", "t", "", "Miner type (e.g., xmrig, tt-miner)") - // remote stop + // remoteCmd.AddCommand(remoteStopCmd) // exposes `remote stop --miner xmrig-1` remoteCmd.AddCommand(remoteStopCmd) remoteStopCmd.Flags().StringP("miner", "m", "", "Miner name to stop") - // remote logs + // remoteCmd.AddCommand(remoteLogsCmd) // exposes `remote logs ` remoteCmd.AddCommand(remoteLogsCmd) remoteLogsCmd.Flags().IntP("lines", "n", 100, "Number of log lines to retrieve") - // remote connect + // remoteCmd.AddCommand(remoteConnectCmd) // exposes `remote connect ` remoteCmd.AddCommand(remoteConnectCmd) - // remote disconnect + // remoteCmd.AddCommand(remoteDisconnectCmd) // exposes `remote disconnect ` remoteCmd.AddCommand(remoteDisconnectCmd) - // remote ping + // remoteCmd.AddCommand(remotePingCmd) // exposes `remote ping ` remoteCmd.AddCommand(remotePingCmd) remotePingCmd.Flags().IntP("count", "c", 4, "Number of pings to send") } @@ -326,46 +326,46 @@ func init() { // getController returns or creates the controller instance (thread-safe). func getController() (*node.Controller, error) { controllerOnce.Do(func() { - nm, err := getNodeManager() + nodeManager, err := getNodeManager() if err != nil { controllerErr = fmt.Errorf("failed to get node manager: %w", err) return } - if !nm.HasIdentity() { + if !nodeManager.HasIdentity() { controllerErr = fmt.Errorf("no node identity found. Run 'node init' first") return } - pr, err := getPeerRegistry() + peerRegistry, err := getPeerRegistry() if err != nil { controllerErr = fmt.Errorf("failed to get peer registry: %w", err) return } - // Initialize transport + // node.DefaultTransportConfig() // provides the transport settings used by `remote` commands config := node.DefaultTransportConfig() - transport = node.NewTransport(nm, pr, config) - controller = node.NewController(nm, pr, transport) + transport = node.NewTransport(nodeManager, peerRegistry, config) + controller = node.NewController(nodeManager, peerRegistry, transport) }) return controller, controllerErr } -// findPeerByPartialID finds a peer by full or partial ID. +// findPeerByPartialID("a1b2c3") // returns the matching peer by full or partial ID. func findPeerByPartialID(partialID string) *node.Peer { - pr, err := getPeerRegistry() + peerRegistry, err := getPeerRegistry() if err != nil { return nil } - // Try exact match first - peer := pr.GetPeer(partialID) + // peerRegistry.GetPeer(partialID) // exact match first + peer := peerRegistry.GetPeer(partialID) if peer != nil { return peer } - // Try partial match - for _, p := range pr.ListPeers() { + // peerRegistry.ListPeers() // then fall back to partial ID or exact name matches + for _, p := range peerRegistry.ListPeers() { if strings.HasPrefix(p.ID, partialID) { return p } @@ -378,7 +378,7 @@ func findPeerByPartialID(partialID string) *node.Peer { return nil } -// printPeerStats prints formatted stats for a peer. +// printPeerStats(peer, stats) // formats the remote stats output for `remote status`. func printPeerStats(peer *node.Peer, stats *node.StatsPayload) { fmt.Printf("\n%s (%s)\n", peer.Name, peer.ID[:16]) fmt.Printf(" Address: %s\n", peer.Address) @@ -397,7 +397,7 @@ func printPeerStats(peer *node.Peer, stats *node.StatsPayload) { } } -// formatDuration formats a duration into a human-readable string. +// formatDuration(90*time.Minute) // returns "1h 30m" func formatDuration(d time.Duration) string { days := int(d.Hours() / 24) hours := int(d.Hours()) % 24 diff --git a/cmd/mining/cmd/root.go b/cmd/mining/cmd/root.go index 0bd22ce..ba5e247 100644 --- a/cmd/mining/cmd/root.go +++ b/cmd/mining/cmd/root.go @@ -11,7 +11,7 @@ var ( manager *mining.Manager ) -// rootCmd represents the base command when called without any subcommands +// rootCmd.Use == "mining" and rootCmd.Version prints pkg/mining.GetVersion(). var rootCmd = &cobra.Command{ Use: "mining", Short: "Mining CLI - Manage miners with RESTful control", @@ -20,8 +20,7 @@ It provides commands to start, stop, list, and manage miners with RESTful contro Version: mining.GetVersion(), } -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. +// Execute() // runs the root command from main.main(). func Execute() error { return rootCmd.Execute() } @@ -30,9 +29,8 @@ func init() { cobra.OnInitialize(initManager) } -// initManager initializes the miner manager +// initManager() // skips simulate so `mining simulate` can create its own manager. func initManager() { - // Skip for commands that create their own manager (like simulate) if len(os.Args) > 1 && os.Args[1] == "simulate" { return } @@ -41,7 +39,7 @@ func initManager() { } } -// getManager returns the singleton manager instance +// getManager() // returns the shared manager used by `mining start` and `mining stop`. func getManager() *mining.Manager { if manager == nil { manager = mining.NewManager() diff --git a/cmd/mining/cmd/serve.go b/cmd/mining/cmd/serve.go index 517b033..6441ced 100644 --- a/cmd/mining/cmd/serve.go +++ b/cmd/mining/cmd/serve.go @@ -22,7 +22,7 @@ var ( namespace string ) -// serveCmd represents the serve command +// serveCmd.Use == "serve" and RunE starts the HTTP API and shell. var serveCmd = &cobra.Command{ Use: "serve", Short: "Start the mining service and interactive shell", @@ -42,7 +42,7 @@ var serveCmd = &cobra.Command{ displayAddr := fmt.Sprintf("%s:%d", displayHost, port) listenAddr := fmt.Sprintf("%s:%d", host, port) - // Use the global manager instance initialized by initManager. + // getManager() // returns the shared manager used by the long-running service. manager := getManager() service, err := mining.NewService(manager, listenAddr, displayAddr, namespace) @@ -50,7 +50,7 @@ var serveCmd = &cobra.Command{ return fmt.Errorf("failed to create new service: %w", err) } - // Start the server in a goroutine + // service.ServiceStartup(ctx) // starts the HTTP server without blocking the shell loop. go func() { if err := service.ServiceStartup(ctx); err != nil { fmt.Fprintf(os.Stderr, "Failed to start service: %v\n", err) @@ -58,11 +58,11 @@ var serveCmd = &cobra.Command{ } }() - // Handle graceful shutdown on Ctrl+C - signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) + // signalChannel captures Ctrl+C so the service can shut down cleanly. + signalChannel := make(chan os.Signal, 1) + signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM) - // Start interactive shell in a goroutine + // Start the interactive shell in a goroutine. go func() { fmt.Printf("Mining service started on http://%s:%d\n", displayHost, port) fmt.Printf("Swagger documentation is available at http://%s:%d%s/index.html\n", displayHost, port, service.SwaggerUIPath) @@ -188,7 +188,7 @@ var serveCmd = &cobra.Command{ } default: fmt.Fprintf(os.Stderr, "Unknown command: %s. Only 'start', 'status', 'stop', 'list' are directly supported in this shell.\n", command) - fmt.Fprintf(os.Stderr, "For other commands, please run them directly from your terminal (e.g., 'miner-ctrl doctor').\n") + fmt.Fprintf(os.Stderr, "For other commands, please run them directly from your terminal (e.g., 'mining doctor').\n") } fmt.Print(">> ") } @@ -200,7 +200,7 @@ var serveCmd = &cobra.Command{ }() select { - case <-signalChan: + case <-signalChannel: fmt.Println("\nReceived shutdown signal, stopping service...") cancel() case <-ctx.Done(): diff --git a/cmd/mining/cmd/simulate.go b/cmd/mining/cmd/simulate.go index 555c99e..6b98d50 100644 --- a/cmd/mining/cmd/simulate.go +++ b/cmd/mining/cmd/simulate.go @@ -21,7 +21,7 @@ var ( simAlgorithm string ) -// simulateCmd represents the simulate command +// simulateCmd.Use == "simulate" and RunE starts the service with fake miners. var simulateCmd = &cobra.Command{ Use: "simulate", Short: "Start the service with simulated miners for UI testing", @@ -31,13 +31,13 @@ without requiring actual mining hardware. Examples: # Start with 3 medium-hashrate CPU miners - miner-ctrl simulate --count 3 --preset cpu-medium + mining simulate --count 3 --preset cpu-medium # Start with custom hashrate - miner-ctrl simulate --count 2 --hashrate 8000 --algorithm rx/0 + mining simulate --count 2 --hashrate 8000 --algorithm rx/0 # Start with a mix of presets - miner-ctrl simulate --count 1 --preset gpu-ethash + mining simulate --count 1 --preset gpu-ethash Available presets: cpu-low - Low-end CPU (500 H/s, rx/0) @@ -63,17 +63,17 @@ Available presets: // Create a dedicated simulation manager without autostarting real miners. manager := mining.NewManagerForSimulation() - // Create and start simulated miners + // getSimulatedConfig(i) // derives one miner profile per index, e.g. sim-cpu-medium-001 for i := 0; i < simCount; i++ { config := getSimulatedConfig(i) simMiner := mining.NewSimulatedMiner(config) - // Start the simulated miner + // simMiner.Start(&mining.Config{}) // uses the simulated miner lifecycle, not a real binary if err := simMiner.Start(&mining.Config{}); err != nil { return fmt.Errorf("failed to start simulated miner %d: %w", i, err) } - // Register with manager + // manager.RegisterMiner(simMiner) // exposes the simulated miner through the shared manager if err := manager.RegisterMiner(simMiner); err != nil { return fmt.Errorf("failed to register simulated miner %d: %w", i, err) } @@ -88,7 +88,7 @@ Available presets: return fmt.Errorf("failed to create new service: %w", err) } - // Start the server in a goroutine + // service.ServiceStartup(ctx) // starts the API server while the simulation loop keeps running. go func() { if err := service.ServiceStartup(ctx); err != nil { fmt.Fprintf(os.Stderr, "Failed to start service: %v\n", err) @@ -102,12 +102,12 @@ Available presets: fmt.Printf("\nSimulating %d miner(s). Press Ctrl+C to stop.\n", simCount) fmt.Printf("Note: All data is simulated - no actual mining is occurring.\n\n") - // Handle graceful shutdown on Ctrl+C - signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) + // signalChannel captures Ctrl+C so simulated miners can stop cleanly. + signalChannel := make(chan os.Signal, 1) + signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM) select { - case <-signalChan: + case <-signalChannel: fmt.Println("\nReceived shutdown signal, stopping simulation...") cancel() case <-ctx.Done(): @@ -156,7 +156,7 @@ func getSimulatedConfig(index int) mining.SimulatedMinerConfig { } func init() { - // Seed random for varied simulation + // rand.Seed(time.Now().UnixNano()) // varies simulated hash rates between runs rand.Seed(time.Now().UnixNano()) simulateCmd.Flags().IntVarP(&simCount, "count", "c", 1, "Number of simulated miners to create") @@ -164,7 +164,7 @@ func init() { simulateCmd.Flags().IntVar(&simHashrate, "hashrate", 0, "Custom base hashrate (overrides preset)") simulateCmd.Flags().StringVar(&simAlgorithm, "algorithm", "", "Custom algorithm (overrides preset)") - // Reuse serve command flags + // simulateCmd.Flags().StringVar(&host, "host", "127.0.0.1", "Host to listen on") // same listen address as `mining serve` simulateCmd.Flags().StringVar(&host, "host", "127.0.0.1", "Host to listen on") simulateCmd.Flags().IntVarP(&port, "port", "p", 9090, "Port to listen on") simulateCmd.Flags().StringVarP(&namespace, "namespace", "n", "/api/v1/mining", "API namespace") diff --git a/cmd/mining/cmd/start.go b/cmd/mining/cmd/start.go index 87fbce7..df00d90 100644 --- a/cmd/mining/cmd/start.go +++ b/cmd/mining/cmd/start.go @@ -13,7 +13,7 @@ var ( minerWallet string ) -// startCmd represents the start command +// startCmd.Use == "start [miner_name]" and RunE starts the requested miner. var startCmd = &cobra.Command{ Use: "start [miner_name]", Short: "Start a new miner", diff --git a/cmd/mining/cmd/status.go b/cmd/mining/cmd/status.go index 7be076a..5b445c5 100644 --- a/cmd/mining/cmd/status.go +++ b/cmd/mining/cmd/status.go @@ -9,7 +9,7 @@ import ( "golang.org/x/text/language" ) -// statusCmd represents the status command +// statusCmd.Use == "status [miner_name]" and RunE prints live miner stats. var statusCmd = &cobra.Command{ Use: "status [miner_name]", Short: "Get status of a running miner", diff --git a/cmd/mining/cmd/stop.go b/cmd/mining/cmd/stop.go index 7bffe4b..4dd34b0 100644 --- a/cmd/mining/cmd/stop.go +++ b/cmd/mining/cmd/stop.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" ) -// stopCmd represents the stop command +// stopCmd.Use == "stop [miner_name]" and RunE stops the named miner. var stopCmd = &cobra.Command{ Use: "stop [miner_name]", Short: "Stop a running miner", diff --git a/cmd/mining/cmd/uninstall.go b/cmd/mining/cmd/uninstall.go index cc4eaae..1748347 100644 --- a/cmd/mining/cmd/uninstall.go +++ b/cmd/mining/cmd/uninstall.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" ) -// uninstallCmd represents the uninstall command +// uninstallCmd.Use == "uninstall [miner_type]" and RunE removes the miner. var uninstallCmd = &cobra.Command{ Use: "uninstall [miner_type]", Short: "Uninstall a miner", @@ -15,7 +15,7 @@ var uninstallCmd = &cobra.Command{ Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { minerType := args[0] - manager := getManager() // Assuming getManager() provides the singleton manager instance + manager := getManager() // getManager() returns the shared manager used by `mining uninstall` fmt.Printf("Uninstalling %s...\n", minerType) if err := manager.UninstallMiner(context.Background(), minerType); err != nil { @@ -24,8 +24,7 @@ var uninstallCmd = &cobra.Command{ fmt.Printf("%s uninstalled successfully.\n", minerType) - // The doctor cache is implicitly updated by the manager's actions, - // but an explicit cache update can still be beneficial. + // updateDoctorCache() // refreshes the cached install status after uninstalling a miner fmt.Println("Updating installation cache...") if err := updateDoctorCache(); err != nil { fmt.Printf("Warning: failed to update doctor cache: %v\n", err) diff --git a/cmd/mining/cmd/update.go b/cmd/mining/cmd/update.go index cee4bb6..2d830ae 100644 --- a/cmd/mining/cmd/update.go +++ b/cmd/mining/cmd/update.go @@ -7,13 +7,13 @@ import ( "path/filepath" "strings" - "github.com/Masterminds/semver/v3" "forge.lthn.ai/Snider/Mining/pkg/mining" + "github.com/Masterminds/semver/v3" "github.com/adrg/xdg" "github.com/spf13/cobra" ) -// validateUpdateConfigPath validates that a config path is within the expected XDG config directory +// validateUpdateConfigPath("/home/alice/.config/lethean-desktop/miners/config.json") // nil func validateUpdateConfigPath(configPath string) error { expectedBase := filepath.Join(xdg.ConfigHome, "lethean-desktop") cleanPath := filepath.Clean(configPath) @@ -23,7 +23,7 @@ func validateUpdateConfigPath(configPath string) error { return nil } -// updateCmd represents the update command +// updateCmd.Use == "update" and RunE checks cached miner versions for upgrades. var updateCmd = &cobra.Command{ Use: "update", Short: "Check for updates to installed miners", @@ -48,7 +48,7 @@ var updateCmd = &cobra.Command{ } configPath := strings.TrimSpace(string(configPathBytes)) - // Security: Validate that the config path is within the expected directory + // validateUpdateConfigPath("/home/alice/.config/lethean-desktop/miners/config.json") // blocks path traversal if err := validateUpdateConfigPath(configPath); err != nil { return fmt.Errorf("security error: %w", err) } @@ -58,7 +58,7 @@ var updateCmd = &cobra.Command{ return fmt.Errorf("could not read cache file from %s: %w", configPath, err) } - // Fix: Use SystemInfo type (matches what doctor.go saves) + // mining.SystemInfo{} // matches what doctor.go writes to the cache file var systemInfo mining.SystemInfo if err := json.Unmarshal(cacheBytes, &systemInfo); err != nil { return fmt.Errorf("could not parse cache file: %w", err) @@ -76,7 +76,7 @@ var updateCmd = &cobra.Command{ minerName = "xmrig" miner = mining.NewXMRigMiner() } else { - continue // Skip unknown miners + continue // skip miners that do not have an updater yet } fmt.Printf("Checking %s... ", minerName) diff --git a/pkg/mining/manager.go b/pkg/mining/manager.go index c66ba51..b84b8fc 100644 --- a/pkg/mining/manager.go +++ b/pkg/mining/manager.go @@ -74,22 +74,22 @@ type Manager struct { eventHubMutex sync.RWMutex // Separate mutex for eventHub to avoid deadlock with main mutex } -// m.SetEventHub(eventHub) -func (m *Manager) SetEventHub(hub *EventHub) { - m.eventHubMutex.Lock() - defer m.eventHubMutex.Unlock() - m.eventHub = hub +// manager.SetEventHub(eventHub) +func (manager *Manager) SetEventHub(eventHub *EventHub) { + manager.eventHubMutex.Lock() + defer manager.eventHubMutex.Unlock() + manager.eventHub = eventHub } -// m.emitEvent(EventMinerStarted, MinerEventData{Name: instanceName}) -// m.emitEvent(EventMinerError, MinerEventData{Name: instanceName, Error: err.Error()}) -func (m *Manager) emitEvent(eventType EventType, data interface{}) { - m.eventHubMutex.RLock() - hub := m.eventHub - m.eventHubMutex.RUnlock() +// manager.emitEvent(EventMinerStarted, MinerEventData{Name: instanceName}) +// manager.emitEvent(EventMinerError, MinerEventData{Name: instanceName, Error: err.Error()}) +func (manager *Manager) emitEvent(eventType EventType, data interface{}) { + manager.eventHubMutex.RLock() + eventHub := manager.eventHub + manager.eventHubMutex.RUnlock() - if hub != nil { - hub.Broadcast(NewEvent(eventType, data)) + if eventHub != nil { + eventHub.Broadcast(NewEvent(eventType, data)) } } @@ -122,47 +122,47 @@ func NewManagerForSimulation() *Manager { return manager } -// m.initDatabase() // NewManager() calls this after loading miners.json, for example with Database.Enabled = true -func (m *Manager) initDatabase() { +// manager.initDatabase() // NewManager() calls this after loading miners.json, for example with Database.Enabled = true +func (manager *Manager) initDatabase() { minersConfiguration, err := LoadMinersConfig() if err != nil { logging.Warn("could not load config for database init", logging.Fields{"error": err}) return } - m.databaseEnabled = minersConfiguration.Database.Enabled - m.databaseRetention = minersConfiguration.Database.RetentionDays - if m.databaseRetention == 0 { - m.databaseRetention = 30 + manager.databaseEnabled = minersConfiguration.Database.Enabled + manager.databaseRetention = minersConfiguration.Database.RetentionDays + if manager.databaseRetention == 0 { + manager.databaseRetention = 30 } - if !m.databaseEnabled { + if !manager.databaseEnabled { logging.Debug("database persistence is disabled") return } databaseConfiguration := database.Config{ Enabled: true, - RetentionDays: m.databaseRetention, + RetentionDays: manager.databaseRetention, } if err := database.Initialize(databaseConfiguration); err != nil { logging.Warn("failed to initialize database", logging.Fields{"error": err}) - m.databaseEnabled = false + manager.databaseEnabled = false return } - logging.Info("database persistence enabled", logging.Fields{"retention_days": m.databaseRetention}) + logging.Info("database persistence enabled", logging.Fields{"retention_days": manager.databaseRetention}) - // m.startDBCleanup() // keeps database.Cleanup(30) running after persistence is enabled - m.startDBCleanup() + // manager.startDBCleanup() // keeps database.Cleanup(30) running after persistence is enabled + manager.startDBCleanup() } -// m.startDBCleanup() // runs database.Cleanup(30) once an hour after persistence is enabled -func (m *Manager) startDBCleanup() { - m.waitGroup.Add(1) +// manager.startDBCleanup() // runs database.Cleanup(30) once an hour after persistence is enabled +func (manager *Manager) startDBCleanup() { + manager.waitGroup.Add(1) go func() { - defer m.waitGroup.Done() + defer manager.waitGroup.Done() defer func() { if r := recover(); r != nil { logging.Error("panic in database cleanup goroutine", logging.Fields{"panic": r}) @@ -173,32 +173,32 @@ func (m *Manager) startDBCleanup() { defer ticker.Stop() // database.Cleanup(30) // removes rows older than 30 days during startup - if err := database.Cleanup(m.databaseRetention); err != nil { + if err := database.Cleanup(manager.databaseRetention); err != nil { logging.Warn("database cleanup failed", logging.Fields{"error": err}) } for { select { case <-ticker.C: - if err := database.Cleanup(m.databaseRetention); err != nil { + if err := database.Cleanup(manager.databaseRetention); err != nil { logging.Warn("database cleanup failed", logging.Fields{"error": err}) } - case <-m.stopChan: + case <-manager.stopChan: return } } }() } -// m.syncMinersConfig() // when miners.json only contains tt-miner, this adds xmrig with Autostart=false -func (m *Manager) syncMinersConfig() { +// manager.syncMinersConfig() // when miners.json only contains tt-miner, this adds xmrig with Autostart=false +func (manager *Manager) syncMinersConfig() { minersConfiguration, err := LoadMinersConfig() if err != nil { logging.Warn("could not load miners config for sync", logging.Fields{"error": err}) return } - availableMiners := m.ListAvailableMiners() + availableMiners := manager.ListAvailableMiners() configUpdated := false for _, availableMiner := range availableMiners { @@ -227,8 +227,8 @@ func (m *Manager) syncMinersConfig() { } } -// m.autostartMiners() // NewManager() uses this to start xmrig when miners.json contains Autostart=true -func (m *Manager) autostartMiners() { +// manager.autostartMiners() // NewManager() uses this to start xmrig when miners.json contains Autostart=true +func (manager *Manager) autostartMiners() { minersConfiguration, err := LoadMinersConfig() if err != nil { logging.Warn("could not load miners config for autostart", logging.Fields{"error": err}) @@ -238,7 +238,7 @@ func (m *Manager) autostartMiners() { for _, autostartEntry := range minersConfiguration.Miners { if autostartEntry.Autostart && autostartEntry.Config != nil { logging.Info("autostarting miner", logging.Fields{"type": autostartEntry.MinerType}) - if _, err := m.StartMiner(context.Background(), autostartEntry.MinerType, autostartEntry.Config); err != nil { + 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}) } } @@ -262,7 +262,7 @@ func findAvailablePort() (int, error) { } // miner, err := manager.StartMiner(ctx, "xmrig", &Config{Algo: "rx/0"}) -func (m *Manager) StartMiner(ctx context.Context, minerType string, config *Config) (Miner, error) { +func (manager *Manager) StartMiner(ctx context.Context, minerType string, config *Config) (Miner, error) { // Check for cancellation before acquiring lock select { case <-ctx.Done(): @@ -270,8 +270,8 @@ func (m *Manager) StartMiner(ctx context.Context, minerType string, config *Conf default: } - m.mutex.Lock() - defer m.mutex.Unlock() + manager.mutex.Lock() + defer manager.mutex.Unlock() if config == nil { config = &Config{} @@ -291,7 +291,7 @@ func (m *Manager) StartMiner(ctx context.Context, minerType string, config *Conf instanceName = instanceName + "-" + strconv.FormatInt(time.Now().UnixNano()%1000, 10) } - if _, exists := m.miners[instanceName]; exists { + if _, exists := manager.miners[instanceName]; exists { return nil, ErrMinerExists(instanceName) } @@ -323,23 +323,23 @@ func (m *Manager) StartMiner(ctx context.Context, minerType string, config *Conf } } - // m.emitEvent(EventMinerStarting, MinerEventData{Name: "xmrig-rx_0"}) // fires before miner.Start(config) - m.emitEvent(EventMinerStarting, MinerEventData{ + // manager.emitEvent(EventMinerStarting, MinerEventData{Name: "xmrig-rx_0"}) // fires before miner.Start(config) + manager.emitEvent(EventMinerStarting, MinerEventData{ Name: instanceName, }) if err := miner.Start(config); err != nil { - // m.emitEvent(EventMinerError, MinerEventData{Name: "xmrig-rx_0", Error: err.Error()}) - m.emitEvent(EventMinerError, MinerEventData{ + // manager.emitEvent(EventMinerError, MinerEventData{Name: "xmrig-rx_0", Error: err.Error()}) + manager.emitEvent(EventMinerError, MinerEventData{ Name: instanceName, Error: err.Error(), }) return nil, err } - m.miners[instanceName] = miner + manager.miners[instanceName] = miner - if err := m.updateMinerConfig(minerType, true, config); err != nil { + if err := manager.updateMinerConfig(minerType, true, config); err != nil { logging.Warn("failed to save miner config for autostart", logging.Fields{"error": err}) } @@ -347,7 +347,7 @@ func (m *Manager) StartMiner(ctx context.Context, minerType string, config *Conf logToSyslog(logMessage) // Emit started event - m.emitEvent(EventMinerStarted, MinerEventData{ + manager.emitEvent(EventMinerStarted, MinerEventData{ Name: instanceName, }) @@ -357,7 +357,7 @@ func (m *Manager) StartMiner(ctx context.Context, minerType string, config *Conf // manager.UninstallMiner(ctx, "xmrig") // stops all xmrig instances and removes config // manager.UninstallMiner(ctx, "ttminer") // stops all ttminer instances and removes config -func (m *Manager) UninstallMiner(ctx context.Context, minerType string) error { +func (manager *Manager) UninstallMiner(ctx context.Context, minerType string) error { // Check for cancellation before acquiring lock select { case <-ctx.Done(): @@ -365,11 +365,11 @@ func (m *Manager) UninstallMiner(ctx context.Context, minerType string) error { default: } - m.mutex.Lock() + manager.mutex.Lock() // Collect miners to stop and delete (can't modify map during iteration) minersToDelete := make([]string, 0) minersToStop := make([]Miner, 0) - for name, runningMiner := range m.miners { + for name, runningMiner := range manager.miners { if xmrigInstance, ok := runningMiner.(*XMRigMiner); ok && equalFold(xmrigInstance.ExecutableName, minerType) { minersToStop = append(minersToStop, runningMiner) minersToDelete = append(minersToDelete, name) @@ -381,9 +381,9 @@ func (m *Manager) UninstallMiner(ctx context.Context, minerType string) error { } // Delete from map first, then release lock before stopping (Stop may block) for _, name := range minersToDelete { - delete(m.miners, name) + delete(manager.miners, name) } - m.mutex.Unlock() + manager.mutex.Unlock() // Stop miners outside the lock to avoid blocking for i, miner := range minersToStop { @@ -413,8 +413,8 @@ func (m *Manager) UninstallMiner(ctx context.Context, minerType string) error { }) } -// m.updateMinerConfig("xmrig", true, config) // saves Autostart=true and the last-used config back to miners.json -func (m *Manager) updateMinerConfig(minerType string, autostart bool, config *Config) error { +// 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 { found := false for i, autostartEntry := range configuration.Miners { @@ -439,7 +439,7 @@ func (m *Manager) updateMinerConfig(minerType string, autostart bool, config *Co // manager.StopMiner(ctx, "xmrig/monero") // manager.StopMiner(ctx, "ttminer/rtx4090") // still removes if already stopped -func (m *Manager) StopMiner(ctx context.Context, name string) error { +func (manager *Manager) StopMiner(ctx context.Context, name string) error { // Check for cancellation before acquiring lock select { case <-ctx.Done(): @@ -447,14 +447,14 @@ func (m *Manager) StopMiner(ctx context.Context, name string) error { default: } - m.mutex.Lock() - defer m.mutex.Unlock() + manager.mutex.Lock() + defer manager.mutex.Unlock() - miner, exists := m.miners[name] + miner, exists := manager.miners[name] if !exists { - for minerKey := range m.miners { + for minerKey := range manager.miners { if hasPrefix(minerKey, name) { - miner = m.miners[minerKey] + miner = manager.miners[minerKey] name = minerKey exists = true break @@ -467,7 +467,7 @@ func (m *Manager) StopMiner(ctx context.Context, name string) error { } // Emit stopping event - m.emitEvent(EventMinerStopping, MinerEventData{ + manager.emitEvent(EventMinerStopping, MinerEventData{ Name: name, }) @@ -476,14 +476,14 @@ func (m *Manager) StopMiner(ctx context.Context, name string) error { stopErr := miner.Stop() // Always remove from map - if it's not running, we still want to clean it up - delete(m.miners, name) + delete(manager.miners, name) // Emit stopped event reason := "stopped" if stopErr != nil && stopErr.Error() != "miner is not running" { reason = stopErr.Error() } - m.emitEvent(EventMinerStopped, MinerEventData{ + manager.emitEvent(EventMinerStopped, MinerEventData{ Name: name, Reason: reason, }) @@ -497,25 +497,25 @@ func (m *Manager) StopMiner(ctx context.Context, name string) error { return nil } -// miner, err := m.GetMiner("xmrig-randomx") // returns ErrMinerNotFound when the name is missing +// miner, err := manager.GetMiner("xmrig-randomx") // returns ErrMinerNotFound when the name is missing // if err != nil { /* miner not found */ } -func (m *Manager) GetMiner(name string) (Miner, error) { - m.mutex.RLock() - defer m.mutex.RUnlock() - miner, exists := m.miners[name] +func (manager *Manager) GetMiner(name string) (Miner, error) { + manager.mutex.RLock() + defer manager.mutex.RUnlock() + miner, exists := manager.miners[name] if !exists { return nil, ErrMinerNotFound(name) } return miner, nil } -// miners := m.ListMiners() +// miners := manager.ListMiners() // for _, miner := range miners { logging.Info(miner.GetName()) } -func (m *Manager) ListMiners() []Miner { - m.mutex.RLock() - defer m.mutex.RUnlock() - miners := make([]Miner, 0, len(m.miners)) - for _, miner := range m.miners { +func (manager *Manager) ListMiners() []Miner { + manager.mutex.RLock() + defer manager.mutex.RUnlock() + miners := make([]Miner, 0, len(manager.miners)) + for _, miner := range manager.miners { miners = append(miners, miner) } return miners @@ -523,21 +523,21 @@ func (m *Manager) ListMiners() []Miner { // simulatedMiner := NewSimulatedMiner(SimulatedMinerConfig{Name: "sim-rx0"}) // if err := manager.RegisterMiner(simulatedMiner); err != nil { return err } -func (m *Manager) RegisterMiner(miner Miner) error { +func (manager *Manager) RegisterMiner(miner Miner) error { name := miner.GetName() - m.mutex.Lock() - if _, exists := m.miners[name]; exists { - m.mutex.Unlock() + manager.mutex.Lock() + if _, exists := manager.miners[name]; exists { + manager.mutex.Unlock() return ErrMinerExists(name) } - m.miners[name] = miner - m.mutex.Unlock() + manager.miners[name] = miner + manager.mutex.Unlock() logging.Info("registered miner", logging.Fields{"name": name}) // Emit miner started event (outside lock) - m.emitEvent(EventMinerStarted, map[string]interface{}{ + manager.emitEvent(EventMinerStarted, map[string]interface{}{ "name": name, }) @@ -545,7 +545,7 @@ func (m *Manager) RegisterMiner(miner Miner) error { } // for _, availableMiner := range manager.ListAvailableMiners() { logging.Info(availableMiner.Name, nil) } -func (m *Manager) ListAvailableMiners() []AvailableMiner { +func (manager *Manager) ListAvailableMiners() []AvailableMiner { return []AvailableMiner{ { Name: "xmrig", @@ -558,11 +558,11 @@ func (m *Manager) ListAvailableMiners() []AvailableMiner { } } -// m.startStatsCollection() // NewManager() uses this to poll each running miner every HighResolutionInterval -func (m *Manager) startStatsCollection() { - m.waitGroup.Add(1) +// manager.startStatsCollection() // NewManager() uses this to poll each running miner every HighResolutionInterval +func (manager *Manager) startStatsCollection() { + manager.waitGroup.Add(1) go func() { - defer m.waitGroup.Done() + defer manager.waitGroup.Done() defer func() { if r := recover(); r != nil { logging.Error("panic in stats collection goroutine", logging.Fields{"panic": r}) @@ -574,8 +574,8 @@ func (m *Manager) startStatsCollection() { for { select { case <-ticker.C: - m.collectMinerStats() - case <-m.stopChan: + manager.collectMinerStats() + case <-manager.stopChan: return } } @@ -585,12 +585,12 @@ func (m *Manager) startStatsCollection() { // ctx, cancel := context.WithTimeout(ctx, statsCollectionTimeout) const statsCollectionTimeout = 5 * time.Second -// m.collectMinerStats() // the stats ticker calls this to poll all running miners in parallel -func (m *Manager) collectMinerStats() { +// manager.collectMinerStats() // the stats ticker calls this to poll all running miners in parallel +func (manager *Manager) collectMinerStats() { // Take a snapshot of miners under read lock - minimize lock duration - m.mutex.RLock() - if len(m.miners) == 0 { - m.mutex.RUnlock() + manager.mutex.RLock() + if len(manager.miners) == 0 { + manager.mutex.RUnlock() return } @@ -598,13 +598,13 @@ func (m *Manager) collectMinerStats() { miner Miner minerType string } - miners := make([]minerInfo, 0, len(m.miners)) - for _, miner := range m.miners { + miners := make([]minerInfo, 0, len(manager.miners)) + for _, miner := range manager.miners { // Use the miner's GetType() method for proper type identification miners = append(miners, minerInfo{miner: miner, minerType: miner.GetType()}) } - databaseEnabled := m.databaseEnabled // Copy to avoid holding lock - m.mutex.RUnlock() + databaseEnabled := manager.databaseEnabled // Copy to avoid holding lock + manager.mutex.RUnlock() now := time.Now() @@ -622,7 +622,7 @@ func (m *Manager) collectMinerStats() { }) } }() - m.collectSingleMinerStats(miner, minerType, now, databaseEnabled) + manager.collectSingleMinerStats(miner, minerType, now, databaseEnabled) }(entry.miner, entry.minerType) } collectionWaitGroup.Wait() @@ -634,8 +634,8 @@ const statsRetryCount = 2 // time.Sleep(statsRetryDelay) // between retry attempts const statsRetryDelay = 500 * time.Millisecond -// m.collectSingleMinerStats(miner, "xmrig", time.Now(), true) // retries up to statsRetryCount times; persists to DB if databaseEnabled -func (m *Manager) collectSingleMinerStats(miner Miner, minerType string, now time.Time, databaseEnabled bool) { +// manager.collectSingleMinerStats(miner, "xmrig", time.Now(), true) // retries up to statsRetryCount times; persists to DB if databaseEnabled +func (manager *Manager) collectSingleMinerStats(miner Miner, minerType string, now time.Time, databaseEnabled bool) { minerName := miner.GetName() var stats *PerformanceMetrics @@ -701,7 +701,7 @@ func (m *Manager) collectSingleMinerStats(miner Miner, minerType string, now tim } // Emit stats event for real-time WebSocket updates - m.emitEvent(EventMinerStats, MinerStatsData{ + manager.emitEvent(EventMinerStats, MinerStatsData{ Name: minerName, Hashrate: stats.Hashrate, Shares: stats.Shares, @@ -714,10 +714,10 @@ func (m *Manager) collectSingleMinerStats(miner Miner, minerType string, now tim // points, err := manager.GetMinerHashrateHistory("xmrig") // for _, point := range points { logging.Info("hashrate", logging.Fields{"time": point.Timestamp, "rate": point.Hashrate}) } -func (m *Manager) GetMinerHashrateHistory(name string) ([]HashratePoint, error) { - m.mutex.RLock() - defer m.mutex.RUnlock() - miner, exists := m.miners[name] +func (manager *Manager) GetMinerHashrateHistory(name string) ([]HashratePoint, error) { + manager.mutex.RLock() + defer manager.mutex.RUnlock() + miner, exists := manager.miners[name] if !exists { return nil, ErrMinerNotFound(name) } @@ -728,23 +728,23 @@ func (m *Manager) GetMinerHashrateHistory(name string) ([]HashratePoint, error) const ShutdownTimeout = 10 * time.Second // defer manager.Stop() // stops miners, waits for goroutines, and closes the database during shutdown -func (m *Manager) Stop() { - m.stopOnce.Do(func() { +func (manager *Manager) Stop() { + manager.stopOnce.Do(func() { // Stop all running miners first - m.mutex.Lock() - for name, miner := range m.miners { + manager.mutex.Lock() + for name, miner := range manager.miners { if err := miner.Stop(); err != nil { logging.Warn("failed to stop miner", logging.Fields{"miner": name, "error": err}) } } - m.mutex.Unlock() + manager.mutex.Unlock() - close(m.stopChan) + close(manager.stopChan) // Wait for goroutines with timeout done := make(chan struct{}) go func() { - m.waitGroup.Wait() + manager.waitGroup.Wait() close(done) }() @@ -756,7 +756,7 @@ func (m *Manager) Stop() { } // Close the database - if m.databaseEnabled { + if manager.databaseEnabled { if err := database.Close(); err != nil { logging.Warn("failed to close database", logging.Fields{"error": err}) } @@ -766,16 +766,16 @@ func (m *Manager) Stop() { // stats, err := manager.GetMinerHistoricalStats("xmrig") // if err == nil { logging.Info("stats", logging.Fields{"average": stats.AverageRate}) } -func (m *Manager) GetMinerHistoricalStats(minerName string) (*database.HashrateStats, error) { - if !m.databaseEnabled { +func (manager *Manager) GetMinerHistoricalStats(minerName string) (*database.HashrateStats, error) { + if !manager.databaseEnabled { return nil, ErrDatabaseError("database persistence is disabled") } return database.GetHashrateStats(minerName) } // points, err := manager.GetMinerHistoricalHashrate("xmrig", time.Now().Add(-1*time.Hour), time.Now()) -func (m *Manager) GetMinerHistoricalHashrate(minerName string, since, until time.Time) ([]HashratePoint, error) { - if !m.databaseEnabled { +func (manager *Manager) GetMinerHistoricalHashrate(minerName string, since, until time.Time) ([]HashratePoint, error) { + if !manager.databaseEnabled { return nil, ErrDatabaseError("database persistence is disabled") } @@ -797,14 +797,14 @@ func (m *Manager) GetMinerHistoricalHashrate(minerName string, since, until time // allStats, err := manager.GetAllMinerHistoricalStats() // for _, stats := range allStats { logging.Info("stats", logging.Fields{"miner": stats.MinerName, "average": stats.AverageRate}) } -func (m *Manager) GetAllMinerHistoricalStats() ([]database.HashrateStats, error) { - if !m.databaseEnabled { +func (manager *Manager) GetAllMinerHistoricalStats() ([]database.HashrateStats, error) { + if !manager.databaseEnabled { return nil, ErrDatabaseError("database persistence is disabled") } return database.GetAllMinerStats() } // if manager.IsDatabaseEnabled() { /* persist stats */ } -func (m *Manager) IsDatabaseEnabled() bool { - return m.databaseEnabled +func (manager *Manager) IsDatabaseEnabled() bool { + return manager.databaseEnabled } diff --git a/pkg/mining/service.go b/pkg/mining/service.go index a1aa007..719b859 100644 --- a/pkg/mining/service.go +++ b/pkg/mining/service.go @@ -480,13 +480,13 @@ func NewService(manager ManagerInterface, listenAddr string, displayAddr string, // service.InitRouter() // http.Handle("/", service.Router) // embeds the API under a parent HTTP server in Wails -func (s *Service) InitRouter() { - s.Router = gin.Default() +func (service *Service) InitRouter() { + service.Router = gin.Default() - // s.Server.Addr = ":9090" -> serverPort = "9090" for local CORS origins + // service.Server.Addr = ":9090" -> serverPort = "9090" for local CORS origins serverPort := "9090" // default fallback - if s.Server.Addr != "" { - if _, port, err := net.SplitHostPort(s.Server.Addr); err == nil && port != "" { + if service.Server.Addr != "" { + if _, port, err := net.SplitHostPort(service.Server.Addr); err == nil && port != "" { serverPort = port } } @@ -508,68 +508,68 @@ func (s *Service) InitRouter() { AllowCredentials: true, MaxAge: 12 * time.Hour, } - s.Router.Use(cors.New(corsConfig)) + service.Router.Use(cors.New(corsConfig)) - // s.Router.Use(securityHeadersMiddleware()) // sets security headers on every API response - s.Router.Use(securityHeadersMiddleware()) + // service.Router.Use(securityHeadersMiddleware()) // sets security headers on every API response + service.Router.Use(securityHeadersMiddleware()) - // s.Router.Use(contentTypeValidationMiddleware()) // rejects POST /miners/xmrig/install without application/json - s.Router.Use(contentTypeValidationMiddleware()) + // service.Router.Use(contentTypeValidationMiddleware()) // rejects POST /miners/xmrig/install without application/json + service.Router.Use(contentTypeValidationMiddleware()) // c.Request.Body = http.MaxBytesReader(..., 1<<20) // caps request bodies at 1 MiB - s.Router.Use(func(c *gin.Context) { + service.Router.Use(func(c *gin.Context) { c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 1<<20) // 1MB c.Next() }) - // s.Router.Use(csrfMiddleware()) // allows API clients with Authorization or X-Requested-With - s.Router.Use(csrfMiddleware()) + // service.Router.Use(csrfMiddleware()) // allows API clients with Authorization or X-Requested-With + service.Router.Use(csrfMiddleware()) - // s.Router.Use(requestTimeoutMiddleware(DefaultRequestTimeout)) // aborts stalled requests after 30 seconds - s.Router.Use(requestTimeoutMiddleware(DefaultRequestTimeout)) + // service.Router.Use(requestTimeoutMiddleware(DefaultRequestTimeout)) // aborts stalled requests after 30 seconds + service.Router.Use(requestTimeoutMiddleware(DefaultRequestTimeout)) - // s.Router.Use(cacheMiddleware()) // returns Cache-Control: public, max-age=300 for /miners/available - s.Router.Use(cacheMiddleware()) + // service.Router.Use(cacheMiddleware()) // returns Cache-Control: public, max-age=300 for /miners/available + service.Router.Use(cacheMiddleware()) - // s.Router.Use(requestIDMiddleware()) // preserves the incoming X-Request-ID or creates a new one - s.Router.Use(requestIDMiddleware()) + // service.Router.Use(requestIDMiddleware()) // preserves the incoming X-Request-ID or creates a new one + service.Router.Use(requestIDMiddleware()) // NewRateLimiter(10, 20) // allows bursts of 20 with a 10 requests/second refill rate - s.rateLimiter = NewRateLimiter(10, 20) - s.Router.Use(s.rateLimiter.Middleware()) + service.rateLimiter = NewRateLimiter(10, 20) + service.Router.Use(service.rateLimiter.Middleware()) - s.SetupRoutes() + service.SetupRoutes() } // service.Stop() // stops rate limiting, auth, event hub, and node transport during shutdown -func (s *Service) Stop() { - if s.rateLimiter != nil { - s.rateLimiter.Stop() +func (service *Service) Stop() { + if service.rateLimiter != nil { + service.rateLimiter.Stop() } - if s.EventHub != nil { - s.EventHub.Stop() + if service.EventHub != nil { + service.EventHub.Stop() } - if s.auth != nil { - s.auth.Stop() + if service.auth != nil { + service.auth.Stop() } - if s.NodeService != nil { - if err := s.NodeService.StopTransport(); err != nil { + if service.NodeService != nil { + if err := service.NodeService.StopTransport(); err != nil { logging.Warn("failed to stop node service transport", logging.Fields{"error": err}) } } } // service.ServiceStartup(ctx) // starts the HTTP server and stops it when ctx.Done() fires -func (s *Service) ServiceStartup(ctx context.Context) error { - s.InitRouter() - s.Server.Handler = s.Router +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) go func() { - if err := s.Server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - logging.Error("server error", logging.Fields{"addr": s.Server.Addr, "error": err}) + if err := service.Server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + logging.Error("server error", logging.Fields{"addr": service.Server.Addr, "error": err}) serverErrorChannel <- err } close(serverErrorChannel) // prevent goroutine leak @@ -577,11 +577,11 @@ func (s *Service) ServiceStartup(ctx context.Context) error { go func() { <-ctx.Done() - s.Stop() // Clean up service resources (auth, event hub, node service) - s.Manager.Stop() + service.Stop() // Clean up service resources (auth, event hub, node service) + service.Manager.Stop() shutdownContext, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - if err := s.Server.Shutdown(shutdownContext); err != nil { + if err := service.Server.Shutdown(shutdownContext); err != nil { logging.Error("server shutdown error", logging.Fields{"error": err}) } }() @@ -597,7 +597,7 @@ func (s *Service) ServiceStartup(ctx context.Context) error { return nil // Channel closed without error means server shut down default: // Try to connect to verify server is listening - conn, err := net.DialTimeout("tcp", s.Server.Addr, 50*time.Millisecond) + conn, err := net.DialTimeout("tcp", service.Server.Addr, 50*time.Millisecond) if err == nil { conn.Close() return nil // Server is ready @@ -606,90 +606,90 @@ func (s *Service) ServiceStartup(ctx context.Context) error { } } - return ErrInternal("server failed to start listening on " + s.Server.Addr + " within timeout") + return ErrInternal("server failed to start listening on " + service.Server.Addr + " within timeout") } // service.InitRouter() // service.SetupRoutes() // re-call after adding middleware manually -func (s *Service) SetupRoutes() { - apiRouterGroup := s.Router.Group(s.APIBasePath) +func (service *Service) SetupRoutes() { + apiRouterGroup := service.Router.Group(service.APIBasePath) // Health endpoints (no auth required for orchestration/monitoring) - apiRouterGroup.GET("/health", s.handleHealth) - apiRouterGroup.GET("/ready", s.handleReady) + apiRouterGroup.GET("/health", service.handleHealth) + apiRouterGroup.GET("/ready", service.handleReady) // Apply authentication middleware if enabled - if s.auth != nil { - apiRouterGroup.Use(s.auth.Middleware()) + if service.auth != nil { + apiRouterGroup.Use(service.auth.Middleware()) } { - apiRouterGroup.GET("/info", s.handleGetInfo) - apiRouterGroup.GET("/metrics", s.handleMetrics) - apiRouterGroup.POST("/doctor", s.handleDoctor) - apiRouterGroup.POST("/update", s.handleUpdateCheck) + apiRouterGroup.GET("/info", service.handleGetInfo) + apiRouterGroup.GET("/metrics", service.handleMetrics) + apiRouterGroup.POST("/doctor", service.handleDoctor) + apiRouterGroup.POST("/update", service.handleUpdateCheck) minersRouterGroup := apiRouterGroup.Group("/miners") { - minersRouterGroup.GET("", s.handleListMiners) - minersRouterGroup.GET("/available", s.handleListAvailableMiners) - minersRouterGroup.POST("/:miner_name/install", s.handleInstallMiner) - minersRouterGroup.DELETE("/:miner_name/uninstall", s.handleUninstallMiner) - minersRouterGroup.DELETE("/:miner_name", s.handleStopMiner) - minersRouterGroup.GET("/:miner_name/stats", s.handleGetMinerStats) - minersRouterGroup.GET("/:miner_name/hashrate-history", s.handleGetMinerHashrateHistory) - minersRouterGroup.GET("/:miner_name/logs", s.handleGetMinerLogs) - minersRouterGroup.POST("/:miner_name/stdin", s.handleMinerStdin) + 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) } // Historical data endpoints (database-backed) historyRouterGroup := apiRouterGroup.Group("/history") { - historyRouterGroup.GET("/status", s.handleHistoryStatus) - historyRouterGroup.GET("/miners", s.handleAllMinersHistoricalStats) - historyRouterGroup.GET("/miners/:miner_name", s.handleMinerHistoricalStats) - historyRouterGroup.GET("/miners/:miner_name/hashrate", s.handleMinerHistoricalHashrate) + 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) } profilesRouterGroup := apiRouterGroup.Group("/profiles") { - profilesRouterGroup.GET("", s.handleListProfiles) - profilesRouterGroup.POST("", s.handleCreateProfile) - profilesRouterGroup.GET("/:id", s.handleGetProfile) - profilesRouterGroup.PUT("/:id", s.handleUpdateProfile) - profilesRouterGroup.DELETE("/:id", s.handleDeleteProfile) - profilesRouterGroup.POST("/:id/start", s.handleStartMinerWithProfile) + 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) } // WebSocket endpoint for real-time events websocketRouterGroup := apiRouterGroup.Group("/ws") { - websocketRouterGroup.GET("/events", s.handleWebSocketEvents) + websocketRouterGroup.GET("/events", service.handleWebSocketEvents) } // Add P2P node endpoints if node service is available - if s.NodeService != nil { - s.NodeService.SetupRoutes(apiRouterGroup) + if service.NodeService != nil { + service.NodeService.SetupRoutes(apiRouterGroup) } } // Serve the embedded web component componentFS, err := GetComponentFS() if err == nil { - s.Router.StaticFS("/component", componentFS) + service.Router.StaticFS("/component", componentFS) } - swaggerURL := ginSwagger.URL("http://" + s.DisplayAddr + s.SwaggerUIPath + "/doc.json") - s.Router.GET(s.SwaggerUIPath+"/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, swaggerURL, ginSwagger.InstanceName(s.SwaggerInstanceName))) + swaggerURL := ginSwagger.URL("http://" + service.DisplayAddr + service.SwaggerUIPath + "/doc.json") + service.Router.GET(service.SwaggerUIPath+"/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, swaggerURL, ginSwagger.InstanceName(service.SwaggerInstanceName))) - // ginmcp.New(s.Router, ...) // exposes the API as MCP tools for Claude or Cursor - s.mcpServer = ginmcp.New(s.Router, &ginmcp.Config{ + // ginmcp.New(service.Router, ...) // exposes the API as MCP tools for Claude or Cursor + service.mcpServer = ginmcp.New(service.Router, &ginmcp.Config{ Name: "Mining API", Description: "Mining dashboard API exposed via Model Context Protocol (MCP)", - BaseURL: "http://" + s.DisplayAddr, + BaseURL: "http://" + service.DisplayAddr, }) - s.mcpServer.Mount(s.APIBasePath + "/mcp") - logging.Info("MCP server enabled", logging.Fields{"endpoint": s.APIBasePath + "/mcp"}) + service.mcpServer.Mount(service.APIBasePath + "/mcp") + logging.Info("MCP server enabled", logging.Fields{"endpoint": service.APIBasePath + "/mcp"}) } // c.JSON(http.StatusOK, HealthResponse{Status: "healthy", Components: map[string]string{"db": "ok"}}) @@ -705,7 +705,7 @@ type HealthResponse struct { // @Produce json // @Success 200 {object} HealthResponse // @Router /health [get] -func (s *Service) handleHealth(c *gin.Context) { +func (service *Service) handleHealth(c *gin.Context) { c.JSON(http.StatusOK, HealthResponse{ Status: "healthy", }) @@ -719,36 +719,36 @@ func (s *Service) handleHealth(c *gin.Context) { // @Success 200 {object} HealthResponse // @Success 503 {object} HealthResponse // @Router /ready [get] -func (s *Service) handleReady(c *gin.Context) { +func (service *Service) handleReady(c *gin.Context) { components := make(map[string]string) allReady := true - // s.Manager != nil -> "manager": "ready" - if s.Manager != nil { + // service.Manager != nil -> "manager": "ready" + if service.Manager != nil { components["manager"] = "ready" } else { components["manager"] = "not initialized" allReady = false } - // s.ProfileManager != nil -> "profiles": "ready" - if s.ProfileManager != nil { + // service.ProfileManager != nil -> "profiles": "ready" + if service.ProfileManager != nil { components["profiles"] = "ready" } else { components["profiles"] = "degraded" // keep readiness green when only profile loading is degraded } - // s.EventHub != nil -> "events": "ready" - if s.EventHub != nil { + // service.EventHub != nil -> "events": "ready" + if service.EventHub != nil { components["events"] = "ready" } else { components["events"] = "not initialized" allReady = false } - // s.NodeService != nil -> "p2p": "ready" - if s.NodeService != nil { + // service.NodeService != nil -> "p2p": "ready" + if service.NodeService != nil { components["p2p"] = "ready" } else { components["p2p"] = "disabled" @@ -775,8 +775,8 @@ func (s *Service) handleReady(c *gin.Context) { // @Success 200 {object} SystemInfo // @Failure 500 {object} map[string]string "Internal server error" // @Router /info [get] -func (s *Service) handleGetInfo(c *gin.Context) { - systemInfo, err := s.updateInstallationCache() +func (service *Service) handleGetInfo(c *gin.Context) { + systemInfo, err := service.updateInstallationCache() if err != nil { respondWithMiningError(c, ErrInternal("failed to get system info").WithCause(err)) return @@ -784,9 +784,9 @@ func (s *Service) handleGetInfo(c *gin.Context) { c.JSON(http.StatusOK, systemInfo) } -// systemInfo, err := s.updateInstallationCache() +// systemInfo, err := service.updateInstallationCache() // if err != nil { return ErrInternal("cache update failed").WithCause(err) } -func (s *Service) updateInstallationCache() (*SystemInfo, error) { +func (service *Service) updateInstallationCache() (*SystemInfo, error) { // Always create a complete SystemInfo object systemInfo := &SystemInfo{ Timestamp: time.Now(), @@ -802,7 +802,7 @@ func (s *Service) updateInstallationCache() (*SystemInfo, error) { systemInfo.TotalSystemRAMGB = float64(vMem.Total) / (1024 * 1024 * 1024) } - for _, availableMiner := range s.Manager.ListAvailableMiners() { + for _, availableMiner := range service.Manager.ListAvailableMiners() { miner, err := CreateMiner(availableMiner.Name) if err != nil { continue // Skip unsupported miner types @@ -842,8 +842,8 @@ func (s *Service) updateInstallationCache() (*SystemInfo, error) { // @Produce json // @Success 200 {object} SystemInfo // @Router /doctor [post] -func (s *Service) handleDoctor(c *gin.Context) { - systemInfo, err := s.updateInstallationCache() +func (service *Service) handleDoctor(c *gin.Context) { + systemInfo, err := service.updateInstallationCache() if err != nil { respondWithMiningError(c, ErrInternal("failed to update cache").WithCause(err)) return @@ -858,9 +858,9 @@ func (s *Service) handleDoctor(c *gin.Context) { // @Produce json // @Success 200 {object} map[string]string // @Router /update [post] -func (s *Service) handleUpdateCheck(c *gin.Context) { +func (service *Service) handleUpdateCheck(c *gin.Context) { updates := make(map[string]string) - for _, availableMiner := range s.Manager.ListAvailableMiners() { + for _, availableMiner := range service.Manager.ListAvailableMiners() { miner, err := CreateMiner(availableMiner.Name) if err != nil { continue // Skip unsupported miner types @@ -907,13 +907,13 @@ func (s *Service) handleUpdateCheck(c *gin.Context) { // @Param miner_type path string true "Miner Type to uninstall" // @Success 200 {object} map[string]string // @Router /miners/{miner_type}/uninstall [delete] -func (s *Service) handleUninstallMiner(c *gin.Context) { +func (service *Service) handleUninstallMiner(c *gin.Context) { minerType := c.Param("miner_name") - if err := s.Manager.UninstallMiner(c.Request.Context(), minerType); err != nil { + if err := service.Manager.UninstallMiner(c.Request.Context(), minerType); err != nil { respondWithMiningError(c, ErrInternal("failed to uninstall miner").WithCause(err)) return } - if _, err := s.updateInstallationCache(); err != nil { + if _, err := service.updateInstallationCache(); err != nil { logging.Warn("failed to update cache after uninstall", logging.Fields{"error": err}) } c.JSON(http.StatusOK, gin.H{"status": minerType + " uninstalled successfully."}) @@ -926,8 +926,8 @@ func (s *Service) handleUninstallMiner(c *gin.Context) { // @Produce json // @Success 200 {array} XMRigMiner // @Router /miners [get] -func (s *Service) handleListMiners(c *gin.Context) { - miners := s.Manager.ListMiners() +func (service *Service) handleListMiners(c *gin.Context) { + miners := service.Manager.ListMiners() c.JSON(http.StatusOK, miners) } @@ -938,8 +938,8 @@ func (s *Service) handleListMiners(c *gin.Context) { // @Produce json // @Success 200 {array} AvailableMiner // @Router /miners/available [get] -func (s *Service) handleListAvailableMiners(c *gin.Context) { - miners := s.Manager.ListAvailableMiners() +func (service *Service) handleListAvailableMiners(c *gin.Context) { + miners := service.Manager.ListAvailableMiners() c.JSON(http.StatusOK, miners) } @@ -951,7 +951,7 @@ func (s *Service) handleListAvailableMiners(c *gin.Context) { // @Param miner_type path string true "Miner Type to install/update" // @Success 200 {object} map[string]string // @Router /miners/{miner_type}/install [post] -func (s *Service) handleInstallMiner(c *gin.Context) { +func (service *Service) handleInstallMiner(c *gin.Context) { minerType := c.Param("miner_name") miner, err := CreateMiner(minerType) if err != nil { @@ -964,7 +964,7 @@ func (s *Service) handleInstallMiner(c *gin.Context) { return } - if _, err := s.updateInstallationCache(); err != nil { + if _, err := service.updateInstallationCache(); err != nil { logging.Warn("failed to update cache after install", logging.Fields{"error": err}) } @@ -985,9 +985,9 @@ func (s *Service) handleInstallMiner(c *gin.Context) { // @Param id path string true "Profile ID" // @Success 200 {object} XMRigMiner // @Router /profiles/{id}/start [post] -func (s *Service) handleStartMinerWithProfile(c *gin.Context) { +func (service *Service) handleStartMinerWithProfile(c *gin.Context) { profileID := c.Param("id") - profile, exists := s.ProfileManager.GetProfile(profileID) + profile, exists := service.ProfileManager.GetProfile(profileID) if !exists { respondWithMiningError(c, ErrProfileNotFound(profileID)) return @@ -1005,7 +1005,7 @@ func (s *Service) handleStartMinerWithProfile(c *gin.Context) { return } - miner, err := s.Manager.StartMiner(c.Request.Context(), profile.MinerType, &config) + miner, err := service.Manager.StartMiner(c.Request.Context(), profile.MinerType, &config) if err != nil { respondWithMiningError(c, ErrStartFailed(profile.Name).WithCause(err)) return @@ -1021,9 +1021,9 @@ func (s *Service) handleStartMinerWithProfile(c *gin.Context) { // @Param miner_name path string true "Miner Name" // @Success 200 {object} map[string]string // @Router /miners/{miner_name} [delete] -func (s *Service) handleStopMiner(c *gin.Context) { +func (service *Service) handleStopMiner(c *gin.Context) { minerName := c.Param("miner_name") - if err := s.Manager.StopMiner(c.Request.Context(), minerName); err != nil { + if err := service.Manager.StopMiner(c.Request.Context(), minerName); err != nil { respondWithMiningError(c, ErrStopFailed(minerName).WithCause(err)) return } @@ -1038,9 +1038,9 @@ func (s *Service) handleStopMiner(c *gin.Context) { // @Param miner_name path string true "Miner Name" // @Success 200 {object} PerformanceMetrics // @Router /miners/{miner_name}/stats [get] -func (s *Service) handleGetMinerStats(c *gin.Context) { +func (service *Service) handleGetMinerStats(c *gin.Context) { minerName := c.Param("miner_name") - miner, err := s.Manager.GetMiner(minerName) + miner, err := service.Manager.GetMiner(minerName) if err != nil { respondWithMiningError(c, ErrMinerNotFound(minerName).WithCause(err)) return @@ -1061,9 +1061,9 @@ func (s *Service) handleGetMinerStats(c *gin.Context) { // @Param miner_name path string true "Miner Name" // @Success 200 {array} HashratePoint // @Router /miners/{miner_name}/hashrate-history [get] -func (s *Service) handleGetMinerHashrateHistory(c *gin.Context) { +func (service *Service) handleGetMinerHashrateHistory(c *gin.Context) { minerName := c.Param("miner_name") - history, err := s.Manager.GetMinerHashrateHistory(minerName) + history, err := service.Manager.GetMinerHashrateHistory(minerName) if err != nil { respondWithMiningError(c, ErrMinerNotFound(minerName).WithCause(err)) return @@ -1079,9 +1079,9 @@ func (s *Service) handleGetMinerHashrateHistory(c *gin.Context) { // @Param miner_name path string true "Miner Name" // @Success 200 {array} string "Base64 encoded log lines" // @Router /miners/{miner_name}/logs [get] -func (s *Service) handleGetMinerLogs(c *gin.Context) { +func (service *Service) handleGetMinerLogs(c *gin.Context) { minerName := c.Param("miner_name") - miner, err := s.Manager.GetMiner(minerName) + miner, err := service.Manager.GetMiner(minerName) if err != nil { respondWithMiningError(c, ErrMinerNotFound(minerName).WithCause(err)) return @@ -1112,9 +1112,9 @@ type StdinInput struct { // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Router /miners/{miner_name}/stdin [post] -func (s *Service) handleMinerStdin(c *gin.Context) { +func (service *Service) handleMinerStdin(c *gin.Context) { minerName := c.Param("miner_name") - miner, err := s.Manager.GetMiner(minerName) + miner, err := service.Manager.GetMiner(minerName) if err != nil { respondWithError(c, http.StatusNotFound, ErrCodeMinerNotFound, "miner not found", err.Error()) return @@ -1141,8 +1141,8 @@ func (s *Service) handleMinerStdin(c *gin.Context) { // @Produce json // @Success 200 {array} MiningProfile // @Router /profiles [get] -func (s *Service) handleListProfiles(c *gin.Context) { - profiles := s.ProfileManager.GetAllProfiles() +func (service *Service) handleListProfiles(c *gin.Context) { + profiles := service.ProfileManager.GetAllProfiles() c.JSON(http.StatusOK, profiles) } @@ -1156,7 +1156,7 @@ func (s *Service) handleListProfiles(c *gin.Context) { // @Success 201 {object} MiningProfile // @Failure 400 {object} APIError "Invalid profile data" // @Router /profiles [post] -func (s *Service) handleCreateProfile(c *gin.Context) { +func (service *Service) handleCreateProfile(c *gin.Context) { var profile MiningProfile if err := c.ShouldBindJSON(&profile); err != nil { respondWithError(c, http.StatusBadRequest, ErrCodeInvalidInput, "invalid profile data", err.Error()) @@ -1173,7 +1173,7 @@ func (s *Service) handleCreateProfile(c *gin.Context) { return } - createdProfile, err := s.ProfileManager.CreateProfile(&profile) + createdProfile, err := service.ProfileManager.CreateProfile(&profile) if err != nil { respondWithError(c, http.StatusInternalServerError, ErrCodeInternalError, "failed to create profile", err.Error()) return @@ -1190,9 +1190,9 @@ func (s *Service) handleCreateProfile(c *gin.Context) { // @Param id path string true "Profile ID" // @Success 200 {object} MiningProfile // @Router /profiles/{id} [get] -func (s *Service) handleGetProfile(c *gin.Context) { +func (service *Service) handleGetProfile(c *gin.Context) { profileID := c.Param("id") - profile, exists := s.ProfileManager.GetProfile(profileID) + profile, exists := service.ProfileManager.GetProfile(profileID) if !exists { respondWithError(c, http.StatusNotFound, ErrCodeProfileNotFound, "profile not found", "") return @@ -1211,7 +1211,7 @@ func (s *Service) handleGetProfile(c *gin.Context) { // @Success 200 {object} MiningProfile // @Failure 404 {object} APIError "Profile not found" // @Router /profiles/{id} [put] -func (s *Service) handleUpdateProfile(c *gin.Context) { +func (service *Service) handleUpdateProfile(c *gin.Context) { profileID := c.Param("id") var profile MiningProfile if err := c.ShouldBindJSON(&profile); err != nil { @@ -1220,7 +1220,7 @@ func (s *Service) handleUpdateProfile(c *gin.Context) { } profile.ID = profileID - if err := s.ProfileManager.UpdateProfile(&profile); err != nil { + if err := service.ProfileManager.UpdateProfile(&profile); err != nil { // Check if error is "not found" if strings.Contains(err.Error(), "not found") { respondWithError(c, http.StatusNotFound, ErrCodeProfileNotFound, "profile not found", err.Error()) @@ -1240,9 +1240,9 @@ func (s *Service) handleUpdateProfile(c *gin.Context) { // @Param id path string true "Profile ID" // @Success 200 {object} map[string]string // @Router /profiles/{id} [delete] -func (s *Service) handleDeleteProfile(c *gin.Context) { +func (service *Service) handleDeleteProfile(c *gin.Context) { profileID := c.Param("id") - if err := s.ProfileManager.DeleteProfile(profileID); err != nil { + if err := service.ProfileManager.DeleteProfile(profileID); err != nil { // Make DELETE idempotent - if profile doesn't exist, still return success if strings.Contains(err.Error(), "not found") { c.JSON(http.StatusOK, gin.H{"status": "profile deleted"}) @@ -1261,8 +1261,8 @@ func (s *Service) handleDeleteProfile(c *gin.Context) { // @Produce json // @Success 200 {object} map[string]interface{} // @Router /history/status [get] -func (s *Service) handleHistoryStatus(c *gin.Context) { - if manager, ok := s.Manager.(*Manager); ok { +func (service *Service) handleHistoryStatus(c *gin.Context) { + if manager, ok := service.Manager.(*Manager); ok { c.JSON(http.StatusOK, gin.H{ "enabled": manager.IsDatabaseEnabled(), "retentionDays": manager.databaseRetention, @@ -1279,8 +1279,8 @@ func (s *Service) handleHistoryStatus(c *gin.Context) { // @Produce json // @Success 200 {array} database.HashrateStats // @Router /history/miners [get] -func (s *Service) handleAllMinersHistoricalStats(c *gin.Context) { - manager, ok := s.Manager.(*Manager) +func (service *Service) handleAllMinersHistoricalStats(c *gin.Context) { + manager, ok := service.Manager.(*Manager) if !ok { respondWithMiningError(c, ErrInternal("manager type not supported")) return @@ -1303,9 +1303,9 @@ func (s *Service) handleAllMinersHistoricalStats(c *gin.Context) { // @Param miner_name path string true "Miner Name" // @Success 200 {object} database.HashrateStats // @Router /history/miners/{miner_name} [get] -func (s *Service) handleMinerHistoricalStats(c *gin.Context) { +func (service *Service) handleMinerHistoricalStats(c *gin.Context) { minerName := c.Param("miner_name") - manager, ok := s.Manager.(*Manager) + manager, ok := service.Manager.(*Manager) if !ok { respondWithMiningError(c, ErrInternal("manager type not supported")) return @@ -1335,9 +1335,9 @@ func (s *Service) handleMinerHistoricalStats(c *gin.Context) { // @Param until query string false "End time (RFC3339 format)" // @Success 200 {array} HashratePoint // @Router /history/miners/{miner_name}/hashrate [get] -func (s *Service) handleMinerHistoricalHashrate(c *gin.Context) { +func (service *Service) handleMinerHistoricalHashrate(c *gin.Context) { minerName := c.Param("miner_name") - manager, ok := s.Manager.(*Manager) + manager, ok := service.Manager.(*Manager) if !ok { respondWithMiningError(c, ErrInternal("manager type not supported")) return @@ -1374,7 +1374,7 @@ func (s *Service) handleMinerHistoricalHashrate(c *gin.Context) { // @Tags websocket // @Success 101 {string} string "Switching Protocols" // @Router /ws/events [get] -func (s *Service) handleWebSocketEvents(c *gin.Context) { +func (service *Service) handleWebSocketEvents(c *gin.Context) { conn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { logging.Error("failed to upgrade WebSocket connection", logging.Fields{"error": err}) @@ -1383,7 +1383,7 @@ func (s *Service) handleWebSocketEvents(c *gin.Context) { logging.Info("new WebSocket connection", logging.Fields{"remote": c.Request.RemoteAddr}) // Only record connection after successful registration to avoid metrics race - if s.EventHub.ServeWs(conn) { + if service.EventHub.ServeWs(conn) { RecordWSConnection(true) } else { logging.Warn("WebSocket connection rejected", logging.Fields{"remote": c.Request.RemoteAddr, "reason": "limit reached"}) @@ -1397,6 +1397,6 @@ func (s *Service) handleWebSocketEvents(c *gin.Context) { // @Produce json // @Success 200 {object} map[string]interface{} // @Router /metrics [get] -func (s *Service) handleMetrics(c *gin.Context) { +func (service *Service) handleMetrics(c *gin.Context) { c.JSON(http.StatusOK, GetMetricsSnapshot()) }