Align mining AX naming and comments
This commit is contained in:
parent
c7f86cf5b9
commit
68c826a3d8
16 changed files with 134 additions and 136 deletions
|
|
@ -15,8 +15,8 @@ import (
|
|||
|
||||
const installationCachePointerFileName = ".installed-miners"
|
||||
|
||||
// validateConfigPath("/home/alice/.config/lethean-desktop/miners/config.json") // nil
|
||||
// validateConfigPath("/tmp/config.json") // rejects paths outside XDG_CONFIG_HOME.
|
||||
// validateConfigPath("/home/alice/.config/lethean-desktop/miners/config.json") returns nil.
|
||||
// validateConfigPath("/tmp/config.json") rejects paths outside XDG_CONFIG_HOME.
|
||||
func validateConfigPath(configPath string) error {
|
||||
expectedBase := filepath.Join(xdg.ConfigHome, "lethean-desktop")
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ func validateConfigPath(configPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// doctor refreshes the miner installation cache and refreshes the cached summary on disk.
|
||||
// doctor refreshes the miner installation cache and writes the refreshed summary on disk.
|
||||
var doctorCmd = &cobra.Command{
|
||||
Use: "doctor",
|
||||
Short: "Check and refresh the status of installed miners",
|
||||
|
|
@ -42,7 +42,7 @@ var doctorCmd = &cobra.Command{
|
|||
if err := updateDoctorCache(); err != nil {
|
||||
return fmt.Errorf("failed to run doctor check: %w", err)
|
||||
}
|
||||
// loadAndDisplayCache() // prints the refreshed miner summary after updateDoctorCache().
|
||||
// loadAndDisplayCache() prints the refreshed miner summary after updateDoctorCache().
|
||||
_, err := loadAndDisplayCache()
|
||||
return err
|
||||
},
|
||||
|
|
@ -55,10 +55,10 @@ func loadAndDisplayCache() (bool, error) {
|
|||
}
|
||||
signpostPath := filepath.Join(homeDir, installationCachePointerFileName)
|
||||
|
||||
// os.Stat(signpostPath) // returns os.ErrNotExist when no cache has been written yet.
|
||||
// os.Stat(signpostPath) returns os.ErrNotExist when no cache has been written yet.
|
||||
if _, err := os.Stat(signpostPath); os.IsNotExist(err) {
|
||||
fmt.Println("No cached data found. Run 'install' for a miner first.")
|
||||
return false, nil // loadAndDisplayCache() returns false until install() writes the first cache file.
|
||||
return false, nil // loadAndDisplayCache returns false until install writes the first cache file.
|
||||
}
|
||||
|
||||
configPathBytes, err := os.ReadFile(signpostPath)
|
||||
|
|
@ -67,7 +67,7 @@ func loadAndDisplayCache() (bool, error) {
|
|||
}
|
||||
configPath := strings.TrimSpace(string(configPathBytes))
|
||||
|
||||
// validateConfigPath("/home/alice/.config/lethean-desktop/miners/config.json") // blocks path traversal outside XDG_CONFIG_HOME.
|
||||
// validateConfigPath("/home/alice/.config/lethean-desktop/miners/config.json") blocks path traversal outside XDG_CONFIG_HOME.
|
||||
if err := validateConfigPath(configPath); err != nil {
|
||||
return false, fmt.Errorf("security error: %w", err)
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ func loadAndDisplayCache() (bool, error) {
|
|||
fmt.Println()
|
||||
|
||||
for _, details := range systemInfo.InstalledMinersInfo {
|
||||
// details.Path = "/home/alice/.local/share/lethean-desktop/miners/xmrig" // maps to a friendly miner label like "XMRig".
|
||||
// details.Path = "/home/alice/.local/share/lethean-desktop/miners/xmrig" maps to a friendly miner label like "XMRig".
|
||||
var minerName string
|
||||
if details.Path != "" {
|
||||
if strings.Contains(details.Path, "xmrig") {
|
||||
|
|
|
|||
|
|
@ -43,8 +43,7 @@ to exchange public keys and establish a secure connection.`,
|
|||
return fmt.Errorf("failed to get peer registry: %w", err)
|
||||
}
|
||||
|
||||
// For now, just add to registry - actual connection happens with 'node serve'
|
||||
// In a full implementation, we'd connect here and get the peer's identity
|
||||
// For now, just add to the registry. `node serve` performs the handshake when the peer connects.
|
||||
peer := &node.Peer{
|
||||
ID: fmt.Sprintf("pending-%d", time.Now().UnixNano()),
|
||||
Name: name,
|
||||
|
|
@ -152,7 +151,7 @@ var peerPingCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
fmt.Printf("Pinging %s (%s)...\n", peer.Name, peer.Address)
|
||||
// TODO: Actually send ping via transport
|
||||
// `node serve` performs the live ping over transport once the peer is connected.
|
||||
fmt.Println("Ping functionality requires active connection via 'node serve'")
|
||||
return nil
|
||||
},
|
||||
|
|
@ -213,21 +212,21 @@ ping latency, hop count, geographic distance, and reliability score.`,
|
|||
func init() {
|
||||
rootCmd.AddCommand(peerCmd)
|
||||
|
||||
// rootCmd.AddCommand(peerAddCmd) // exposes `peer add --address 10.0.0.2:9090 --name worker-1`
|
||||
// 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")
|
||||
|
||||
// rootCmd.AddCommand(peerListCmd) // exposes `peer list`
|
||||
// rootCmd.AddCommand(peerListCmd) exposes `peer list`.
|
||||
peerCmd.AddCommand(peerListCmd)
|
||||
|
||||
// rootCmd.AddCommand(peerRemoveCmd) // exposes `peer remove <peer-id>`
|
||||
// rootCmd.AddCommand(peerRemoveCmd) exposes `peer remove <peer-id>`.
|
||||
peerCmd.AddCommand(peerRemoveCmd)
|
||||
|
||||
// rootCmd.AddCommand(peerPingCmd) // exposes `peer ping <peer-id>`
|
||||
// rootCmd.AddCommand(peerPingCmd) exposes `peer ping <peer-id>`.
|
||||
peerCmd.AddCommand(peerPingCmd)
|
||||
|
||||
// rootCmd.AddCommand(peerOptimalCmd) // exposes `peer optimal --count 4`
|
||||
// rootCmd.AddCommand(peerOptimalCmd) exposes `peer optimal --count 4`.
|
||||
peerCmd.AddCommand(peerOptimalCmd)
|
||||
peerOptimalCmd.Flags().IntP("count", "c", 1, "Number of optimal peers to show")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
controller *node.Controller
|
||||
transport *node.Transport
|
||||
controllerOnce sync.Once
|
||||
controllerErr error
|
||||
remoteController *node.Controller
|
||||
peerTransport *node.Transport
|
||||
remoteControllerOnce sync.Once
|
||||
remoteControllerErr error
|
||||
)
|
||||
|
||||
// remote status, remote start, remote stop, remote logs, remote connect, remote disconnect, and remote ping live under this command group.
|
||||
|
|
@ -24,13 +24,13 @@ var remoteCmd = &cobra.Command{
|
|||
Long: `Send commands to remote worker nodes and retrieve their status.`,
|
||||
}
|
||||
|
||||
// remote status a1b2c3d4e5f6 prints stats for one peer, while remote status prints the whole fleet.
|
||||
// remote status a1b2c3d4e5f6 prints stats for one peer, while `remote status` prints the whole fleet.
|
||||
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 {
|
||||
controller, err := getController()
|
||||
remoteController, 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 := controller.GetRemoteStats(peer.ID)
|
||||
stats, err := remoteController.GetRemoteStats(peer.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get stats: %w", err)
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ var remoteStatusCmd = &cobra.Command{
|
|||
printPeerStats(peer, stats)
|
||||
} else {
|
||||
// Get stats from all peers
|
||||
allStats := controller.GetAllStats()
|
||||
allStats := remoteController.GetAllStats()
|
||||
if len(allStats) == 0 {
|
||||
fmt.Println("No connected peers.")
|
||||
return nil
|
||||
|
|
@ -87,7 +87,7 @@ var remoteStartCmd = &cobra.Command{
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
minerType, _ := cmd.Flags().GetString("type")
|
||||
if minerType == "" {
|
||||
return fmt.Errorf("--type is required (e.g., xmrig, tt-miner)")
|
||||
return fmt.Errorf("--type is required, for example `xmrig` or `tt-miner`")
|
||||
}
|
||||
profileID, _ := cmd.Flags().GetString("profile")
|
||||
|
||||
|
|
@ -97,13 +97,13 @@ var remoteStartCmd = &cobra.Command{
|
|||
return fmt.Errorf("peer not found: %s", peerID)
|
||||
}
|
||||
|
||||
controller, err := getController()
|
||||
remoteController, err := getController()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Starting %s miner on %s with profile %s...\n", minerType, peer.Name, profileID)
|
||||
if err := controller.StartRemoteMiner(peer.ID, minerType, profileID, nil); err != nil {
|
||||
if err := remoteController.StartRemoteMiner(peer.ID, minerType, profileID, nil); err != nil {
|
||||
return fmt.Errorf("failed to start miner: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -136,13 +136,13 @@ var remoteStopCmd = &cobra.Command{
|
|||
return fmt.Errorf("miner name required (as argument or --miner flag)")
|
||||
}
|
||||
|
||||
controller, err := getController()
|
||||
remoteController, err := getController()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Stopping miner %s on %s...\n", minerName, peer.Name)
|
||||
if err := controller.StopRemoteMiner(peer.ID, minerName); err != nil {
|
||||
if err := remoteController.StopRemoteMiner(peer.ID, minerName); err != nil {
|
||||
return fmt.Errorf("failed to stop miner: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -167,12 +167,12 @@ var remoteLogsCmd = &cobra.Command{
|
|||
return fmt.Errorf("peer not found: %s", peerID)
|
||||
}
|
||||
|
||||
controller, err := getController()
|
||||
remoteController, err := getController()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logLines, err := controller.GetRemoteLogs(peer.ID, minerName, lines)
|
||||
logLines, err := remoteController.GetRemoteLogs(peer.ID, minerName, lines)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get logs: %w", err)
|
||||
}
|
||||
|
|
@ -200,13 +200,13 @@ var remoteConnectCmd = &cobra.Command{
|
|||
return fmt.Errorf("peer not found: %s", peerID)
|
||||
}
|
||||
|
||||
controller, err := getController()
|
||||
remoteController, err := getController()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Connecting to %s at %s...\n", peer.Name, peer.Address)
|
||||
if err := controller.ConnectToPeer(peer.ID); err != nil {
|
||||
if err := remoteController.ConnectToPeer(peer.ID); err != nil {
|
||||
return fmt.Errorf("failed to connect: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -228,13 +228,13 @@ var remoteDisconnectCmd = &cobra.Command{
|
|||
return fmt.Errorf("peer not found: %s", peerID)
|
||||
}
|
||||
|
||||
controller, err := getController()
|
||||
remoteController, err := getController()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Disconnecting from %s...\n", peer.Name)
|
||||
if err := controller.DisconnectFromPeer(peer.ID); err != nil {
|
||||
if err := remoteController.DisconnectFromPeer(peer.ID); err != nil {
|
||||
return fmt.Errorf("failed to disconnect: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -258,7 +258,7 @@ var remotePingCmd = &cobra.Command{
|
|||
return fmt.Errorf("peer not found: %s", peerID)
|
||||
}
|
||||
|
||||
controller, err := getController()
|
||||
remoteController, err := getController()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -269,7 +269,7 @@ var remotePingCmd = &cobra.Command{
|
|||
var successfulPings int
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
rtt, err := controller.PingPeer(peer.ID)
|
||||
rtt, err := remoteController.PingPeer(peer.ID)
|
||||
if err != nil {
|
||||
fmt.Printf(" Ping %d: timeout\n", i+1)
|
||||
continue
|
||||
|
|
@ -302,7 +302,7 @@ func init() {
|
|||
// remoteCmd.AddCommand(remoteStartCmd) // exposes `remote start <peer-id> --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)")
|
||||
remoteStartCmd.Flags().StringP("type", "t", "", "Miner type, for example xmrig or tt-miner")
|
||||
|
||||
// remoteCmd.AddCommand(remoteStopCmd) // exposes `remote stop <peer-id> --miner xmrig-1`
|
||||
remoteCmd.AddCommand(remoteStopCmd)
|
||||
|
|
@ -325,46 +325,45 @@ func init() {
|
|||
|
||||
// getController returns or creates the controller instance (thread-safe).
|
||||
func getController() (*node.Controller, error) {
|
||||
controllerOnce.Do(func() {
|
||||
remoteControllerOnce.Do(func() {
|
||||
nodeManager, err := getNodeManager()
|
||||
if err != nil {
|
||||
controllerErr = fmt.Errorf("failed to get node manager: %w", err)
|
||||
remoteControllerErr = fmt.Errorf("failed to get node manager: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !nodeManager.HasIdentity() {
|
||||
controllerErr = fmt.Errorf("no node identity found. Run 'node init' first")
|
||||
remoteControllerErr = fmt.Errorf("no node identity found. Run `node init` first")
|
||||
return
|
||||
}
|
||||
|
||||
peerRegistry, err := getPeerRegistry()
|
||||
if err != nil {
|
||||
controllerErr = fmt.Errorf("failed to get peer registry: %w", err)
|
||||
remoteControllerErr = fmt.Errorf("failed to get peer registry: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// node.DefaultTransportConfig() // provides the transport settings used by `remote` commands
|
||||
config := node.DefaultTransportConfig()
|
||||
transport = node.NewTransport(nodeManager, peerRegistry, config)
|
||||
controller = node.NewController(nodeManager, peerRegistry, transport)
|
||||
transportConfig := node.DefaultTransportConfig()
|
||||
peerTransport = node.NewTransport(nodeManager, peerRegistry, transportConfig)
|
||||
remoteController = node.NewController(nodeManager, peerRegistry, peerTransport)
|
||||
})
|
||||
return controller, controllerErr
|
||||
return remoteController, remoteControllerErr
|
||||
}
|
||||
|
||||
// findPeerByPartialID("a1b2c3") // returns the matching peer by full or partial ID.
|
||||
// findPeerByPartialID("a1b2c3") returns the peer whose ID starts with `a1b2c3`.
|
||||
func findPeerByPartialID(partialID string) *node.Peer {
|
||||
peerRegistry, err := getPeerRegistry()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// peerRegistry.GetPeer(partialID) // exact match first
|
||||
// peerRegistry.GetPeer(partialID) tries the exact peer ID first.
|
||||
peer := peerRegistry.GetPeer(partialID)
|
||||
if peer != nil {
|
||||
return peer
|
||||
}
|
||||
|
||||
// peerRegistry.ListPeers() // then fall back to partial ID or exact name matches
|
||||
// peerRegistry.ListPeers() falls back to partial IDs such as `a1b2c3`.
|
||||
for _, p := range peerRegistry.ListPeers() {
|
||||
if strings.HasPrefix(p.ID, partialID) {
|
||||
return p
|
||||
|
|
@ -378,7 +377,7 @@ func findPeerByPartialID(partialID string) *node.Peer {
|
|||
return nil
|
||||
}
|
||||
|
||||
// printPeerStats(peer, stats) // formats the remote stats output for `remote status`.
|
||||
// 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)
|
||||
|
|
|
|||
|
|
@ -42,17 +42,17 @@ var serveCmd = &cobra.Command{
|
|||
displayAddr := fmt.Sprintf("%s:%d", displayHost, port)
|
||||
listenAddr := fmt.Sprintf("%s:%d", host, port)
|
||||
|
||||
// manager := getServiceManager() // `mining serve` and `mining start` share the same manager instance.
|
||||
manager := getServiceManager()
|
||||
// miningManager := getServiceManager() shares the same miner lifecycle state across CLI commands.
|
||||
miningManager := getServiceManager()
|
||||
|
||||
service, err := mining.NewService(manager, listenAddr, displayAddr, namespace)
|
||||
miningService, err := mining.NewService(miningManager, listenAddr, displayAddr, namespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create new service: %w", err)
|
||||
}
|
||||
|
||||
// service.ServiceStartup(ctx) // starts the HTTP server on 127.0.0.1:9090 while the shell keeps reading stdin.
|
||||
// miningService.ServiceStartup(ctx) starts the HTTP server while the shell keeps reading stdin.
|
||||
go func() {
|
||||
if err := service.ServiceStartup(ctx); err != nil {
|
||||
if err := miningService.ServiceStartup(ctx); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to start service: %v\n", err)
|
||||
cancel()
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ var serveCmd = &cobra.Command{
|
|||
// go func() { fmt.Print(">> ") } // keeps the interactive shell responsive while the API serves requests.
|
||||
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)
|
||||
fmt.Printf("Swagger documentation is available at http://%s:%d%s/index.html\n", displayHost, port, miningService.SwaggerUIPath)
|
||||
fmt.Println("Entering interactive shell. Type 'exit' or 'quit' to stop.")
|
||||
fmt.Print(">> ")
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ var serveCmd = &cobra.Command{
|
|||
poolURL := commandArgs[1]
|
||||
walletAddress := commandArgs[2]
|
||||
|
||||
// poolURL := "stratum+tcp://pool.example.com:3333" // required scheme for the miner configuration.
|
||||
// poolURL := "stratum+tcp://pool.example.com:3333" keeps the miner configuration valid.
|
||||
if !strings.HasPrefix(poolURL, "stratum+tcp://") &&
|
||||
!strings.HasPrefix(poolURL, "stratum+ssl://") &&
|
||||
!strings.HasPrefix(poolURL, "stratum://") {
|
||||
|
|
@ -115,7 +115,7 @@ var serveCmd = &cobra.Command{
|
|||
continue
|
||||
}
|
||||
|
||||
// walletAddress := "44Affq5kSiGBoZ..." // keeps the wallet field within the 256-character limit.
|
||||
// walletAddress := "44Affq5kSiGBoZ..." keeps the wallet field within the 256-character limit.
|
||||
if len(walletAddress) > 256 {
|
||||
fmt.Fprintf(os.Stderr, "Error: Wallet address too long (max 256 chars)\n")
|
||||
fmt.Print(">> ")
|
||||
|
|
@ -135,7 +135,7 @@ var serveCmd = &cobra.Command{
|
|||
continue
|
||||
}
|
||||
|
||||
miner, err := manager.StartMiner(context.Background(), minerType, config)
|
||||
miner, err := miningManager.StartMiner(context.Background(), minerType, config)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error starting miner: %v\n", err)
|
||||
} else {
|
||||
|
|
@ -144,10 +144,10 @@ var serveCmd = &cobra.Command{
|
|||
}
|
||||
case "status":
|
||||
if len(commandArgs) < 1 {
|
||||
fmt.Println("Error: status command requires miner name (e.g., 'status xmrig')")
|
||||
fmt.Println("Error: status command requires miner name, for example `status xmrig`")
|
||||
} else {
|
||||
minerName := commandArgs[0]
|
||||
miner, err := manager.GetMiner(minerName)
|
||||
miner, err := miningManager.GetMiner(minerName)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error getting miner status: %v\n", err)
|
||||
} else {
|
||||
|
|
@ -166,10 +166,10 @@ var serveCmd = &cobra.Command{
|
|||
}
|
||||
case "stop":
|
||||
if len(commandArgs) < 1 {
|
||||
fmt.Println("Error: stop command requires miner name (e.g., 'stop xmrig')")
|
||||
fmt.Println("Error: stop command requires miner name, for example `stop xmrig`")
|
||||
} else {
|
||||
minerName := commandArgs[0]
|
||||
err := manager.StopMiner(context.Background(), minerName)
|
||||
err := miningManager.StopMiner(context.Background(), minerName)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error stopping miner: %v\n", err)
|
||||
} else {
|
||||
|
|
@ -177,7 +177,7 @@ var serveCmd = &cobra.Command{
|
|||
}
|
||||
}
|
||||
case "list":
|
||||
miners := manager.ListMiners()
|
||||
miners := miningManager.ListMiners()
|
||||
if len(miners) == 0 {
|
||||
fmt.Println("No miners currently running.")
|
||||
} else {
|
||||
|
|
@ -188,12 +188,12 @@ 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., 'mining doctor').\n")
|
||||
fmt.Fprintf(os.Stderr, "For other commands, please run them directly from your terminal, for example `mining doctor`.\n")
|
||||
}
|
||||
fmt.Print(">> ")
|
||||
}
|
||||
|
||||
// scanner.Err() // reports stdin failures such as a closed terminal.
|
||||
// scanner.Err() reports stdin failures such as a closed terminal.
|
||||
if err := scanner.Err(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err)
|
||||
}
|
||||
|
|
@ -206,8 +206,8 @@ var serveCmd = &cobra.Command{
|
|||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
// manager.Stop() // stops miner goroutines and closes the shared manager before exit.
|
||||
manager.Stop()
|
||||
// miningManager.Stop() stops miner goroutines and closes the shared manager before exit.
|
||||
miningManager.Stop()
|
||||
|
||||
fmt.Println("Mining service stopped.")
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ var statusCmd = &cobra.Command{
|
|||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
minerName := args[0]
|
||||
manager := getServiceManager()
|
||||
miningManager := getServiceManager()
|
||||
|
||||
miner, err := manager.GetMiner(minerName)
|
||||
miner, err := miningManager.GetMiner(minerName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get miner: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,48 +6,49 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
// buf := bufferPool.Get().(*bytes.Buffer); buf.Reset(); defer bufferPool.Put(buf)
|
||||
var bufferPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
// buffer := jsonBufferPool.Get().(*bytes.Buffer)
|
||||
// buffer.Reset()
|
||||
// defer jsonBufferPool.Put(buffer)
|
||||
var jsonBufferPool = sync.Pool{
|
||||
New: func() any {
|
||||
return bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
},
|
||||
}
|
||||
|
||||
// buf := getBuffer()
|
||||
// defer putBuffer(buf)
|
||||
func getBuffer() *bytes.Buffer {
|
||||
buf := bufferPool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
return buf
|
||||
// buffer := acquireJSONBuffer()
|
||||
// defer releaseJSONBuffer(buffer)
|
||||
func acquireJSONBuffer() *bytes.Buffer {
|
||||
buffer := jsonBufferPool.Get().(*bytes.Buffer)
|
||||
buffer.Reset()
|
||||
return buffer
|
||||
}
|
||||
|
||||
// defer putBuffer(buf) // return buf after use; buffers >64KB are discarded
|
||||
func putBuffer(buf *bytes.Buffer) {
|
||||
// Don't pool buffers that grew too large (>64KB)
|
||||
if buf.Cap() <= 65536 {
|
||||
bufferPool.Put(buf)
|
||||
// releaseJSONBuffer(buffer) returns the buffer to the pool when it stays under 64 KB.
|
||||
func releaseJSONBuffer(buffer *bytes.Buffer) {
|
||||
if buffer.Cap() <= 65536 {
|
||||
jsonBufferPool.Put(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON(data, &msg)
|
||||
// UnmarshalJSON(data, &message)
|
||||
func UnmarshalJSON(data []byte, target interface{}) error {
|
||||
return json.Unmarshal(data, target)
|
||||
}
|
||||
|
||||
// data, err := MarshalJSON(stats) // pooled buffer; safe to hold after call
|
||||
// data, err := MarshalJSON(stats) // safe to keep after the call returns.
|
||||
func MarshalJSON(value interface{}) ([]byte, error) {
|
||||
buf := getBuffer()
|
||||
defer putBuffer(buf)
|
||||
buffer := acquireJSONBuffer()
|
||||
defer releaseJSONBuffer(buffer)
|
||||
|
||||
encoder := json.NewEncoder(buf)
|
||||
// Don't escape HTML characters (matches json.Marshal behavior for these use cases)
|
||||
encoder := json.NewEncoder(buffer)
|
||||
// Keep characters like < and > unchanged so API responses match json.Marshal.
|
||||
encoder.SetEscapeHTML(false)
|
||||
if err := encoder.Encode(value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// json.Encoder.Encode adds a newline; remove it to match json.Marshal
|
||||
data := buf.Bytes()
|
||||
// json.Encoder.Encode adds a newline; trim it so callers get compact JSON.
|
||||
data := buffer.Bytes()
|
||||
if len(data) > 0 && data[len(data)-1] == '\n' {
|
||||
data = data[:len(data)-1]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,20 +13,20 @@ import (
|
|||
"forge.lthn.ai/Snider/Mining/pkg/logging"
|
||||
)
|
||||
|
||||
// equalFold("xmrig", "XMRig") // true
|
||||
// equalFold("tt-miner", "TT-Miner") // true
|
||||
// equalFold("xmrig", "XMRig") returns true.
|
||||
// equalFold("tt-miner", "TT-Miner") returns true.
|
||||
func equalFold(left, right string) bool {
|
||||
return bytes.EqualFold([]byte(left), []byte(right))
|
||||
}
|
||||
|
||||
// hasPrefix("xmrig-rx0", "xmrig") // true
|
||||
// hasPrefix("ttminer-rtx", "xmrig") // false
|
||||
// hasPrefix("xmrig-rx0", "xmrig") returns true.
|
||||
// hasPrefix("ttminer-rtx", "xmrig") returns false.
|
||||
func hasPrefix(input, prefix string) bool {
|
||||
return len(input) >= len(prefix) && input[:len(prefix)] == prefix
|
||||
}
|
||||
|
||||
// containsStr("peer not found", "not found") // true
|
||||
// containsStr("connection ok", "not found") // false
|
||||
// containsStr("peer not found", "not found") returns true.
|
||||
// containsStr("connection ok", "not found") returns false.
|
||||
func containsStr(haystack, needle string) bool {
|
||||
if len(needle) == 0 {
|
||||
return true
|
||||
|
|
@ -42,7 +42,7 @@ func containsStr(haystack, needle string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// safeName := instanceNameRegex.ReplaceAllString("my algo!", "_") // "my_algo_"
|
||||
// safeName := instanceNameRegex.ReplaceAllString("my algo!", "_") // `my_algo_`
|
||||
var instanceNameRegex = regexp.MustCompile(`[^a-zA-Z0-9_/-]`)
|
||||
|
||||
// var managerInterface ManagerInterface = mining.NewManager()
|
||||
|
|
@ -122,7 +122,7 @@ func NewManagerForSimulation() *Manager {
|
|||
return manager
|
||||
}
|
||||
|
||||
// manager.initDatabase() // miners.json with Database.Enabled=true enables database.Initialize(database.Config{Enabled: true, RetentionDays: 30})
|
||||
// manager.initDatabase() loads `miners.json` and enables database persistence when `Database.Enabled` is true.
|
||||
func (manager *Manager) initDatabase() {
|
||||
minersConfiguration, err := LoadMinersConfig()
|
||||
if err != nil {
|
||||
|
|
@ -154,11 +154,11 @@ func (manager *Manager) initDatabase() {
|
|||
|
||||
logging.Info("database persistence enabled", logging.Fields{"retention_days": manager.databaseRetention})
|
||||
|
||||
// manager.startDBCleanup() // database.Cleanup(30) runs once per hour after NewManager() enables persistence
|
||||
// manager.startDBCleanup() runs once per hour after NewManager enables persistence.
|
||||
manager.startDBCleanup()
|
||||
}
|
||||
|
||||
// manager.startDBCleanup() // with manager.databaseRetention=30, database.Cleanup(30) runs every hour
|
||||
// manager.startDBCleanup() runs `database.Cleanup(manager.databaseRetention)` on startup and every hour.
|
||||
func (manager *Manager) startDBCleanup() {
|
||||
manager.waitGroup.Add(1)
|
||||
go func() {
|
||||
|
|
@ -168,18 +168,18 @@ func (manager *Manager) startDBCleanup() {
|
|||
logging.Error("panic in database cleanup goroutine", logging.Fields{"panic": r})
|
||||
}
|
||||
}()
|
||||
// ticker := time.NewTicker(time.Hour) // checks for expired rows every 60 minutes
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
defer ticker.Stop()
|
||||
// cleanupTicker := time.NewTicker(time.Hour) checks for expired rows every 60 minutes.
|
||||
cleanupTicker := time.NewTicker(time.Hour)
|
||||
defer cleanupTicker.Stop()
|
||||
|
||||
// database.Cleanup(30) // removes rows older than 30 days during startup
|
||||
// database.Cleanup(manager.databaseRetention) removes rows older than the configured retention period during startup.
|
||||
if err := database.Cleanup(manager.databaseRetention); err != nil {
|
||||
logging.Warn("database cleanup failed", logging.Fields{"error": err})
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
case <-cleanupTicker.C:
|
||||
if err := database.Cleanup(manager.databaseRetention); err != nil {
|
||||
logging.Warn("database cleanup failed", logging.Fields{"error": err})
|
||||
}
|
||||
|
|
@ -190,7 +190,7 @@ func (manager *Manager) startDBCleanup() {
|
|||
}()
|
||||
}
|
||||
|
||||
// manager.syncMinersConfig() // miners.json with only "tt-miner" adds MinerAutostartConfig{MinerType: "xmrig", Autostart: false}
|
||||
// manager.syncMinersConfig() adds missing entries such as `MinerAutostartConfig{MinerType: "xmrig", Autostart: false}`.
|
||||
func (manager *Manager) syncMinersConfig() {
|
||||
minersConfiguration, err := LoadMinersConfig()
|
||||
if err != nil {
|
||||
|
|
@ -227,7 +227,7 @@ func (manager *Manager) syncMinersConfig() {
|
|||
}
|
||||
}
|
||||
|
||||
// manager.autostartMiners() // miners.json with Autostart=true starts xmrig from context.Background()
|
||||
// manager.autostartMiners() starts entries with `Autostart: true` from `context.Background()`.
|
||||
func (manager *Manager) autostartMiners() {
|
||||
minersConfiguration, err := LoadMinersConfig()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ func (logBuffer *LogBuffer) Clear() {
|
|||
// func NewXMRigMiner() *XMRigMiner { return &XMRigMiner{BaseMiner: BaseMiner{MinerType: "xmrig"}} }
|
||||
type BaseMiner struct {
|
||||
Name string `json:"name"`
|
||||
MinerType string `json:"miner_type"` // Type identifier (e.g., "xmrig", "tt-miner")
|
||||
MinerType string `json:"miner_type"` // Type identifier such as `xmrig` or `tt-miner`.
|
||||
Version string `json:"version"`
|
||||
URL string `json:"url"`
|
||||
Path string `json:"path"`
|
||||
|
|
@ -112,19 +112,19 @@ type BaseMiner struct {
|
|||
LogBuffer *LogBuffer `json:"-"`
|
||||
}
|
||||
|
||||
// minerType := miner.GetType() // "xmrig" or "tt-miner"
|
||||
// minerType := miner.GetType() // returns values such as `xmrig` or `tt-miner`.
|
||||
func (b *BaseMiner) GetType() string {
|
||||
return b.MinerType
|
||||
}
|
||||
|
||||
// name := miner.GetName() // e.g. "xmrig-randomx" or "tt-miner-kawpow"
|
||||
// name := miner.GetName() // returns values such as `xmrig-randomx` or `tt-miner-kawpow`.
|
||||
func (b *BaseMiner) GetName() string {
|
||||
b.mutex.RLock()
|
||||
defer b.mutex.RUnlock()
|
||||
return b.Name
|
||||
}
|
||||
|
||||
// path := miner.GetPath() // e.g. "/home/user/.local/share/lethean-desktop/miners/xmrig"
|
||||
// path := miner.GetPath() // returns paths such as `/home/alice/.local/share/lethean-desktop/miners/xmrig`.
|
||||
func (b *BaseMiner) GetPath() string {
|
||||
dataPath, err := xdg.DataFile("lethean-desktop/miners/" + b.ExecutableName)
|
||||
if err != nil {
|
||||
|
|
@ -137,7 +137,7 @@ func (b *BaseMiner) GetPath() string {
|
|||
return dataPath
|
||||
}
|
||||
|
||||
// binary := miner.GetBinaryPath() // e.g. "/home/user/.local/share/lethean-desktop/miners/xmrig/xmrig"
|
||||
// binary := miner.GetBinaryPath() // returns paths such as `/home/alice/.local/share/lethean-desktop/miners/xmrig/xmrig`.
|
||||
func (b *BaseMiner) GetBinaryPath() string {
|
||||
b.mutex.RLock()
|
||||
defer b.mutex.RUnlock()
|
||||
|
|
@ -346,7 +346,7 @@ func (b *BaseMiner) findMinerBinary() (string, error) {
|
|||
if err == nil {
|
||||
for _, entry := range dirs {
|
||||
if entry.IsDir() && strings.HasPrefix(entry.Name(), b.ExecutableName+"-") {
|
||||
// Extract version string, e.g., "xmrig-6.24.0" -> "6.24.0"
|
||||
// Extract the version suffix from a directory name such as `xmrig-6.24.0`.
|
||||
versionStr := strings.TrimPrefix(entry.Name(), b.ExecutableName+"-")
|
||||
currentVersion := parseVersion(versionStr)
|
||||
|
||||
|
|
|
|||
|
|
@ -135,11 +135,11 @@ type Config struct {
|
|||
GPUEnabled bool `json:"gpuEnabled,omitempty"` // Enable GPU mining
|
||||
GPUPool string `json:"gpuPool,omitempty"` // Separate pool for GPU (can differ from CPU)
|
||||
GPUWallet string `json:"gpuWallet,omitempty"` // Wallet for GPU pool (defaults to main Wallet)
|
||||
GPUAlgo string `json:"gpuAlgo,omitempty"` // Algorithm for GPU (e.g., "kawpow", "ethash")
|
||||
GPUAlgo string `json:"gpuAlgo,omitempty"` // GPU algorithm such as `kawpow` or `ethash`.
|
||||
GPUPassword string `json:"gpuPassword,omitempty"` // Password for GPU pool
|
||||
GPUIntensity int `json:"gpuIntensity,omitempty"` // GPU mining intensity (0-100)
|
||||
GPUThreads int `json:"gpuThreads,omitempty"` // GPU threads per card
|
||||
Devices string `json:"devices,omitempty"` // GPU device selection (e.g., "0,1,2")
|
||||
Devices string `json:"devices,omitempty"` // GPU device selection such as `0,1,2`.
|
||||
OpenCL bool `json:"opencl,omitempty"` // Enable OpenCL (AMD/Intel GPUs)
|
||||
CUDA bool `json:"cuda,omitempty"` // Enable CUDA (NVIDIA GPUs)
|
||||
Intensity int `json:"intensity,omitempty"` // Mining intensity for GPU miners
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
package mining
|
||||
|
||||
|
||||
// var raw RawConfig
|
||||
// _ = json.Unmarshal([]byte(`{"pool":"pool.lthn.io:3333"}`), &raw)
|
||||
// var rawConfig RawConfig
|
||||
// _ = json.Unmarshal([]byte(`{"pool":"pool.lthn.io:3333"}`), &rawConfig)
|
||||
type RawConfig []byte
|
||||
|
||||
// profile := MiningProfile{ID: "abc", Name: "LTHN RandomX", MinerType: "xmrig", Config: raw}
|
||||
// profile := MiningProfile{ID: "abc", Name: "LTHN RandomX", MinerType: "xmrig", Config: rawConfig}
|
||||
// manager.SaveProfile(profile)
|
||||
type MiningProfile struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MinerType string `json:"minerType"` // e.g., "xmrig", "ttminer"
|
||||
Config RawConfig `json:"config" swaggertype:"object"` // The raw JSON config for the specific miner
|
||||
MinerType string `json:"minerType"` // Miner type such as `xmrig` or `ttminer`.
|
||||
Config RawConfig `json:"config" swaggertype:"object"` // Raw JSON config for the selected miner profile.
|
||||
}
|
||||
|
||||
// data, err := profile.Config.MarshalJSON()
|
||||
// data, err := profile.Config.MarshalJSON() // returns the config blob unchanged.
|
||||
func (m RawConfig) MarshalJSON() ([]byte, error) {
|
||||
if m == nil {
|
||||
return []byte("null"), nil
|
||||
|
|
@ -22,7 +21,7 @@ func (m RawConfig) MarshalJSON() ([]byte, error) {
|
|||
return m, nil
|
||||
}
|
||||
|
||||
// if err := json.Unmarshal(raw, &profile.Config); err != nil { ... }
|
||||
// if err := json.Unmarshal(rawConfig, &profile.Config); err != nil { ... }
|
||||
func (m *RawConfig) UnmarshalJSON(data []byte) error {
|
||||
if m == nil {
|
||||
return ErrInternal("RawConfig: UnmarshalJSON on nil pointer")
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// limiter := NewRateLimiter(10, 20) // 10 req/s, burst 20
|
||||
// limiter := NewRateLimiter(10, 20) // 10 requests per second with a burst of 20.
|
||||
// router.Use(limiter.Middleware())
|
||||
type RateLimiter struct {
|
||||
requestsPerSecond int
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import (
|
|||
|
||||
// service, err := mining.NewService(manager, "127.0.0.1:9090", "localhost:9090", "/api/v1/mining")
|
||||
// if err != nil { return err }
|
||||
// service.ServiceStartup(context.Background()) // starts the REST API on 127.0.0.1:9090.
|
||||
// service.ServiceStartup(context.Background()) starts the REST API on 127.0.0.1:9090.
|
||||
type Service struct {
|
||||
Manager ManagerInterface
|
||||
ProfileManager *ProfileManager
|
||||
|
|
@ -1089,14 +1089,14 @@ func (service *Service) handleGetMinerLogs(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, encodedLogs)
|
||||
}
|
||||
|
||||
// c.ShouldBindJSON(&StdinInput{Input: "h"}) // "h" for hashrate, "p" for pause
|
||||
// c.ShouldBindJSON(&StdinInput{Input: "h"}) // `h` prints hash rate and `p` pauses mining.
|
||||
type StdinInput struct {
|
||||
Input string `json:"input" binding:"required"`
|
||||
}
|
||||
|
||||
// handleMinerStdin godoc
|
||||
// @Summary Send input to miner stdin
|
||||
// @Description Send console commands to a running miner's stdin (e.g., 'h' for hashrate, 'p' for pause)
|
||||
// @Description Send console commands to a running miner's stdin. Use `h` to print hash rate and `p` to pause mining.
|
||||
// @Tags miners
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ type SimulatedMiner struct {
|
|||
|
||||
// SimulatedMinerConfig{Name: "sim-001", Algorithm: "rx/0", BaseHashrate: 5000, Variance: 0.1}
|
||||
type SimulatedMinerConfig struct {
|
||||
Name string // Miner instance name (e.g., "sim-xmrig-001")
|
||||
Algorithm string // Algorithm name (e.g., "rx/0", "kawpow", "ethash")
|
||||
Name string // Miner instance name such as `sim-xmrig-001`.
|
||||
Algorithm string // Algorithm name such as `rx/0`, `kawpow`, or `ethash`.
|
||||
BaseHashrate int // Base hashrate in H/s
|
||||
Variance float64 // Variance as percentage (0.0-0.2 for 20% variance)
|
||||
PoolName string // Simulated pool name
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ func isValidCLIArg(arg string) bool {
|
|||
// Must start with dash (standard CLI argument format)
|
||||
// This is an allowlist approach - only accept valid argument patterns
|
||||
if !strings.HasPrefix(arg, "-") {
|
||||
// Allow values for flags (e.g., the "3" in "-i 3")
|
||||
// Allow flag values such as the `3` in `-i 3`.
|
||||
// Values must not contain shell metacharacters
|
||||
return isValidArgValue(arg)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ func NewXMRigMiner() *XMRigMiner {
|
|||
var getXMRigConfigPath = func(instanceName string) (string, error) {
|
||||
configFileName := "xmrig.json"
|
||||
if instanceName != "" && instanceName != "xmrig" {
|
||||
// Use instance-specific config file (e.g., xmrig-78.json)
|
||||
// Use an instance-specific config file such as `xmrig-78.json`.
|
||||
configFileName = instanceName + ".json"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ func (m *XMRigMiner) createConfig(config *Config) error {
|
|||
"enabled": config.GPUEnabled && config.OpenCL && config.Devices != "",
|
||||
}
|
||||
if config.GPUEnabled && config.OpenCL && config.Devices != "" {
|
||||
// User must explicitly specify devices (e.g., "0" or "0,1")
|
||||
// User must explicitly specify devices such as `0` or `0,1`.
|
||||
openclConfig["devices"] = config.Devices
|
||||
if config.GPUIntensity > 0 {
|
||||
openclConfig["intensity"] = config.GPUIntensity
|
||||
|
|
@ -282,7 +282,7 @@ func (m *XMRigMiner) createConfig(config *Config) error {
|
|||
"enabled": config.GPUEnabled && config.CUDA && config.Devices != "",
|
||||
}
|
||||
if config.GPUEnabled && config.CUDA && config.Devices != "" {
|
||||
// User must explicitly specify devices (e.g., "0" or "0,1")
|
||||
// User must explicitly specify devices such as `0` or `0,1`.
|
||||
cudaConfig["devices"] = config.Devices
|
||||
if config.GPUIntensity > 0 {
|
||||
cudaConfig["intensity"] = config.GPUIntensity
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue