AX: clarify cache paths and remote command examples
This commit is contained in:
parent
ba0d61d4bc
commit
85cfb18ddd
3 changed files with 53 additions and 53 deletions
|
|
@ -15,12 +15,12 @@ import (
|
|||
|
||||
const installationCachePointerFileName = ".installed-miners"
|
||||
|
||||
// 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 {
|
||||
// validateCacheFilePath("/home/alice/.config/lethean-desktop/miners/config.json") returns nil.
|
||||
// validateCacheFilePath("/tmp/config.json") rejects paths outside XDG_CONFIG_HOME.
|
||||
func validateCacheFilePath(cacheFilePath string) error {
|
||||
expectedBase := filepath.Join(xdg.ConfigHome, "lethean-desktop")
|
||||
|
||||
cleanPath := filepath.Clean(configPath)
|
||||
cleanPath := filepath.Clean(cacheFilePath)
|
||||
|
||||
if !strings.HasPrefix(cleanPath, expectedBase+string(os.PathSeparator)) && cleanPath != expectedBase {
|
||||
return fmt.Errorf("invalid config path: must be within %s", expectedBase)
|
||||
|
|
@ -29,7 +29,7 @@ func validateConfigPath(configPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// doctor refreshes the miner installation cache and writes the refreshed summary on disk.
|
||||
// doctorCmd adds `doctor` so `mining doctor` refreshes the miner cache and prints the installed miner summary.
|
||||
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 `mining doctor` refreshes the cache.
|
||||
_, err := loadAndDisplayCache()
|
||||
return err
|
||||
},
|
||||
|
|
@ -55,30 +55,30 @@ func loadAndDisplayCache() (bool, error) {
|
|||
}
|
||||
signpostPath := filepath.Join(homeDir, installationCachePointerFileName)
|
||||
|
||||
// os.Stat(signpostPath) returns os.ErrNotExist when no cache has been written yet.
|
||||
// os.Stat("/home/alice/.installed-miners") returns os.ErrNotExist before the first `mining install xmrig` run.
|
||||
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.
|
||||
}
|
||||
|
||||
configPathBytes, err := os.ReadFile(signpostPath)
|
||||
cachePointerBytes, err := os.ReadFile(signpostPath)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not read signpost file: %w", err)
|
||||
}
|
||||
configPath := strings.TrimSpace(string(configPathBytes))
|
||||
cacheFilePath := strings.TrimSpace(string(cachePointerBytes))
|
||||
|
||||
// validateConfigPath("/home/alice/.config/lethean-desktop/miners/config.json") blocks path traversal outside XDG_CONFIG_HOME.
|
||||
if err := validateConfigPath(configPath); err != nil {
|
||||
// validateCacheFilePath("/home/alice/.config/lethean-desktop/miners/config.json") blocks path traversal outside XDG_CONFIG_HOME.
|
||||
if err := validateCacheFilePath(cacheFilePath); err != nil {
|
||||
return false, fmt.Errorf("security error: %w", err)
|
||||
}
|
||||
|
||||
cacheBytes, err := os.ReadFile(configPath)
|
||||
cacheBytes, err := os.ReadFile(cacheFilePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Println("No cached data found. Run 'install' for a miner first.")
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("could not read cache file from %s: %w", configPath, err)
|
||||
return false, fmt.Errorf("could not read cache file from %s: %w", cacheFilePath, err)
|
||||
}
|
||||
|
||||
var systemInfo mining.SystemInfo
|
||||
|
|
@ -109,22 +109,22 @@ func loadAndDisplayCache() (bool, error) {
|
|||
}
|
||||
|
||||
func saveResultsToCache(systemInfo *mining.SystemInfo) error {
|
||||
cacheRelativePath := filepath.Join("lethean-desktop", "miners")
|
||||
configDir, err := xdg.ConfigFile(cacheRelativePath)
|
||||
cacheDirectoryRelativePath := filepath.Join("lethean-desktop", "miners")
|
||||
cacheDirectoryPath, err := xdg.ConfigFile(cacheDirectoryRelativePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get config directory: %w", err)
|
||||
}
|
||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(cacheDirectoryPath, 0755); err != nil {
|
||||
return fmt.Errorf("could not create config directory: %w", err)
|
||||
}
|
||||
configPath := filepath.Join(configDir, "config.json")
|
||||
cacheFilePath := filepath.Join(cacheDirectoryPath, "config.json")
|
||||
|
||||
data, err := json.MarshalIndent(systemInfo, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not marshal cache data: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
||||
if err := os.WriteFile(cacheFilePath, data, 0600); err != nil {
|
||||
return fmt.Errorf("could not write cache file: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -133,11 +133,11 @@ func saveResultsToCache(systemInfo *mining.SystemInfo) error {
|
|||
return fmt.Errorf("could not get home directory for signpost: %w", err)
|
||||
}
|
||||
signpostPath := filepath.Join(homeDir, installationCachePointerFileName)
|
||||
if err := os.WriteFile(signpostPath, []byte(configPath), 0600); err != nil {
|
||||
if err := os.WriteFile(signpostPath, []byte(cacheFilePath), 0600); err != nil {
|
||||
return fmt.Errorf("could not write signpost file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\n(Cache updated at %s)\n", configPath)
|
||||
fmt.Printf("\n(Cache updated at %s)\n", cacheFilePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,14 +17,14 @@ var (
|
|||
remoteControllerErr error
|
||||
)
|
||||
|
||||
// remote status, remote start, remote stop, remote logs, remote connect, remote disconnect, and remote ping live under this command group.
|
||||
// remote status peer-19f3, remote start peer-19f3 --type xmrig, and remote ping peer-19f3 --count 4 live under this command group.
|
||||
var remoteCmd = &cobra.Command{
|
||||
Use: "remote",
|
||||
Short: "Control remote mining nodes",
|
||||
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 peer-19f3 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",
|
||||
|
|
@ -36,7 +36,7 @@ var remoteStatusCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
// Get stats from specific peer
|
||||
// remote status peer-19f3 shows that peer's stats.
|
||||
peerID := args[0]
|
||||
peer := findPeerByPartialID(peerID)
|
||||
if peer == nil {
|
||||
|
|
@ -50,7 +50,7 @@ var remoteStatusCmd = &cobra.Command{
|
|||
|
||||
printPeerStats(peer, stats)
|
||||
} else {
|
||||
// Get stats from all peers
|
||||
// remote status peer-19f3 shows one peer, while `remote status` shows the fleet.
|
||||
allStats := remoteController.GetAllStats()
|
||||
if len(allStats) == 0 {
|
||||
fmt.Println("No connected peers.")
|
||||
|
|
@ -78,7 +78,7 @@ var remoteStatusCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
// remote start a1b2c3d4e5f6 --type xmrig --profile default starts a miner on the selected peer.
|
||||
// remote start peer-19f3 --type xmrig --profile default starts a miner on the selected peer.
|
||||
var remoteStartCmd = &cobra.Command{
|
||||
Use: "start <peer-id>",
|
||||
Short: "Start miner on remote peer",
|
||||
|
|
@ -112,7 +112,7 @@ var remoteStartCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
// remote stop a1b2c3d4e5f6 xmrig-1 stops a named miner on the selected peer.
|
||||
// remote stop peer-19f3 xmrig-main stops a named miner on the selected peer.
|
||||
var remoteStopCmd = &cobra.Command{
|
||||
Use: "stop <peer-id> [miner-name]",
|
||||
Short: "Stop miner on remote peer",
|
||||
|
|
@ -151,7 +151,7 @@ var remoteStopCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
// remote logs a1b2c3d4e5f6 xmrig-1 prints the first 100 log lines for the remote miner.
|
||||
// remote logs peer-19f3 xmrig-main prints the first 100 log lines for the remote miner.
|
||||
var remoteLogsCmd = &cobra.Command{
|
||||
Use: "logs <peer-id> <miner-name>",
|
||||
Short: "Get console logs from remote miner",
|
||||
|
|
@ -187,7 +187,7 @@ var remoteLogsCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
// remote connect a1b2c3d4e5f6 opens a WebSocket connection to the peer.
|
||||
// remote connect peer-19f3 opens a WebSocket connection to the peer.
|
||||
var remoteConnectCmd = &cobra.Command{
|
||||
Use: "connect <peer-id>",
|
||||
Short: "Connect to a remote peer",
|
||||
|
|
@ -215,7 +215,7 @@ var remoteConnectCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
// remote disconnect a1b2c3d4e5f6 closes the active peer connection.
|
||||
// remote disconnect peer-19f3 closes the active peer connection.
|
||||
var remoteDisconnectCmd = &cobra.Command{
|
||||
Use: "disconnect <peer-id>",
|
||||
Short: "Disconnect from a remote peer",
|
||||
|
|
@ -243,7 +243,7 @@ var remoteDisconnectCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
// remote ping a1b2c3d4e5f6 --count 4 averages four ping samples.
|
||||
// remote ping peer-19f3 --count 4 averages four ping samples.
|
||||
var remotePingCmd = &cobra.Command{
|
||||
Use: "ping <peer-id>",
|
||||
Short: "Ping a remote peer",
|
||||
|
|
@ -296,34 +296,34 @@ var remotePingCmd = &cobra.Command{
|
|||
func init() {
|
||||
rootCmd.AddCommand(remoteCmd)
|
||||
|
||||
// remoteCmd.AddCommand(remoteStatusCmd) // exposes `remote status <peer-id>`
|
||||
// remoteCmd.AddCommand(remoteStatusCmd) // remote status peer-19f3 prints one peer, while `remote status` prints the fleet.
|
||||
remoteCmd.AddCommand(remoteStatusCmd)
|
||||
|
||||
// remoteCmd.AddCommand(remoteStartCmd) // exposes `remote start <peer-id> --type xmrig --profile default`
|
||||
// remoteCmd.AddCommand(remoteStartCmd) // remote start peer-19f3 --type xmrig --profile default launches a miner.
|
||||
remoteCmd.AddCommand(remoteStartCmd)
|
||||
remoteStartCmd.Flags().StringP("profile", "p", "", "Profile ID to use for starting the 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) // remote stop peer-19f3 xmrig-main stops the selected miner.
|
||||
remoteCmd.AddCommand(remoteStopCmd)
|
||||
remoteStopCmd.Flags().StringP("miner", "m", "", "Miner name to stop")
|
||||
|
||||
// remoteCmd.AddCommand(remoteLogsCmd) // exposes `remote logs <peer-id> <miner-name>`
|
||||
// remoteCmd.AddCommand(remoteLogsCmd) // remote logs peer-19f3 xmrig-main prints miner logs.
|
||||
remoteCmd.AddCommand(remoteLogsCmd)
|
||||
remoteLogsCmd.Flags().IntP("lines", "n", 100, "Number of log lines to retrieve")
|
||||
|
||||
// remoteCmd.AddCommand(remoteConnectCmd) // exposes `remote connect <peer-id>`
|
||||
// remoteCmd.AddCommand(remoteConnectCmd) // remote connect peer-19f3 opens the peer connection.
|
||||
remoteCmd.AddCommand(remoteConnectCmd)
|
||||
|
||||
// remoteCmd.AddCommand(remoteDisconnectCmd) // exposes `remote disconnect <peer-id>`
|
||||
// remoteCmd.AddCommand(remoteDisconnectCmd) // remote disconnect peer-19f3 closes the peer connection.
|
||||
remoteCmd.AddCommand(remoteDisconnectCmd)
|
||||
|
||||
// remoteCmd.AddCommand(remotePingCmd) // exposes `remote ping <peer-id>`
|
||||
// remoteCmd.AddCommand(remotePingCmd) // remote ping peer-19f3 --count 4 measures latency.
|
||||
remoteCmd.AddCommand(remotePingCmd)
|
||||
remotePingCmd.Flags().IntP("count", "c", 4, "Number of pings to send")
|
||||
}
|
||||
|
||||
// getController returns or creates the controller instance (thread-safe).
|
||||
// getController() returns the cached controller after `node init` succeeds.
|
||||
func getController() (*node.Controller, error) {
|
||||
remoteControllerOnce.Do(func() {
|
||||
nodeManager, err := getNodeManager()
|
||||
|
|
@ -350,20 +350,20 @@ func getController() (*node.Controller, error) {
|
|||
return remoteController, remoteControllerErr
|
||||
}
|
||||
|
||||
// findPeerByPartialID("a1b2c3") returns the peer whose ID starts with `a1b2c3`.
|
||||
// findPeerByPartialID("peer-19f3") returns the peer whose ID starts with `peer-19f3`.
|
||||
func findPeerByPartialID(partialID string) *node.Peer {
|
||||
peerRegistry, err := getPeerRegistry()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// peerRegistry.GetPeer(partialID) tries the exact peer ID first.
|
||||
// peerRegistry.GetPeer("peer-19f3") tries the exact peer ID first.
|
||||
peer := peerRegistry.GetPeer(partialID)
|
||||
if peer != nil {
|
||||
return peer
|
||||
}
|
||||
|
||||
// peerRegistry.ListPeers() falls back to partial IDs such as `a1b2c3`.
|
||||
// peerRegistry.ListPeers() falls back to partial IDs such as `peer-19`.
|
||||
for _, p := range peerRegistry.ListPeers() {
|
||||
if strings.HasPrefix(p.ID, partialID) {
|
||||
return p
|
||||
|
|
@ -377,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 peer-19f3`.
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -13,17 +13,17 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// validateUpdateConfigPath("/home/alice/.config/lethean-desktop/miners/config.json") // nil
|
||||
func validateUpdateConfigPath(configPath string) error {
|
||||
// validateUpdateCacheFilePath("/home/alice/.config/lethean-desktop/miners/config.json") returns nil.
|
||||
func validateUpdateCacheFilePath(cacheFilePath string) error {
|
||||
expectedBase := filepath.Join(xdg.ConfigHome, "lethean-desktop")
|
||||
cleanPath := filepath.Clean(configPath)
|
||||
cleanPath := filepath.Clean(cacheFilePath)
|
||||
if !strings.HasPrefix(cleanPath, expectedBase+string(os.PathSeparator)) && cleanPath != expectedBase {
|
||||
return fmt.Errorf("invalid config path: must be within %s", expectedBase)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mining update checks cached miner versions and reports available upgrades.
|
||||
// updateCmd adds `update` so `mining update` can compare the cached miner version against the latest release.
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Check for updates to installed miners",
|
||||
|
|
@ -42,23 +42,23 @@ var updateCmd = &cobra.Command{
|
|||
return nil
|
||||
}
|
||||
|
||||
configPathBytes, err := os.ReadFile(signpostPath)
|
||||
cachePointerBytes, err := os.ReadFile(signpostPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read signpost file: %w", err)
|
||||
}
|
||||
configPath := strings.TrimSpace(string(configPathBytes))
|
||||
cacheFilePath := strings.TrimSpace(string(cachePointerBytes))
|
||||
|
||||
// validateUpdateConfigPath("/home/alice/.config/lethean-desktop/miners/config.json") // blocks path traversal
|
||||
if err := validateUpdateConfigPath(configPath); err != nil {
|
||||
// validateUpdateCacheFilePath("/home/alice/.config/lethean-desktop/miners/config.json") blocks path traversal.
|
||||
if err := validateUpdateCacheFilePath(cacheFilePath); err != nil {
|
||||
return fmt.Errorf("security error: %w", err)
|
||||
}
|
||||
|
||||
cacheBytes, err := os.ReadFile(configPath)
|
||||
cacheBytes, err := os.ReadFile(cacheFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read cache file from %s: %w", configPath, err)
|
||||
return fmt.Errorf("could not read cache file from %s: %w", cacheFilePath, err)
|
||||
}
|
||||
|
||||
// mining.SystemInfo{} // matches what doctor.go writes to the cache file
|
||||
// mining.SystemInfo{} matches the JSON shape that `mining doctor` writes to /home/alice/.config/lethean-desktop/miners/config.json.
|
||||
var systemInfo mining.SystemInfo
|
||||
if err := json.Unmarshal(cacheBytes, &systemInfo); err != nil {
|
||||
return fmt.Errorf("could not parse cache file: %w", err)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue