Repository: algorandfoundation/nodekit (language: go)

Issue:
🐛 wait for node timeout not working
The timeout when waiting for a node to spin up does not work

You can test this by installing on a system without systemd, like dockerized ubuntu 

**To Reproduce**
Steps to reproduce the behavior:
1. Run `nodekit bootstrap` inside a docker ubuntu:24.04
2. Press `Y` and `Y` for install/fast catchup
3. Process hangs indefinitely after installation while waiting for node.

**Expected behavior**
Waiting for the node to spin up should time out after 2 minutes


Discussion / hints:

Fix patch:
diff --git a/cmd/bootstrap.go b/cmd/bootstrap.go
index 988bcac1..4808427f 100644
--- a/cmd/bootstrap.go
+++ b/cmd/bootstrap.go
@@ -3,8 +3,6 @@ package cmd
 import (
 	"context"
 	"fmt"
-	"time"
-
 	"github.com/algorandfoundation/nodekit/api"
 	"github.com/algorandfoundation/nodekit/cmd/utils/explanations"
 	"github.com/algorandfoundation/nodekit/internal/algod"
@@ -17,6 +15,7 @@ import (
 	"github.com/charmbracelet/lipgloss"
 	"github.com/charmbracelet/log"
 	"github.com/spf13/cobra"
+	"time"
 )
 
 const CheckAlgodInterval = 10 * time.Second
@@ -69,7 +68,6 @@ var bootstrapCmd = &cobra.Command{
 			}
 
 			// Wait for the client to respond
-			log.Warn(style.Yellow.Render("Waiting for the node to start..."))
 			client, err = algod.WaitForClient(context.Background(), dir, CheckAlgodInterval, CheckAlgodTimeout)
 			if err != nil {
 				log.Fatal(err)
@@ -168,22 +166,6 @@ var bootstrapCmd = &cobra.Command{
 			if err != nil {
 				return err
 			}
-
-			// Parse the data directory
-			dir, err := algod.GetDataDir("")
-			if err != nil {
-				log.Fatal(err)
-			}
-
-			// Wait for the client to respond
-			client, err = algod.WaitForClient(context.Background(), dir, CheckAlgodInterval, CheckAlgodTimeout)
-			if err != nil {
-				log.Fatal(err)
-			}
-
-			if !algod.IsRunning() {
-				log.Fatal("algod is not running. Something went wrong with installation")
-			}
 		} else {
 			// This should not happen but just in case, ensure it is running
 			if !algod.IsRunning() {
@@ -192,19 +174,27 @@ var bootstrapCmd = &cobra.Command{
 				if err != nil {
 					log.Fatal(err)
 				}
-				log.Info(style.Green.Render("Algorand started successfully 🎉"))
-				time.Sleep(2 * time.Second)
 			}
 		}
 
-		// Find the data directory automatically
+		// Parse the data directory
 		dataDir, err := algod.GetDataDir("")
+		if err != nil {
+			log.Fatal(err)
+		}
+
 		// Wait for the client to respond
 		client, err = algod.WaitForClient(context.Background(), dataDir, CheckAlgodInterval, CheckAlgodTimeout)
 		if err != nil {
 			log.Fatal(err)
 		}
 
+		if !algod.IsRunning() {
+			log.Fatal("algod is not running. Something went wrong with installation")
+		}
+
+		log.Info(style.Green.Render("Algorand node started successfully 🎉"))
+
 		// User answer for catchup question
 		if msg.Catchup {
 			ctx := context.Background()
diff --git a/cmd/debug.go b/cmd/debug.go
index 9f8ff2ad..843a9cb0 100644
--- a/cmd/debug.go
+++ b/cmd/debug.go
@@ -90,7 +90,7 @@ var debugCmd = cmdutils.WithAlgodFlags(&cobra.Command{
 		folderDebug, err := utils.ToDataFolderConfig(dataDir)
 		if err != nil {
 			folderDebug.Token = fmt.Sprint(err)
-		} else {
+		} else if len(folderDebug.Token) > 3 {
 			folderDebug.Token = folderDebug.Token[:3] + "..."
 		}
 
diff --git a/cmd/telemetry/enable/nodely.go b/cmd/telemetry/enable/nodely.go
index 651c0c5c..f0af0c50 100644
--- a/cmd/telemetry/enable/nodely.go
+++ b/cmd/telemetry/enable/nodely.go
@@ -85,6 +85,8 @@ var nodelyCmd = cmdutils.WithAlgodFlags(&cobra.Command{
 			if err != nil {
 				log.Fatal(err)
 			}
+
+			log.Info("Telemetry enabled. To see your Nodely dashboard URL, run:\n    ./nodekit telemetry status")
 		}
 	},
 }, &dataDir)
diff --git a/internal/algod/client.go b/internal/algod/client.go
index 4f2ffd63..74b10643 100644
--- a/internal/algod/client.go
+++ b/internal/algod/client.go
@@ -3,8 +3,10 @@ package algod
 import (
 	"context"
 	"errors"
+	"fmt"
 	"github.com/algorandfoundation/nodekit/api"
 	"github.com/algorandfoundation/nodekit/internal/algod/utils"
+	"github.com/charmbracelet/log"
 	"github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider"
 	"os"
 	"path/filepath"
@@ -13,7 +15,7 @@ import (
 )
 
 const InvalidDataDirMsg = "invalid data directory"
-const ClientTimeoutMsg = "the client has timed out"
+const ClientTimeoutMsg = "timed out while waiting for the node"
 
 func GetDataDir(dataDir string) (string, error) {
 	envDataDir := os.Getenv("ALGORAND_DATA")
@@ -64,6 +66,7 @@ func GetClient(dataDir string) (*api.ClientWithResponses, error) {
 func WaitForClient(ctx context.Context, dataDir string, interval time.Duration, timeout time.Duration) (*api.ClientWithResponses, error) {
 	var client *api.ClientWithResponses
 	var err error
+	log.Info(fmt.Sprintf("Waiting for the node (up to %s)", timeout))
 	dataDir, err = GetDataDir(dataDir)
 	if err != nil {
 		return client, err
@@ -78,6 +81,7 @@ func WaitForClient(ctx context.Context, dataDir string, interval time.Duration,
 		}
 	}
 	// Wait for client to respond
+	timeoutTimer := time.After(timeout)
 	for {
 		select {
 		case <-ctx.Done():
@@ -91,9 +95,8 @@ func WaitForClient(ctx context.Context, dataDir string, interval time.Duration,
 					return client, nil
 				}
 			}
-		case <-time.After(timeout):
+		case <-timeoutTimer:
 			return client, errors.New(ClientTimeoutMsg)
 		}
-
 	}
 }
diff --git a/internal/algod/linux/linux.go b/internal/algod/linux/linux.go
index ec96c154..4eb0a5f2 100644
--- a/internal/algod/linux/linux.go
+++ b/internal/algod/linux/linux.go
@@ -8,7 +8,9 @@ import (
 	"github.com/charmbracelet/log"
 	"os"
 	"os/exec"
+	"os/user"
 	"runtime"
+	"strconv"
 	"strings"
 	"text/template"
 )
@@ -24,6 +26,20 @@ type Algod struct {
 	DataDirectoryPath string
 }
 
+func hasConflictingUser() bool {
+	if runtime.GOOS != "linux" {
+		return false
+	}
+	algorandUser, _ := user.Lookup("algorand")
+	if algorandUser != nil {
+		uid, _ := strconv.Atoi(algorandUser.Uid)
+		if uid >= 1000 {
+			return true
+		}
+	}
+	return false
+}
+
 // InstallRequirements generates installation commands for "sudo" based on the detected package manager and system state.
 func InstallRequirements() system.CmdsList {
 	var cmds system.CmdsList
@@ -47,7 +63,12 @@ func InstallRequirements() system.CmdsList {
 
 // Install installs Algorand development tools or node software depending on the package manager.
 func Install() error {
+	if hasConflictingUser() {
+		return fmt.Errorf("Your system has a user called \"algorand\". The algorand node requires the \"algorand\" username for internal usage. Rename or remove the algorand user to continue.")
+	}
+
 	log.Info("Installing Algod on Linux")
+
 	// Based off of https://developer.algorand.org/docs/run-a-node/setup/install/#installation-with-a-package-manager
 	if system.CmdExists("apt-get") { // On some Debian systems we use apt-get
 		log.Info("Installing with apt-get")
diff --git a/internal/algod/mac/mac.go b/internal/algod/mac/mac.go
index 8e68559c..a8125919 100644
--- a/internal/algod/mac/mac.go
+++ b/internal/algod/mac/mac.go
@@ -36,7 +36,7 @@ func IsService() bool {
 // configures necessary directories, and ensures it
 // runs as a background service.
 func Install() error {
-	log.Info("Installing Algod on macOS...")
+	log.Info("Installing Algod on macOS")
 
 	// Homebrew is our package manager of choice
 	if !system.CmdExists("brew") {
diff --git a/internal/algod/participation/participation.go b/internal/algod/participation/participation.go
index d7b6f94a..ca2545a7 100644
--- a/internal/algod/participation/participation.go
+++ b/internal/algod/participation/participation.go
@@ -77,6 +77,7 @@ func GenerateKeys(
 	}
 
 	// 😠 - Zero 2024
+	timeoutTimer := time.After(20 * time.Minute)
 	for {
 		select {
 		case <-ctx.Done():
@@ -96,7 +97,7 @@ func GenerateKeys(
 					return &k, nil
 				}
 			}
-		case <-time.After(20 * time.Minute):
+		case <-timeoutTimer:
 			return nil, errors.New("timeout waiting for key to be created")
 		}
 	}


Test patch: