Skip to content

Commit

Permalink
Merge pull request #129 from algorandfoundation/v1.2.0
Browse files Browse the repository at this point in the history
v1.2.0
  • Loading branch information
tasosbit authored Jan 23, 2025
2 parents 660c139 + f852911 commit dca2ed2
Show file tree
Hide file tree
Showing 32 changed files with 596 additions and 245 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ nodekit [flags]
```
-d, --datadir string Data directory for the node
-h, --help help for nodekit
-n, --no-incentives Disable setting incentive eligibility fees
```

### SEE ALSO
Expand All @@ -39,7 +40,7 @@ nodekit [flags]
* [nodekit uninstall](/man/nodekit_uninstall.md) - Uninstall the node daemon
* [nodekit upgrade](/man/nodekit_upgrade.md) - Upgrade the node daemon

###### Auto generated by spf13/cobra on 7-Jan-2025
###### Auto generated by spf13/cobra on 22-Jan-2025

### Installing

Expand Down
144 changes: 89 additions & 55 deletions cmd/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@ import (
"time"

"github.com/algorandfoundation/nodekit/api"
cmdutils "github.com/algorandfoundation/nodekit/cmd/utils"
"github.com/algorandfoundation/nodekit/cmd/utils/explanations"
"github.com/algorandfoundation/nodekit/internal/algod"
"github.com/algorandfoundation/nodekit/internal/algod/utils"
"github.com/algorandfoundation/nodekit/internal/system"
"github.com/algorandfoundation/nodekit/ui"
"github.com/algorandfoundation/nodekit/ui/app"
"github.com/algorandfoundation/nodekit/ui/bootstrap"
"github.com/algorandfoundation/nodekit/ui/style"
Expand All @@ -22,6 +19,11 @@ import (
"github.com/spf13/cobra"
)

const CheckAlgodInterval = 10 * time.Second
const CheckAlgodTimeout = 2 * time.Minute

var CatchpointLagThreshold int = 30_000

// bootstrapCmdShort provides a brief description of the "bootstrap" command to initialize a fresh Algorand node.
var bootstrapCmdShort = "Initialize a fresh node"

Expand All @@ -45,13 +47,56 @@ This is the beginning of your adventure into running an Algorand node!
`

var FailedToAutoStartMessage = "Failed to start Algorand automatically."

// bootstrapCmd defines the "debug" command used to display diagnostic information for developers, including debug data.
var bootstrapCmd = &cobra.Command{
Use: "bootstrap",
Short: bootstrapCmdShort,
Long: bootstrapCmdLong,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
var client *api.ClientWithResponses
// Create the Bootstrap TUI
model := bootstrap.NewModel()
log.Warn(style.Yellow.Render(explanations.SudoWarningMsg))
// Try to launch the TUI if it's already running and configured
if algod.IsInitialized() {
// Parse the data directory
dir, err := algod.GetDataDir("")
if err != nil {
log.Fatal(err)
}

// 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)
}

// Fetch the latest status
var resp *api.GetStatusResponse
resp, err = client.GetStatusWithResponse(context.Background())
// This should not happen, we waited for a status already
if err != nil {
log.Fatal(err)
}
if resp.StatusCode() != 200 {
log.Fatal(fmt.Sprintf("Failed to connect to the node at %s", dir))
}

// Execute the TUI if we are caught up.
// TODO: check the delta to see if it is necessary,
if resp.JSON200.CatchupTime == 0 {
err = runTUI(RootCmd, dir, false)
if err != nil {
log.Fatal(err)
}
return nil
}
}

// Exit the application in an invalid state
if algod.IsInstalled() && !algod.IsService() {
dataDir, _ := algod.GetDataDir("")
Expand All @@ -63,8 +108,7 @@ var bootstrapCmd = &cobra.Command{
log.Fatal("invalid state, exiting")
}

ctx := context.Background()
httpPkg := new(api.HttpPkg)
// Render the welcome text
r, _ := glamour.NewTermRenderer(
glamour.WithAutoStyle(),
)
Expand All @@ -75,11 +119,25 @@ var bootstrapCmd = &cobra.Command{
}
fmt.Println(out)

model := bootstrap.NewModel()
// Ensure it the service is started,
// in this case we won't be able to query state without the node running
if algod.IsInstalled() && algod.IsService() && !algod.IsRunning() {
log.Debug("Algorand is installed, but not running. Attempting to start it automatically.")
log.Warn(style.Yellow.Render(explanations.SudoWarningMsg))
err := algod.Start()
if err != nil {
log.Error(FailedToAutoStartMessage)
log.Fatal(err)
}

}

// Prefill questions
if algod.IsInstalled() {
model.BootstrapMsg.Install = false
model.Question = bootstrap.CatchupQuestion
}
// Run the Bootstrap TUI
p := tea.NewProgram(model)
var msg *app.BootstrapMsg
go func() {
Expand All @@ -92,33 +150,44 @@ var bootstrapCmd = &cobra.Command{
}
}
}()

if _, err := p.Run(); err != nil {
log.Fatal(err)
}

// If the pointer is empty, return (should not happen)
if msg == nil {
return nil
}

// User Answer for Install Question
if msg.Install {
log.Warn(style.Yellow.Render(explanations.SudoWarningMsg))

// Run the installer
err := algod.Install()
if err != nil {
return err
}

// Wait for algod
time.Sleep(10 * time.Second)
// 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() {
log.Info(style.Green.Render("Starting Algod 🚀"))
log.Warn(style.Yellow.Render(explanations.SudoWarningMsg))
err := algod.Start()
if err != nil {
log.Fatal(err)
Expand All @@ -128,17 +197,18 @@ var bootstrapCmd = &cobra.Command{
}
}

// Find the data directory automatically
dataDir, err := algod.GetDataDir("")
// Wait for the client to respond
client, err = algod.WaitForClient(context.Background(), dataDir, CheckAlgodInterval, CheckAlgodTimeout)
if err != nil {
return err
}
// Create the client
client, err := algod.GetClient(dataDir)
if err != nil {
return err
log.Fatal(err)
}

// User answer for catchup question
if msg.Catchup {
ctx := context.Background()
httpPkg := new(api.HttpPkg)
network, err := utils.GetNetworkFromDataDir(dataDir)
if err != nil {
return err
Expand All @@ -151,51 +221,15 @@ var bootstrapCmd = &cobra.Command{
log.Info(style.Green.Render("Latest Catchpoint: " + catchpoint))
}

// Start catchup
res, _, err := algod.StartCatchup(ctx, client, catchpoint, nil)
// Start catchup with round threshold
res, _, err := algod.StartCatchup(ctx, client, catchpoint, &api.StartCatchupParams{Min: &CatchpointLagThreshold})
if err != nil {
log.Fatal(err)
}
log.Info(style.Green.Render(res))

}

t := new(system.Clock)
// Fetch the state and handle any creation errors
state, stateResponse, err := algod.NewStateModel(ctx, client, httpPkg)
cmdutils.WithInvalidResponsesExplanations(err, stateResponse, cmd.UsageString())
cobra.CheckErr(err)

// Construct the TUI Model from the State
m, err := ui.NewViewportViewModel(state, client)
cobra.CheckErr(err)

// Construct the TUI Application
p = tea.NewProgram(
m,
tea.WithAltScreen(),
tea.WithFPS(120),
)

// Watch for State Updates on a separate thread
// TODO: refactor into context aware watcher without callbacks
go func() {
state.Watch(func(status *algod.StateModel, err error) {
if err == nil {
p.Send(state)
}
if err != nil {
p.Send(state)
p.Send(err)
}
}, ctx, t)
}()

// Execute the TUI Application
_, err = p.Run()
if err != nil {
log.Fatal(err)
}
return nil
return runTUI(RootCmd, dataDir, false)
},
}
17 changes: 14 additions & 3 deletions cmd/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cmd
import (
"encoding/json"
"fmt"
"os/exec"

cmdutils "github.com/algorandfoundation/nodekit/cmd/utils"
"github.com/algorandfoundation/nodekit/cmd/utils/explanations"
"github.com/algorandfoundation/nodekit/internal/algod"
Expand All @@ -12,12 +14,13 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/log"
"github.com/spf13/cobra"
"os/exec"
"golang.org/x/sys/unix"
)

// DebugInfo represents diagnostic information about
// the Algod service, path availability, and related metadata.
type DebugInfo struct {
Version string `json:"version"`

// InPath indicates whether the `algod` command-line tool is available in the system's executable path.
InPath bool `json:"inPath"`
Expand Down Expand Up @@ -72,11 +75,19 @@ var debugCmd = cmdutils.WithAlgodFlags(&cobra.Command{
return err
}
folderDebug, err := utils.ToDataFolderConfig(dataDir)
folderDebug.Token = folderDebug.Token[:3] + "..."
if err != nil {
return err
folderDebug.Token = fmt.Sprint(err)
} else {
folderDebug.Token = folderDebug.Token[:3] + "..."
}

var stat unix.Statfs_t
unix.Statfs(dataDir, &stat)
bytesFree := stat.Bavail * uint64(stat.Bsize)
folderDebug.BytesFree = fmt.Sprintf("%d bytes (%d MB)", bytesFree, bytesFree/1024/1024)

info := DebugInfo{
Version: cmd.Root().Version,
InPath: system.CmdExists("algod"),
IsRunning: algod.IsRunning(),
IsService: algod.IsService(),
Expand Down
Loading

0 comments on commit dca2ed2

Please sign in to comment.