From 4b80bd454b198984f56727cd6a3f85ba17dc2893 Mon Sep 17 00:00:00 2001 From: HashMapsData2Value <83883690+HashMapsData2Value@users.noreply.github.com> Date: Fri, 7 Feb 2025 20:50:37 +0100 Subject: [PATCH 1/4] chore: brew clarification --- internal/algod/mac/mac.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/internal/algod/mac/mac.go b/internal/algod/mac/mac.go index 09909552..8e68559c 100644 --- a/internal/algod/mac/mac.go +++ b/internal/algod/mac/mac.go @@ -4,10 +4,6 @@ import ( "bytes" "errors" "fmt" - "github.com/algorandfoundation/nodekit/internal/algod/utils" - "github.com/algorandfoundation/nodekit/internal/system" - "github.com/charmbracelet/log" - "github.com/spf13/cobra" "io" "net/http" "os" @@ -15,13 +11,18 @@ import ( "path/filepath" "strings" "text/template" + + "github.com/algorandfoundation/nodekit/internal/algod/utils" + "github.com/algorandfoundation/nodekit/internal/system" + "github.com/charmbracelet/log" + "github.com/spf13/cobra" ) // MustBeServiceMsg is an error message indicating that a service must be installed to manage it. const MustBeServiceMsg = "service must be installed to be able to manage it" // HomeBrewNotFoundMsg is the error message returned when Homebrew is not detected on the system during execution. -const HomeBrewNotFoundMsg = "homebrew is not installed. please install Homebrew and try again" +const HomeBrewNotFoundMsg = "brew not found. please go to https://brew.sh to install Homebrew before trying again" // IsService check if Algorand service has been created with launchd (macOS) // Note that it needs to be run in super-user privilege mode to From 5c60f9b5646848f2fc025ce88fd71f038476a2d0 Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Thu, 6 Feb 2025 13:01:08 +0100 Subject: [PATCH 2/4] chore: installer env vars enabling unattended installation --- install.sh | 15 ++++++++++----- unattended.sh | 13 +++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) create mode 100755 unattended.sh diff --git a/install.sh b/install.sh index 34a0eff5..9a361bf1 100755 --- a/install.sh +++ b/install.sh @@ -54,7 +54,8 @@ prompt_default_no() { if [ -f nodekit ]; then warn "A nodekit file already exists in the current directory." - if prompt_default_no "Do you want to upgrade it to the latest nodekit?"; then + # Set the NODEKIT_FORCE_INSTALL environment variable to anything in order to force upgrading nodekit without prompting + if [[ -n "${NODEKIT_FORCE_INSTALL-}" ]] || prompt_default_no "Do you want to upgrade it to the latest nodekit?"; then rm nodekit else info "Not upgrading nodekit.\n\nYou can run nodekit with:\n\n./nodekit\n\nOr start the installer with:\n\n./nodekit bootstrap" @@ -103,8 +104,12 @@ trap - exit success "Downloaded: ${Bold_Green}${target} as nodekit ๐ŸŽ‰${Reset}" info "Explore all nodekit options with:" echo "./nodekit --help" -echo "" -info "Starting nodekit bootstrap" -echo "./nodekit bootstrap" -./nodekit bootstrap +# Set the NODEKIT_SKIP_BOOTSTRAP environment variable to anything in order to skip bootstrap +# Useful for non-interactive setup +if [[ -z "${NODEKIT_SKIP_BOOTSTRAP-}" ]]; then + echo "" + info "Starting nodekit bootstrap" + echo "./nodekit bootstrap" + ./nodekit bootstrap +fi diff --git a/unattended.sh b/unattended.sh new file mode 100755 index 00000000..c09f9635 --- /dev/null +++ b/unattended.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# Install nodekit, replacing any previous nodekit (FORCE_INSTALL) and skipping the interactive bootstrap +wget -qO- https://nodekit.run/install.sh | NODEKIT_FORCE_INSTALL=1 NODEKIT_SKIP_BOOTSTRAP=1 bash + +# Install node +./nodekit install -f + +# Wait a bit for connections to be established +sleep 2m + +# Start a fast catchup +./nodekit catchup start From a2eecd5607109159c8f1e6be0b46a2711ad3fa80 Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Tue, 11 Feb 2025 12:58:46 +0100 Subject: [PATCH 3/4] fix: enable QR signing --- ui/modals/transaction/model.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/modals/transaction/model.go b/ui/modals/transaction/model.go index e03d3dbb..fcd42384 100644 --- a/ui/modals/transaction/model.go +++ b/ui/modals/transaction/model.go @@ -43,7 +43,7 @@ func (m ViewModel) FormatedAddress() string { } func (m ViewModel) IsQREnabled() bool { - return m.State.Status.Network == "testnet-v1.0" // || m.State.Status.Network == "mainnet-v1.0" + return m.State.Status.Network == "testnet-v1.0" || m.State.Status.Network == "mainnet-v1.0" } // New creates and instance of the ViewModel with a default controls.Model From 9183a54ea86e69a93b9e969abe57f2937fe2bf7e Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Tue, 11 Feb 2025 13:57:15 +0100 Subject: [PATCH 4/4] feat: show nodekit version within the TUI Co-authored-by: Michael Feher --- cmd/bootstrap.go | 4 ++-- cmd/root.go | 6 ++--- internal/algod/state.go | 6 ++++- ui/internal/test/state.go | 1 + ui/status.go | 21 +++++++++------- ui/status_test.go | 3 +++ ui/style/style.go | 24 +++++++++++++++++++ .../Test_StatusSnapshot/Syncing.golden | 2 +- 8 files changed, 52 insertions(+), 15 deletions(-) diff --git a/cmd/bootstrap.go b/cmd/bootstrap.go index 0198d070..988bcac1 100644 --- a/cmd/bootstrap.go +++ b/cmd/bootstrap.go @@ -89,7 +89,7 @@ var bootstrapCmd = &cobra.Command{ // 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) + err = runTUI(RootCmd, dir, false, RootCmd.Version) if err != nil { log.Fatal(err) } @@ -230,6 +230,6 @@ var bootstrapCmd = &cobra.Command{ } - return runTUI(RootCmd, dataDir, false) + return runTUI(RootCmd, dataDir, false, RootCmd.Version) }, } diff --git a/cmd/root.go b/cmd/root.go index d1432fc4..30bd61e2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -56,7 +56,7 @@ var ( }, Run: func(cmd *cobra.Command, args []string) { log.SetOutput(cmd.OutOrStdout()) - err := runTUI(cmd, algodData, IncentivesDisabled) + err := runTUI(cmd, algodData, IncentivesDisabled, cmd.Version) if err != nil { log.Fatal(err) } @@ -116,7 +116,7 @@ func Execute(version string, needsUpgrade bool) error { return RootCmd.Execute() } -func runTUI(cmd *cobra.Command, dataDir string, incentivesFlag bool) error { +func runTUI(cmd *cobra.Command, dataDir string, incentivesFlag bool, version string) error { if cmd == nil { return fmt.Errorf("cmd is nil") } @@ -128,7 +128,7 @@ func runTUI(cmd *cobra.Command, dataDir string, incentivesFlag bool) error { cobra.CheckErr(err) // Fetch the state and handle any creation errors - state, stateResponse, err := algod.NewStateModel(ctx, client, httpPkg, incentivesFlag) + state, stateResponse, err := algod.NewStateModel(ctx, client, httpPkg, incentivesFlag, version) utils.WithInvalidResponsesExplanations(err, stateResponse, cmd.UsageString()) cobra.CheckErr(err) diff --git a/internal/algod/state.go b/internal/algod/state.go index e0ffab00..f22cdd0b 100644 --- a/internal/algod/state.go +++ b/internal/algod/state.go @@ -13,6 +13,9 @@ import ( // including status, metrics, accounts, keys, and other configurations. type StateModel struct { + // Version indicates the version of the application. + Version string + // Status represents the current status of the algod node, // including network state and round information. Status Status @@ -56,7 +59,7 @@ type StateModel struct { // NewStateModel initializes and returns a new StateModel instance // along with an API response and potential error. -func NewStateModel(ctx context.Context, client api.ClientWithResponsesInterface, httpPkg api.HttpPkgInterface, incentivesDisabled bool) (*StateModel, api.ResponseInterface, error) { +func NewStateModel(ctx context.Context, client api.ClientWithResponsesInterface, httpPkg api.HttpPkgInterface, incentivesDisabled bool, version string) (*StateModel, api.ResponseInterface, error) { // Preload the node status status, response, err := NewStatus(ctx, client, httpPkg) if err != nil { @@ -79,6 +82,7 @@ func NewStateModel(ctx context.Context, client api.ClientWithResponsesInterface, Admin: true, Watching: true, + Version: version, Client: client, HttpPkg: httpPkg, Context: ctx, diff --git a/ui/internal/test/state.go b/ui/internal/test/state.go index 87835d22..66fc57ce 100644 --- a/ui/internal/test/state.go +++ b/ui/internal/test/state.go @@ -11,6 +11,7 @@ import ( func GetState(client api.ClientWithResponsesInterface) *algod.StateModel { sm := &algod.StateModel{ + Version: "vTest", Status: algod.Status{ State: algod.StableState, Version: "v-test", diff --git a/ui/status.go b/ui/status.go index a7a4d822..d65f8e88 100644 --- a/ui/status.go +++ b/ui/status.go @@ -113,14 +113,19 @@ func (m StatusViewModel) View() string { row3 := lipgloss.JoinHorizontal(lipgloss.Left, beginning, middle, end) - return style.WithTitle("Status", style.ApplyBorder(max(0, size-2), 5, "5").Render( - lipgloss.JoinVertical(lipgloss.Left, - row1, - "", - style.Cyan.Render(" -- "+strconv.Itoa(m.Data.Metrics.Window)+" round average --"), - row2, - row3, - ))) + return style.WithTitles( + "( "+style.Red.Render(fmt.Sprintf("Nodekit-%s", m.Data.Version))+" )", + lipgloss.NewStyle().Foreground(lipgloss.Color("5")).Render("Status"), + style.ApplyBorder(max(0, size-2), 5, "5").Render( + lipgloss.JoinVertical(lipgloss.Left, + row1, + "", + style.Cyan.Render(" -- "+strconv.Itoa(m.Data.Metrics.Window)+" round average --"), + row2, + row3, + ), + ), + ) } // MakeStatusViewModel constructs the model to be used in a tea.Program diff --git a/ui/status_test.go b/ui/status_test.go index 2822507b..5e6b2dfc 100644 --- a/ui/status_test.go +++ b/ui/status_test.go @@ -15,6 +15,7 @@ import ( var statusViewSnapshots = map[string]StatusViewModel{ "Syncing": { Data: &algod.StateModel{ + Version: "v0.0.0-test", Status: algod.Status{ LastRound: 1337, NeedsUpdate: true, @@ -31,6 +32,7 @@ var statusViewSnapshots = map[string]StatusViewModel{ }, "Hidden": { Data: &algod.StateModel{ + Version: "v0.0.0-test", Status: algod.Status{ LastRound: 1337, NeedsUpdate: true, @@ -47,6 +49,7 @@ var statusViewSnapshots = map[string]StatusViewModel{ }, "Loading": { Data: &algod.StateModel{ + Version: "v0.0.0-test", Status: algod.Status{ LastRound: 1337, NeedsUpdate: true, diff --git a/ui/style/style.go b/ui/style/style.go index a609666b..fdbac3de 100644 --- a/ui/style/style.go +++ b/ui/style/style.go @@ -59,6 +59,30 @@ func WithTitle(title string, view string) string { } return view } +func WithTitles(leftText string, rightText string, view string) string { + if leftText == "" || rightText == "" { + return view + } + + pad := 4 + controlWidth := lipgloss.Width(leftText) + lipgloss.Width(rightText) + + lines := strings.Split(view, "\n") + + if lipgloss.Width(view) >= controlWidth+(2*pad) { + line := lines[0] + lineWidth := lipgloss.Width(line) + lineLeft := ansi.Truncate(line, pad, "") + leftText + lineRight := rightText + TruncateLeft(line, lineWidth-pad) + + midTemplate := TruncateLeft(line, pad) + midLen := lineWidth - lipgloss.Width(lineLeft) - lipgloss.Width(lineRight) + midLine := ansi.Truncate(midTemplate, midLen, "") + lines[0] = lineLeft + midLine + lineRight + } + return strings.Join(lines, "\n") +} + func WithControls(nav string, view string) string { if nav == "" { return view diff --git a/ui/testdata/Test_StatusSnapshot/Syncing.golden b/ui/testdata/Test_StatusSnapshot/Syncing.golden index 0004a66d..063cde3e 100644 --- a/ui/testdata/Test_StatusSnapshot/Syncing.golden +++ b/ui/testdata/Test_StatusSnapshot/Syncing.golden @@ -1,4 +1,4 @@ -โ•ญโ”€โ”€Statusโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ•ญโ”€โ”€โ”€( Nodekit-v0.0.0-test )โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€Statusโ”€โ”€โ”€โ•ฎ โ”‚ Latest Round: 1337 SYNCING โ”‚ โ”‚ โ”‚ โ”‚ -- 0 round average -- โ”‚