From 07e16db4dd9cfd1f4b64ee2ac48b724adec578f5 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Mon, 23 Dec 2024 08:56:45 -0500 Subject: [PATCH 01/26] feat: bootstrap command --- cmd/node/bootstrap.go | 96 ++++++++++++++++++++++++++++++++ cmd/node/node.go | 1 + go.mod | 11 ++++ go.sum | 31 +++++++++++ internal/algod/utils/utils.go | 8 +++ ui/app/bootstrap.go | 19 +++++++ ui/app/emitter.go | 16 ++++++ ui/bootstrap/model.go | 101 ++++++++++++++++++++++++++++++++++ ui/viewport.go | 11 ++-- 9 files changed, 289 insertions(+), 5 deletions(-) create mode 100644 cmd/node/bootstrap.go create mode 100644 ui/app/bootstrap.go create mode 100644 ui/app/emitter.go create mode 100644 ui/bootstrap/model.go diff --git a/cmd/node/bootstrap.go b/cmd/node/bootstrap.go new file mode 100644 index 00000000..9d1e1f2f --- /dev/null +++ b/cmd/node/bootstrap.go @@ -0,0 +1,96 @@ +package node + +import ( + "fmt" + "github.com/algorandfoundation/algorun-tui/internal/algod" + "github.com/algorandfoundation/algorun-tui/ui/app" + "github.com/algorandfoundation/algorun-tui/ui/bootstrap" + "github.com/algorandfoundation/algorun-tui/ui/style" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/glamour" + "github.com/charmbracelet/log" + "github.com/spf13/cobra" + "time" +) + +var in = `# Welcome! + +This is the beginning of your adventure into running the an Algorand node! + +Morbi mauris quam, ornare ac commodo et, posuere id sem. Nulla id condimentum mauris. In vehicula sit amet libero vitae interdum. Nullam ac massa in erat volutpat sodales. Integer imperdiet enim cursus, ullamcorper tortor vel, imperdiet diam. Maecenas viverra ex iaculis, vehicula ligula quis, cursus lorem. Mauris nec nunc feugiat tortor sollicitudin porta ac quis turpis. Nam auctor hendrerit metus et pharetra. + +` + +// bootstrapCmd defines the "debug" command used to display diagnostic information for developers, including debug data. +var bootstrapCmd = &cobra.Command{ + Use: "bootstrap", + Short: "Initialize a fresh node. Alias for install, catchup, and start.", + Long: "Text", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Print(style.Purple(style.BANNER)) + out, err := glamour.Render(in, "dark") + if err != nil { + return err + } + fmt.Println(out) + + model := bootstrap.NewModel() + p := tea.NewProgram(model) + var msg *app.BootstrapMsg + go func() { + for { + val := <-model.Outside + switch val.(type) { + case app.BootstrapMsg: + msgVal := val.(app.BootstrapMsg) + msg = &msgVal + } + } + }() + + if _, err := p.Run(); err != nil { + log.Fatal(err) + } + if msg == nil { + return nil + } + + log.Warn(style.Yellow.Render(SudoWarningMsg)) + if msg.Install && !algod.IsInstalled() { + err := algod.Install() + if err != nil { + return err + } + } + + // Wait for algod + time.Sleep(10 * time.Second) + + if !algod.IsRunning() { + log.Fatal("algod is not running") + } + + //if msg.Catchup { + // ctx := context.Background() + // httpPkg := new(api.HttpPkg) + // client, err := algod.GetClient(endpoint, token) + // + // // Get the latest catchpoint + // catchpoint, _, err := algod.GetLatestCatchpoint(httpPkg, status.Network) + // if err != nil && err.Error() == api.InvalidNetworkParamMsg { + // log.Fatal("This network does not support fast-catchup.") + // } else { + // log.Info(style.Green.Render("Latest Catchpoint: " + catchpoint)) + // } + // + // // Start catchup + // res, _, err := algod.StartCatchup(ctx, client, catchpoint, nil) + // if err != nil { + // log.Fatal(err) + // } + // + //} + return nil + }, +} diff --git a/cmd/node/node.go b/cmd/node/node.go index 5280d8a2..3d378042 100644 --- a/cmd/node/node.go +++ b/cmd/node/node.go @@ -84,6 +84,7 @@ func init() { Cmd.AddCommand(upgradeCmd) Cmd.AddCommand(syncCmd) Cmd.AddCommand(debugCmd) + Cmd.AddCommand(bootstrapCmd) Cmd.AddCommand(catchup.Cmd) Cmd.AddCommand(configure.Cmd) } diff --git a/go.mod b/go.mod index 7134e0ea..346c9af0 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/algorandfoundation/algourl v0.0.0-20241023193235-8bbf72ad0b37 github.com/charmbracelet/bubbles v0.20.0 github.com/charmbracelet/bubbletea v1.1.1 + github.com/charmbracelet/glamour v0.8.0 github.com/charmbracelet/lipgloss v0.13.1 github.com/charmbracelet/log v0.4.0 github.com/charmbracelet/x/exp/teatest v0.0.0-20241022174419-46d9bb99a691 @@ -20,9 +21,19 @@ require ( ) require ( + github.com/alecthomas/chroma/v2 v2.14.0 // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dlclark/regexp2 v1.11.0 // indirect + github.com/gorilla/css v1.0.1 // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect + github.com/muesli/reflow v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/yuin/goldmark v1.7.4 // indirect + github.com/yuin/goldmark-emoji v1.0.3 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/term v0.25.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index 0b57cc4a..c896f58d 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,10 @@ github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= +github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= +github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/algorand/go-algorand-sdk/v2 v2.6.0 h1:pfL8lloEi26l6PwAFicmPUguWgKpy1eZZTMlQcci5h0= github.com/algorand/go-algorand-sdk/v2 v2.6.0/go.mod h1:4ayerzjoWChm3kuVhbgFgURTbaYTtlj0c41eP3av5lw= github.com/algorand/go-codec/codec v1.1.10 h1:zmWYU1cp64jQVTOG8Tw8wa+k0VfwgXIPbnDfiVa+5QA= @@ -15,11 +21,15 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= github.com/charmbracelet/bubbletea v1.1.1 h1:KJ2/DnmpfqFtDNVTvYZ6zpPFL9iRCRr0qqKOCvppbPY= github.com/charmbracelet/bubbletea v1.1.1/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4= +github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs= +github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw= github.com/charmbracelet/lipgloss v0.13.1 h1:Oik/oqDTMVA01GetT4JdEC033dNzWoQHdWnHnQmXE2A= github.com/charmbracelet/lipgloss v0.13.1/go.mod h1:zaYVJ2xKSKEnTEEbX6uAHabh2d975RJ+0yfkFpRBz5U= github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= @@ -43,6 +53,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -58,8 +70,12 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= @@ -80,14 +96,19 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg= github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 h1:ykgG34472DWey7TSjd8vIfNykXgjOgYJZoQbKfEeY/Q= @@ -99,6 +120,7 @@ github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xl github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -129,6 +151,11 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= +github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4= +github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -141,6 +168,8 @@ golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= @@ -156,6 +185,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/internal/algod/utils/utils.go b/internal/algod/utils/utils.go index 1ba86af9..e0d7b5d3 100644 --- a/internal/algod/utils/utils.go +++ b/internal/algod/utils/utils.go @@ -67,3 +67,11 @@ func GetExpiresTime(t system.Time, lastRound int, roundTime time.Duration, voteL } return nil } + +func GetTokenFromDataDir(path string) string { + paths := system.FindPathToFile(path, "algod.token") + if len(paths) == 1 { + + } + return "" +} diff --git a/ui/app/bootstrap.go b/ui/app/bootstrap.go new file mode 100644 index 00000000..5b3cea8c --- /dev/null +++ b/ui/app/bootstrap.go @@ -0,0 +1,19 @@ +package app + +import ( + tea "github.com/charmbracelet/bubbletea" +) + +type BootstrapMsg struct { + Install bool + Catchup bool +} + +type BoostrapSelected BootstrapMsg + +// EmitBootstrapSelection waits for and retrieves a new set of table rows from a given channel. +func EmitBootstrapSelection(selection BoostrapSelected) tea.Cmd { + return func() tea.Msg { + return selection + } +} diff --git a/ui/app/emitter.go b/ui/app/emitter.go new file mode 100644 index 00000000..fa4514cd --- /dev/null +++ b/ui/app/emitter.go @@ -0,0 +1,16 @@ +package app + +import tea "github.com/charmbracelet/bubbletea" + +type Outside chan tea.Msg + +func NewOutside() Outside { + return make(chan tea.Msg) +} + +func (o Outside) Emit(msg tea.Msg) tea.Cmd { + return func() tea.Msg { + o <- msg + return nil + } +} diff --git a/ui/bootstrap/model.go b/ui/bootstrap/model.go new file mode 100644 index 00000000..9c6bd9ba --- /dev/null +++ b/ui/bootstrap/model.go @@ -0,0 +1,101 @@ +package bootstrap + +import ( + "github.com/algorandfoundation/algorun-tui/ui/app" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/glamour" +) + +type Question string + +const ( + InstallQuestion Question = "install" + CatchupQuestion Question = "catchup" + WaitingQuestion Question = "waiting" +) + +const InstallQuestionMsg = `# Installing A Node + +It looks like you're running this for the first time. Would you like to install a node? (Y/n) +` + +const CatchupQuestionMsg = `# Catching Up + +Would you like to preform a fast-catchup? (Y/n) +` + +type Model struct { + Outside app.Outside + BootstrapMsg app.BootstrapMsg + Question Question +} + +func NewModel() Model { + return Model{ + Outside: make(app.Outside), + Question: InstallQuestion, + BootstrapMsg: app.BootstrapMsg{ + Install: false, + Catchup: false, + }, + } +} +func (m Model) Init() tea.Cmd { + return textinput.Blink +} +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + m.Outside.Emit(msg) + if m.Question == WaitingQuestion { + return m, tea.Sequence(m.Outside.Emit(m.BootstrapMsg), tea.Quit) + } + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "y": + { + switch m.Question { + case InstallQuestion: + m.Question = CatchupQuestion + m.BootstrapMsg.Install = true + case CatchupQuestion: + m.BootstrapMsg.Catchup = true + m.Question = WaitingQuestion + return m, app.EmitBootstrapSelection(app.BoostrapSelected(m.BootstrapMsg)) + } + + } + case "n": + { + switch m.Question { + case InstallQuestion: + m.Question = CatchupQuestion + m.BootstrapMsg.Install = true + case CatchupQuestion: + m.Question = WaitingQuestion + m.BootstrapMsg.Catchup = true + case WaitingQuestion: + return m, tea.Sequence(m.Outside.Emit(m.BootstrapMsg), tea.Quit) + } + + } + + case "ctrl+c", "esc", "q": + return m, tea.Quit + } + } + return m, cmd +} + +func (m Model) View() string { + var str string + switch m.Question { + case InstallQuestion: + str = InstallQuestionMsg + case CatchupQuestion: + str = CatchupQuestionMsg + } + msg, _ := glamour.Render(str, "dark") + return msg +} diff --git a/ui/viewport.go b/ui/viewport.go index 584cddca..2ffc5560 100644 --- a/ui/viewport.go +++ b/ui/viewport.go @@ -28,9 +28,10 @@ type ViewportViewModel struct { accountsPage accounts.ViewModel keysPage keys.ViewModel - modal *modal.ViewModel - page app.Page - client api.ClientWithResponsesInterface + outside app.Outside + modal *modal.ViewModel + page app.Page + client api.ClientWithResponsesInterface } // Init is a no-op @@ -223,8 +224,8 @@ func NewViewportViewModel(state *algod.StateModel, client api.ClientWithResponse keysPage: keys.New("", state.ParticipationKeys), // Modal - modal: modal.New("", false, state), - + modal: modal.New("", false, state), + outside: app.NewOutside(), // Current Page page: app.AccountsPage, // RPC client From 6f9b3c6dc4e94c19e90211c4e78f2ae2fe40e613 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Mon, 23 Dec 2024 18:31:18 -0500 Subject: [PATCH 02/26] refactor: to data directory --- .docker/start_all.sh | 16 ---- .docker/start_dev.sh | 49 ----------- .docker/start_empty.sh | 41 --------- .docker/start_fast_catchup.sh | 46 ---------- Dockerfile | 31 ------- cmd/node/bootstrap.go | 92 +++++++++++++++----- cmd/node/catchup/catchup.go | 9 +- cmd/node/catchup/debug.go | 19 +---- cmd/node/catchup/start.go | 19 +---- cmd/node/catchup/stop.go | 19 +---- cmd/node/debug.go | 6 ++ cmd/node/sync.go | 22 +---- cmd/root.go | 27 +----- cmd/utils/config.go | 153 ---------------------------------- cmd/utils/flags.go | 34 ++------ docker-compose.yaml | 19 ----- internal/algod/catchpoint.go | 2 +- internal/algod/client.go | 12 ++- internal/algod/utils/utils.go | 104 ++++++++++++++++++++++- 19 files changed, 209 insertions(+), 511 deletions(-) delete mode 100755 .docker/start_all.sh delete mode 100755 .docker/start_dev.sh delete mode 100755 .docker/start_empty.sh delete mode 100755 .docker/start_fast_catchup.sh delete mode 100644 Dockerfile delete mode 100644 docker-compose.yaml diff --git a/.docker/start_all.sh b/.docker/start_all.sh deleted file mode 100755 index 4bcdb5e6..00000000 --- a/.docker/start_all.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -set -e - -if [ "$DEBUG" = "1" ]; then - set -x -fi - -if [ "$ALGORAND_DATA" != "/algod/data" ]; then - echo "Do not override 'ALGORAND_DATA' environment variable." - exit 1 -fi - -/node/run/start_empty.sh & -/node/run/start_fast_catchup.sh & -/node/run/start_dev.sh \ No newline at end of file diff --git a/.docker/start_dev.sh b/.docker/start_dev.sh deleted file mode 100755 index 64ff6582..00000000 --- a/.docker/start_dev.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash - -set -e - -if [ "$DEBUG" = "1" ]; then - set -x -fi - -if [ "$ALGORAND_DATA" != "/algod/data" ]; then - echo "Do not override 'ALGORAND_DATA' environment variable." - exit 1 -fi - -# Configure the participation node -if [ -d "$ALGORAND_DATA" ]; then - if [ -f "$ALGORAND_DATA/genesis.json" ]; then - if [ "$TOKEN" != "" ]; then - echo "$TOKEN" >"$EMPTY_DATA/algod.token" - fi - if [ "$ADMIN_TOKEN" != "" ]; then - echo "$ADMIN_TOKEN" >"$EMPTY_DATA/algod.admin.token" - fi - algod -o -d "$ALGORAND_DATA" -l "0.0.0.0:8080" - else - sed -i "s/NUM_ROUNDS/${NUM_ROUNDS:-30000}/" "/node/run/template.json" - sed -i "s/\"NetworkName\": \"\"/\"NetworkName\": \"algorun-tui\"/" "/node/run/template.json" - goal network create --noclean -n tuinet -r "${ALGORAND_DATA}/.." -t "/node/run/template.json" - - # Cycle Network - goal network start -r "${ALGORAND_DATA}/.." - goal node stop - - # Update Tokens - if [ "$TOKEN" != "" ]; then - echo "$TOKEN" >"$ALGORAND_DATA/algod.token" - fi - if [ "$ADMIN_TOKEN" != "" ]; then - echo "$ADMIN_TOKEN" >"$ALGORAND_DATA/algod.admin.token" - fi - # Import wallet - goal account import -m "artefact exist coil life turtle edge edge inside punch glance recycle teach melody diet method pause slam dumb race interest amused side learn able heavy" - - algod -o -d "$ALGORAND_DATA" -l "0.0.0.0:8080" - fi - -else - echo "$ALGORAND_DATA" does not exist - exit 1 -fi diff --git a/.docker/start_empty.sh b/.docker/start_empty.sh deleted file mode 100755 index e99ad2ab..00000000 --- a/.docker/start_empty.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -set -e - -if [ "$DEBUG" = "1" ]; then - set -x -fi - -if [ "$ALGORAND_DATA" != "/algod/data" ]; then - echo "Do not override 'ALGORAND_DATA' environment variable." - exit 1 -fi - -EMPTY_DATA=/algod/empty - -# To allow mounting the data directory we need to change permissions -# to our algorand user. The script is initially run as the root user -# in order to change permissions, afterwards the script is re-launched -# as the algorand user. -if [ "$(id -u)" = '0' ]; then - chown -R algorand:algorand $EMPTY_DATA - exec su -p -c "$(readlink -f $0) $@" algorand -fi - - -# Configure the participation node -if [ -d "$EMPTY_DATA" ]; then - if [ "$TOKEN" != "" ]; then - echo "$TOKEN" > "$EMPTY_DATA/algod.token" - fi - if [ "$ADMIN_TOKEN" != "" ]; then - echo "$ADMIN_TOKEN" > "$EMPTY_DATA/algod.admin.token" - fi - cd $EMPTY_DATA - cp "/node/run/genesis/testnet/genesis.json" genesis.json - algocfg profile set --yes -d "$EMPTY_DATA" "participation" - algod -o -d $EMPTY_DATA -l "0.0.0.0:8082" -else - echo $EMPTY_DATA does not exist - exit 1 -fi \ No newline at end of file diff --git a/.docker/start_fast_catchup.sh b/.docker/start_fast_catchup.sh deleted file mode 100755 index 89acc238..00000000 --- a/.docker/start_fast_catchup.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash - -set -e - -if [ "$DEBUG" = "1" ]; then - set -x -fi - -if [ "$ALGORAND_DATA" != "/algod/data" ]; then - echo "Do not override 'ALGORAND_DATA' environment variable." - exit 1 -fi - -FAST_CATCHUP_DATA=/algod/fast-catchup - -# To allow mounting the data directory we need to change permissions -# to our algorand user. The script is initially run as the root user -# in order to change permissions, afterwards the script is re-launched -# as the algorand user. -if [ "$(id -u)" = '0' ]; then - chown -R algorand:algorand $FAST_CATCHUP_DATA - exec su -p -c "$(readlink -f $0) $@" algorand -fi - -function catchup() { - sleep 5 - goal node catchup --force --min 1000000 -d $FAST_CATCHUP_DATA -} - -# Configure the participation node -if [ -d "$FAST_CATCHUP_DATA" ]; then - if [ "$TOKEN" != "" ]; then - echo "$TOKEN" > "$FAST_CATCHUP_DATA/algod.token" - fi - if [ "$ADMIN_TOKEN" != "" ]; then - echo "$ADMIN_TOKEN" > "$FAST_CATCHUP_DATA/algod.admin.token" - fi - cd $FAST_CATCHUP_DATA - cp "/node/run/genesis/testnet/genesis.json" genesis.json - algocfg profile set --yes -d "$FAST_CATCHUP_DATA" "participation" - catchup & - algod -o -d $FAST_CATCHUP_DATA -l "0.0.0.0:8081" -else - echo $FAST_CATCHUP_DATA does not exist - exit 1 -fi \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index e6cda073..00000000 --- a/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM golang:1.23-bookworm AS builder - -WORKDIR /app - -ADD . . - -RUN CGO_ENABLED=0 go build -o ./bin/algorun *.go - -FROM algorand/algod:latest - -ENV TOKEN: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -ENV ADMIN_TOKEN: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -ENV GOSSIP_PORT: 10000 - -USER root - -ADD .docker/start_all.sh /node/run/start_all.sh -ADD .docker/start_dev.sh /node/run/start_dev.sh -ADD .docker/start_empty.sh /node/run/start_empty.sh -ADD .docker/start_fast_catchup.sh /node/run/start_fast_catchup.sh - -COPY --from=builder /app/bin/algorun /bin/algorun - -RUN apt-get update && apt-get install jq -y - -ENTRYPOINT /node/run/start_dev.sh -CMD [] - -EXPOSE 8080 -EXPOSE 8081 -EXPOSE 8082 diff --git a/cmd/node/bootstrap.go b/cmd/node/bootstrap.go index 9d1e1f2f..d2f7af02 100644 --- a/cmd/node/bootstrap.go +++ b/cmd/node/bootstrap.go @@ -1,8 +1,14 @@ package node import ( + "context" "fmt" + "github.com/algorandfoundation/algorun-tui/api" + cmdutils "github.com/algorandfoundation/algorun-tui/cmd/utils" "github.com/algorandfoundation/algorun-tui/internal/algod" + "github.com/algorandfoundation/algorun-tui/internal/algod/utils" + "github.com/algorandfoundation/algorun-tui/internal/system" + "github.com/algorandfoundation/algorun-tui/ui" "github.com/algorandfoundation/algorun-tui/ui/app" "github.com/algorandfoundation/algorun-tui/ui/bootstrap" "github.com/algorandfoundation/algorun-tui/ui/style" @@ -28,6 +34,9 @@ var bootstrapCmd = &cobra.Command{ Long: "Text", SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + httpPkg := new(api.HttpPkg) + fmt.Print(style.Purple(style.BANNER)) out, err := glamour.Render(in, "dark") if err != nil { @@ -70,27 +79,70 @@ var bootstrapCmd = &cobra.Command{ if !algod.IsRunning() { log.Fatal("algod is not running") } + // Create the client + client, err := algod.GetClient("/var/lib/algorand") + if err != nil { + return err + } - //if msg.Catchup { - // ctx := context.Background() - // httpPkg := new(api.HttpPkg) - // client, err := algod.GetClient(endpoint, token) - // - // // Get the latest catchpoint - // catchpoint, _, err := algod.GetLatestCatchpoint(httpPkg, status.Network) - // if err != nil && err.Error() == api.InvalidNetworkParamMsg { - // log.Fatal("This network does not support fast-catchup.") - // } else { - // log.Info(style.Green.Render("Latest Catchpoint: " + catchpoint)) - // } - // - // // Start catchup - // res, _, err := algod.StartCatchup(ctx, client, catchpoint, nil) - // if err != nil { - // log.Fatal(err) - // } - // - //} + if msg.Catchup { + network, err := utils.GetNetworkFromDataDir("/var/lib/algorand") + if err != nil { + return err + } + // Get the latest catchpoint + catchpoint, _, err := algod.GetLatestCatchpoint(httpPkg, network) + if err != nil && err.Error() == api.InvalidNetworkParamMsg { + log.Fatal("This network does not support fast-catchup.") + } else { + log.Info(style.Green.Render("Latest Catchpoint: " + catchpoint)) + } + + // Start catchup + res, _, err := algod.StartCatchup(ctx, client, catchpoint, nil) + 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 }, } diff --git a/cmd/node/catchup/catchup.go b/cmd/node/catchup/catchup.go index d9d5572b..d6ae3094 100644 --- a/cmd/node/catchup/catchup.go +++ b/cmd/node/catchup/catchup.go @@ -8,11 +8,8 @@ import ( ) var ( - // endpoint is a string variable used to store the algod API endpoint address for communication with the node. - endpoint string = "" - - // token is a string flag used to store the admin token required for authenticating with the Algod API. - token string = "" + // dataDir path to the algorand data folder + dataDir string = "" // force indicates whether to bypass certain checks or enforcement logic within a function or command execution flow. force bool = false @@ -36,7 +33,7 @@ var ( Use: "catchup", Short: "Manage Fast-Catchup for your node", Long: cmdLong, - }, &endpoint, &token) + }, &dataDir) ) func init() { diff --git a/cmd/node/catchup/debug.go b/cmd/node/catchup/debug.go index 25ffe55c..c4049d36 100644 --- a/cmd/node/catchup/debug.go +++ b/cmd/node/catchup/debug.go @@ -11,7 +11,6 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/log" "github.com/spf13/cobra" - "github.com/spf13/viper" ) type Catchpoint struct { @@ -54,23 +53,9 @@ var debugCmd = utils.WithAlgodFlags(&cobra.Command{ Long: debugCmdLong, SilenceUsage: false, Run: func(cmd *cobra.Command, args []string) { - err := utils.InitConfig() - if err != nil { - log.Fatal(err) - } - - endpoint := viper.GetString("algod-endpoint") - token := viper.GetString("algod-token") - if endpoint == "" { - log.Fatal("algod-endpoint is required") - } - if token == "" { - log.Fatal("algod-token is required") - } - ctx := context.Background() httpPkg := new(api.HttpPkg) - client, err := algod.GetClient(endpoint, token) + client, err := algod.GetClient("/var/lib/algorand") cobra.CheckErr(err) status, response, err := algod.NewStatus(ctx, client, httpPkg) @@ -103,4 +88,4 @@ var debugCmd = utils.WithAlgodFlags(&cobra.Command{ fmt.Println(style.Bold(string(data))) }, -}, &endpoint, &token) +}, &dataDir) diff --git a/cmd/node/catchup/start.go b/cmd/node/catchup/start.go index 2de628db..b6e72867 100644 --- a/cmd/node/catchup/start.go +++ b/cmd/node/catchup/start.go @@ -9,7 +9,6 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/log" "github.com/spf13/cobra" - "github.com/spf13/viper" ) var startCmdLong = lipgloss.JoinVertical( @@ -32,23 +31,9 @@ var startCmd = utils.WithAlgodFlags(&cobra.Command{ Long: startCmdLong, SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { - err := utils.InitConfig() - if err != nil { - log.Fatal(err) - } - - endpoint := viper.GetString("algod-endpoint") - token := viper.GetString("algod-token") - if endpoint == "" { - log.Fatal("algod-endpoint is required") - } - if token == "" { - log.Fatal("algod-token is required") - } - ctx := context.Background() httpPkg := new(api.HttpPkg) - client, err := algod.GetClient(endpoint, token) + client, err := algod.GetClient("/var/lib/algorand") cobra.CheckErr(err) status, response, err := algod.NewStatus(ctx, client, httpPkg) @@ -74,7 +59,7 @@ var startCmd = utils.WithAlgodFlags(&cobra.Command{ log.Info(style.Green.Render(res)) }, -}, &endpoint, &token) +}, &dataDir) func init() { startCmd.Flags().BoolVarP(&force, "force", "f", false, style.Yellow.Render("forcefully catchup the node")) diff --git a/cmd/node/catchup/stop.go b/cmd/node/catchup/stop.go index 63d008cd..b6977f0c 100644 --- a/cmd/node/catchup/stop.go +++ b/cmd/node/catchup/stop.go @@ -8,7 +8,6 @@ import ( "github.com/algorandfoundation/algorun-tui/ui/style" "github.com/charmbracelet/log" "github.com/spf13/cobra" - "github.com/spf13/viper" ) // stopCmd is a Cobra command used to check the node's sync status and initiate a fast catchup when necessary. @@ -18,23 +17,9 @@ var stopCmd = utils.WithAlgodFlags(&cobra.Command{ Long: "Checks if the node is caught up and if not, starts catching up.", SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { - // Load configuration - err := utils.InitConfig() - if err != nil { - log.Fatal(err) - } - endpoint := viper.GetString("algod-endpoint") - token := viper.GetString("algod-token") - if endpoint == "" { - log.Fatal("algod-endpoint is required") - } - if token == "" { - log.Fatal("algod-token is required") - } - ctx := context.Background() httpPkg := new(api.HttpPkg) - client, err := algod.GetClient(endpoint, token) + client, err := algod.GetClient("/var/lib/algorand") cobra.CheckErr(err) status, response, err := algod.NewStatus(ctx, client, httpPkg) @@ -50,7 +35,7 @@ var stopCmd = utils.WithAlgodFlags(&cobra.Command{ log.Info(style.Green.Render("Latest Catchpoint: " + msg)) }, -}, &endpoint, &token) +}, &dataDir) func init() { stopCmd.Flags().BoolVarP(&force, "force", "f", false, style.Yellow.Render("forcefully catchup the node")) diff --git a/cmd/node/debug.go b/cmd/node/debug.go index 8c84ed07..e816ddca 100644 --- a/cmd/node/debug.go +++ b/cmd/node/debug.go @@ -33,6 +33,8 @@ type DebugInfo struct { // Data contains a list of string entries providing additional paths or diagnostic information about the `algod` service. Data []string `json:"data"` + + DataFolder utils.DataFolderConfig } // debugCmd defines the "debug" command used to display diagnostic information for developers, including debug data. @@ -49,6 +51,9 @@ var debugCmd = &cobra.Command{ paths := utils.GetKnownDataPaths() path, _ := exec.LookPath("algod") + + folderDebug, err := utils.ToDataFolderConfig("/var/lib/algorand") + info := DebugInfo{ InPath: system.CmdExists("algod"), IsRunning: algod.IsRunning(), @@ -56,6 +61,7 @@ var debugCmd = &cobra.Command{ IsInstalled: algod.IsInstalled(), Algod: path, Data: paths, + DataFolder: folderDebug, } data, err := json.MarshalIndent(info, "", " ") if err != nil { diff --git a/cmd/node/sync.go b/cmd/node/sync.go index b9ce0a58..368bfa0f 100644 --- a/cmd/node/sync.go +++ b/cmd/node/sync.go @@ -9,14 +9,10 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/log" "github.com/spf13/cobra" - "github.com/spf13/viper" ) -// syncEndpoint is a string variable used to store the algod API endpoint address for communication with the node. -var syncEndpoint string - -// syncToken is a string flag used to store the admin token required for authenticating with the Algod API. -var syncToken string +// dataDir path to the algorand data folder +var dataDir string = "" // defaultLag represents the default minimum catchup delay in milliseconds for the Fast Catchup process. var defaultLag int = 30_000 @@ -41,20 +37,10 @@ var syncCmd = utils.WithAlgodFlags(&cobra.Command{ Long: syncCmdLongTxt, SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { - // Load the configuration - endpoint := viper.GetString("algod-endpoint") - token := viper.GetString("algod-token") - if endpoint == "" { - log.Fatal("algod-endpoint is required") - } - if token == "" { - log.Fatal("algod-token is required") - } - // Create Clients ctx := context.Background() httpPkg := new(api.HttpPkg) - client, err := algod.GetClient(endpoint, token) + client, err := algod.GetClient(dataDir) cobra.CheckErr(err) // Fetch Status from Node @@ -79,4 +65,4 @@ var syncCmd = utils.WithAlgodFlags(&cobra.Command{ log.Info(style.Green.Render(res)) }, -}, &syncEndpoint, &syncToken) +}, &dataDir) diff --git a/cmd/root.go b/cmd/root.go index f5379fea..28fa3d87 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,18 +14,13 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/log" "github.com/spf13/cobra" - "github.com/spf13/viper" "runtime" - "strings" ) var ( // algodEndpoint defines the URI address of the Algorand node, including the protocol (http/https), for client communication. - algodEndpoint string - - // algodToken is a placeholder string representing an Algod client token, typically used for node authentication. - algodToken = strings.Repeat("a", 64) + algodData string // Version represents the application version string, which is set during build or defaults to "unknown". Version = "" @@ -53,25 +48,9 @@ var ( }, Run: func(cmd *cobra.Command, args []string) { log.SetOutput(cmd.OutOrStdout()) - err := utils.InitConfig() - if err != nil { - log.Fatal(err) - } - - endpoint := viper.GetString("algod-endpoint") - token := viper.GetString("algod-token") - - if endpoint == "" { - log.Fatal(style.Red.Render("algod-endpoint is required") + explanations.NodeNotFound) - } - - if token == "" { - log.Fatal(style.Red.Render("algod-token is required")) - } - // Create the dependencies ctx := context.Background() - client, err := algod.GetClient(endpoint, token) + client, err := algod.GetClient("/var/lib/algorand") cobra.CheckErr(err) httpPkg := new(api.HttpPkg) t := new(system.Clock) @@ -111,7 +90,7 @@ var ( log.Fatal(err) } }, - }, &algodEndpoint, &algodToken) + }, &algodData) ) // init initializes the application, setting up logging, commands, and version information. diff --git a/cmd/utils/config.go b/cmd/utils/config.go index 4d62f13b..dea6f0db 100644 --- a/cmd/utils/config.go +++ b/cmd/utils/config.go @@ -1,57 +1,5 @@ package utils -import ( - "encoding/json" - "github.com/spf13/viper" - "io" - "os" - "strings" -) - -// InitConfig initializes and loads the application's configuration from multiple sources, -// including environment variables and files. -func InitConfig() error { - // Find home directory. - home, err := os.UserHomeDir() - if err != nil { - return err - } - - // Look for paths - viper.AddConfigPath(".") - viper.AddConfigPath(home) - viper.AddConfigPath("/etc/algorun/") - - // Set Config Properties - viper.SetConfigType("yaml") - viper.SetConfigName(".algorun") - viper.SetEnvPrefix("algorun") - - // Load Configurations - viper.AutomaticEnv() - _ = viper.ReadInConfig() - - // Check for algod - loadedAlgod := viper.GetString("algod-endpoint") - loadedToken := viper.GetString("algod-token") - - // Merge in the configuration from Algod - conf, err := MergeAlgorandData(loadedAlgod, loadedToken) - if err != nil { - return err - } - - if conf.Token != loadedToken { - viper.Set("algod-token", conf.Token) - } - - if conf.EndpointAddress != loadedAlgod { - viper.Set("algod-endpoint", conf.EndpointAddress) - } - - return nil -} - // Config represents the config.json file type Config struct { EndpointAddress string `json:"EndpointAddress"` @@ -64,104 +12,3 @@ type DaemonConfig struct { EndpointAddress string `json:"endpoint"` Token string `json:"token"` } - -// MergeAlgorandData loads and merges Algorand configuration data either from environment settings or provided parameters. -// It reads the configuration from the `ALGORAND_DATA` directory if available or uses the provided endpoint and token. -func MergeAlgorandData(endpoint string, token string) (DaemonConfig, error) { - result := DaemonConfig{ - DataDirectoryPath: "", - EndpointAddress: endpoint, - Token: token, - } - - // Load ALGORAND_DATA/config.json - algorandData, exists := os.LookupEnv("ALGORAND_DATA") - - // Load the Algorand Data Configuration - if exists && algorandData != "" && endpoint == "" { - // Placeholder for Struct - var algodConfig Config - - dataConfigPath := algorandData + "/config.json" - - // Open the config.json File - configFile, err := os.Open(dataConfigPath) - if err != nil { - return result, err - } - - // Read the bytes of the File - byteValue, _ := io.ReadAll(configFile) - err = json.Unmarshal(byteValue, &algodConfig) - if err != nil { - return result, err - } - - // Close the open handle - err = configFile.Close() - if err != nil { - return result, err - } - - // Check for endpoint address - if hasWildcardEndpointUrl(algodConfig.EndpointAddress) { - algodConfig.EndpointAddress = replaceEndpointUrl(algodConfig.EndpointAddress) - } else if algodConfig.EndpointAddress == "" { - // Assume it is not set, try to discover the port from the network file - networkPath := algorandData + "/algod.net" - networkFile, err := os.Open(networkPath) - if err != nil { - return result, err - } - - byteValue, err = io.ReadAll(networkFile) - if err != nil { - return result, err - } - - if hasWildcardEndpointUrl(string(byteValue)) { - algodConfig.EndpointAddress = replaceEndpointUrl(string(byteValue)) - } else { - algodConfig.EndpointAddress = string(byteValue) - } - - } - if strings.Contains(algodConfig.EndpointAddress, ":0") { - algodConfig.EndpointAddress = strings.Replace(algodConfig.EndpointAddress, ":0", ":8080", 1) - } - if token == "" { - // Handle Token Path - tokenPath := algorandData + "/algod.admin.token" - - tokenFile, err := os.Open(tokenPath) - if err != nil { - return result, err - } - - byteValue, err = io.ReadAll(tokenFile) - if err != nil { - return result, err - } - - result.Token = strings.Replace(string(byteValue), "\n", "", 1) - } - - // Set the algod configuration - result.EndpointAddress = "http://" + strings.Replace(algodConfig.EndpointAddress, "\n", "", 1) - result.DataDirectoryPath = algorandData - } - return result, nil -} - -// replaceEndpointUrl replaces newline characters and wildcard IP addresses in a URL with a specific local address. -func replaceEndpointUrl(s string) string { - s = strings.Replace(s, "\n", "", 1) - s = strings.Replace(s, "0.0.0.0", "127.0.0.1", 1) - s = strings.Replace(s, "[::]", "127.0.0.1", 1) - return s -} - -// hasWildcardEndpointUrl checks if the given string contains wildcard IP addresses ("0.0.0.0" or "::"). -func hasWildcardEndpointUrl(s string) bool { - return strings.Contains(s, "0.0.0.0") || strings.Contains(s, "::") -} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e3264904..42c40330 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -5,7 +5,6 @@ import ( "github.com/algorandfoundation/algorun-tui/cmd/utils/explanations" "github.com/algorandfoundation/algorun-tui/internal/algod" "github.com/algorandfoundation/algorun-tui/ui/style" - "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -27,37 +26,14 @@ func WithInvalidResponsesExplanations(err error, response api.ResponseInterface, } // WithAlgodFlags enhances a cobra.Command with flags for Algod endpoint and token configuration. -func WithAlgodFlags(cmd *cobra.Command, algodEndpoint *string, token *string) *cobra.Command { - _ = InitConfig() - cmd.Flags().StringVarP(algodEndpoint, "algod-endpoint", "a", "", style.LightBlue("algod endpoint address URI, including http[s]")) - cmd.Flags().StringVarP(token, "algod-token", "t", "", lipgloss.JoinHorizontal( - lipgloss.Left, - style.LightBlue("algod "), - style.BoldUnderline("admin"), - style.LightBlue(" token"), - )) - _ = viper.BindPFlag("algod-endpoint", cmd.Flags().Lookup("algod-endpoint")) - _ = viper.BindPFlag("algod-token", cmd.Flags().Lookup("algod-token")) +func WithAlgodFlags(cmd *cobra.Command, algodData *string) *cobra.Command { + cmd.Flags().StringVarP(algodData, "datadir", "d", "", style.LightBlue("Data directory for the node")) - if viper.GetString("algod-endpoint") != "" || viper.GetViper().ConfigFileUsed() != "" { - cmd.Long += "\n\n" + style.Bold("Configuration:") + "\n" - } - - if viper.GetViper().ConfigFileUsed() != "" { - cmd.Long += - style.LightBlue(" path: ") + viper.GetViper().ConfigFileUsed() + "\n" - } - - // Update Description Text - if viper.GetString("algod-endpoint") != "" { - cmd.Long += - style.LightBlue(" endpoint: ") + viper.GetString("algod-endpoint") + "\n" - - } + _ = viper.BindPFlag("datadir", cmd.Flags().Lookup("datadir")) - if viper.GetString("data") != "" { + if viper.GetString("datadir") != "" { cmd.Long += - style.LightBlue(" data: ") + viper.GetString("data") + "\n" + style.LightBlue(" Data: ") + viper.GetString("datadir") + "\n" } return cmd diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index baf59c83..00000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,19 +0,0 @@ -services: - dev: - build: - context: . - dockerfile: Dockerfile - environment: - - ADMIN_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - - TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - tmpfs: - - /algod/empty - - /algod/fast-catchup - volumes: - - algod:/algod/data - ports: - - "8080:8080" - - "8081:8081" - - "8082:8082" -volumes: - algod: \ No newline at end of file diff --git a/internal/algod/catchpoint.go b/internal/algod/catchpoint.go index d9d815c2..da297b1b 100644 --- a/internal/algod/catchpoint.go +++ b/internal/algod/catchpoint.go @@ -27,7 +27,7 @@ func AbortCatchup(ctx context.Context, client api.ClientWithResponsesInterface, if err != nil { return "", response, err } - if response.StatusCode() != 200 { + if response.StatusCode() >= 300 { return "", response, errors.New(response.Status()) } diff --git a/internal/algod/client.go b/internal/algod/client.go index caf2d9d5..8bcc52c0 100644 --- a/internal/algod/client.go +++ b/internal/algod/client.go @@ -2,14 +2,20 @@ package algod import ( "github.com/algorandfoundation/algorun-tui/api" + "github.com/algorandfoundation/algorun-tui/internal/algod/utils" "github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider" ) // GetClient initializes and returns a new API client configured with the provided endpoint and access token. -func GetClient(endpoint string, token string) (*api.ClientWithResponses, error) { - apiToken, err := securityprovider.NewSecurityProviderApiKey("header", "X-Algo-API-Token", token) +func GetClient(dataDir string) (*api.ClientWithResponses, error) { + config, err := utils.ToDataFolderConfig(dataDir) if err != nil { return nil, err } - return api.NewClientWithResponses(endpoint, api.WithRequestEditorFn(apiToken.Intercept)) + + apiToken, err := securityprovider.NewSecurityProviderApiKey("header", "X-Algo-API-Token", config.Token) + if err != nil { + return nil, err + } + return api.NewClientWithResponses(config.Endpoint, api.WithRequestEditorFn(apiToken.Intercept)) } diff --git a/internal/algod/utils/utils.go b/internal/algod/utils/utils.go index e0d7b5d3..9a71dd11 100644 --- a/internal/algod/utils/utils.go +++ b/internal/algod/utils/utils.go @@ -1,13 +1,53 @@ package utils import ( + "encoding/json" + "fmt" "github.com/algorandfoundation/algorun-tui/internal/system" "github.com/spf13/cobra" "os" "path/filepath" + "strconv" + "strings" "time" ) +type DataFolderConfig struct { + Path string `json:"path"` + Token string `json:"token"` + Endpoint string `json:"endpoint"` + Network string `json:"network"` + PID int `json:"PID"` +} + +func ToDataFolderConfig(path string) (DataFolderConfig, error) { + var dataFolderConfig DataFolderConfig + var err error + if !IsDataDir(path) { + return dataFolderConfig, nil + } + dataFolderConfig.Path = path + dataFolderConfig.Token, err = GetTokenFromDataDir(path) + if err != nil { + return dataFolderConfig, err + } + dataFolderConfig.Network, err = GetNetworkFromDataDir(path) + if err != nil { + return dataFolderConfig, err + } + + dataFolderConfig.Endpoint, err = GetEndpointFromDataDir(path) + if err != nil { + return dataFolderConfig, err + } + dataFolderConfig.PID, err = GetPidFromDataDir(path) + if err != nil { + return dataFolderConfig, err + } + + return dataFolderConfig, nil +} + // IsDataDir determines if the specified path is a valid Algorand data directory containing an "algod.token" file. func IsDataDir(path string) bool { info, err := os.Stat(path) @@ -68,10 +108,66 @@ func GetExpiresTime(t system.Time, lastRound int, roundTime time.Duration, voteL return nil } -func GetTokenFromDataDir(path string) string { - paths := system.FindPathToFile(path, "algod.token") - if len(paths) == 1 { +func GetTokenFromDataDir(path string) (string, error) { + var token string + + file, err := os.ReadFile(path + "/algod.admin.token") + if err != nil { + return token, err + } + + token = strings.Replace(string(file), "\n", "", -1) + return token, nil +} +func GetNetworkFromDataDir(path string) (string, error) { + var network string + file, err := os.ReadFile(path + "/genesis.json") + if err != nil { + return network, err } - return "" + var result map[string]interface{} + err = json.Unmarshal(file, &result) + if err != nil { + return "", err + } + + network = fmt.Sprintf("%s-%s", result["network"].(string), result["id"].(string)) + + return network, nil +} + +func GetPidFromDataDir(path string) (int, error) { + var pid int + file, err := os.ReadFile(path + "/algod.pid") + if err != nil { + return pid, err + } + + pid, err = strconv.Atoi(strings.Replace(string(file), "\n", "", -1)) + if err != nil { + return pid, err + } + + return pid, nil +} + +func GetEndpointFromDataDir(path string) (string, error) { + var endpoint string + file, err := os.ReadFile(path + "/algod.net") + if err != nil { + return endpoint, err + } + + endpoint = "http://" + ReplaceEndpointUrl(string(file)) + + return endpoint, nil +} + +// ReplaceEndpointUrl replaces newline characters and wildcard IP addresses in a URL with a specific local address. +func ReplaceEndpointUrl(s string) string { + s = strings.Replace(s, "\n", "", -1) + s = strings.Replace(s, "0.0.0.0", "127.0.0.1", 1) + s = strings.Replace(s, "[::]", "127.0.0.1", 1) + return s } From b8d4f1c26f0f8a1871c7ad79c6e6e86357043d1f Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 24 Dec 2024 09:00:04 -0500 Subject: [PATCH 03/26] chore: flatten commands --- .github/workflows/node_test.yaml | 20 +++--- cmd/{node => }/bootstrap.go | 30 +++++++-- cmd/{node => }/catchup/catchup.go | 36 +++++++++++ cmd/{node => }/catchup/debug.go | 0 cmd/{node => }/catchup/start.go | 2 +- cmd/{node => }/catchup/stop.go | 0 cmd/{node => }/configure/configure.go | 0 cmd/{node => }/configure/network.go | 0 cmd/{node => }/configure/service.go | 0 cmd/{node => }/configure/utils.go | 0 cmd/{node => }/debug.go | 5 +- cmd/{node => }/install.go | 9 ++- cmd/node/node.go | 90 --------------------------- cmd/node/sync.go | 68 -------------------- cmd/root.go | 58 +++++++++++++---- cmd/{node => }/start.go | 5 +- cmd/{node => }/stop.go | 5 +- cmd/{node => }/uninstall.go | 2 +- cmd/{node => }/upgrade.go | 5 +- cmd/utils/explanations/errors.go | 16 +++++ internal/algod/catchpoint.go | 7 ++- internal/algod/client.go | 18 +++++- playbook.yaml | 10 +-- ui/style/style.go | 32 +++++++--- 24 files changed, 203 insertions(+), 215 deletions(-) rename cmd/{node => }/bootstrap.go (79%) rename cmd/{node => }/catchup/catchup.go (52%) rename cmd/{node => }/catchup/debug.go (100%) rename cmd/{node => }/catchup/start.go (95%) rename cmd/{node => }/catchup/stop.go (100%) rename cmd/{node => }/configure/configure.go (100%) rename cmd/{node => }/configure/network.go (100%) rename cmd/{node => }/configure/service.go (100%) rename cmd/{node => }/configure/utils.go (100%) rename cmd/{node => }/debug.go (94%) rename cmd/{node => }/install.go (84%) delete mode 100644 cmd/node/node.go delete mode 100644 cmd/node/sync.go rename cmd/{node => }/start.go (88%) rename cmd/{node => }/stop.go (90%) rename cmd/{node => }/uninstall.go (99%) rename cmd/{node => }/upgrade.go (89%) create mode 100644 cmd/utils/explanations/errors.go diff --git a/.github/workflows/node_test.yaml b/.github/workflows/node_test.yaml index cacf280c..1a4d4be4 100644 --- a/.github/workflows/node_test.yaml +++ b/.github/workflows/node_test.yaml @@ -24,14 +24,14 @@ jobs: - name: Run Ubuntu commands run: | go build . - ./algorun-tui node install + ./algorun-tui install systemctl status algorand.service export TOKEN=$(cat /var/lib/algorand/algod.admin.token) curl http://localhost:8080/v2/participation -H "X-Algo-API-Token: $TOKEN" | grep "null" - ./algorun-tui node stop - ./algorun-tui node upgrade - ./algorun-tui node stop - ./algorun-tui node uninstall + ./algorun-tui stop + ./algorun-tui upgrade + ./algorun-tui stop + ./algorun-tui uninstall macos: runs-on: macos-latest @@ -45,12 +45,12 @@ jobs: - name: Run MacOs commands run: | go build . - ./algorun-tui node install + ./algorun-tui install sudo launchctl print system/com.algorand.algod sleep 5 export TOKEN=$(cat ~/.algorand/algod.admin.token) curl http://localhost:8080/v2/participation -H "X-Algo-API-Token: $TOKEN" | grep "null" - ./algorun-tui node stop - ./algorun-tui node upgrade - ./algorun-tui node stop - ./algorun-tui node uninstall + ./algorun-tui stop + ./algorun-tui upgrade + ./algorun-tui stop + ./algorun-tui uninstall diff --git a/cmd/node/bootstrap.go b/cmd/bootstrap.go similarity index 79% rename from cmd/node/bootstrap.go rename to cmd/bootstrap.go index d2f7af02..9f44f9cf 100644 --- a/cmd/node/bootstrap.go +++ b/cmd/bootstrap.go @@ -1,10 +1,11 @@ -package node +package cmd import ( "context" "fmt" "github.com/algorandfoundation/algorun-tui/api" cmdutils "github.com/algorandfoundation/algorun-tui/cmd/utils" + "github.com/algorandfoundation/algorun-tui/cmd/utils/explanations" "github.com/algorandfoundation/algorun-tui/internal/algod" "github.com/algorandfoundation/algorun-tui/internal/algod/utils" "github.com/algorandfoundation/algorun-tui/internal/system" @@ -14,12 +15,29 @@ import ( "github.com/algorandfoundation/algorun-tui/ui/style" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/glamour" + "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/log" "github.com/spf13/cobra" "time" ) -var in = `# Welcome! +var bootstrapCmdShort = "Initialize a fresh node. Alias for install, catchup, and start." + +// cmdLong provides a detailed description of the Fast-Catchup feature, explaining its purpose and expected sync durations. +var bootstrapCmdLong = lipgloss.JoinVertical( + lipgloss.Left, + style.BANNER, + "", + style.Bold(bootstrapCmdShort), + "", + style.BoldUnderline("Overview:"), + "Get up and running with a fresh Algorand node.", + "Uses the local package manager to install Algorand, and then starts the node and preforms a Fast-Catchup.", + "", + style.Yellow.Render("Note: This command only supports the default data directory, /var/lib/algorand"), +) + +var tutorial = `# Welcome! This is the beginning of your adventure into running the an Algorand node! @@ -30,15 +48,15 @@ Morbi mauris quam, ornare ac commodo et, posuere id sem. Nulla id condimentum ma // bootstrapCmd defines the "debug" command used to display diagnostic information for developers, including debug data. var bootstrapCmd = &cobra.Command{ Use: "bootstrap", - Short: "Initialize a fresh node. Alias for install, catchup, and start.", - Long: "Text", + Short: bootstrapCmdShort, + Long: bootstrapCmdLong, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() httpPkg := new(api.HttpPkg) fmt.Print(style.Purple(style.BANNER)) - out, err := glamour.Render(in, "dark") + out, err := glamour.Render(tutorial, "dark") if err != nil { return err } @@ -65,7 +83,7 @@ var bootstrapCmd = &cobra.Command{ return nil } - log.Warn(style.Yellow.Render(SudoWarningMsg)) + log.Warn(style.Yellow.Render(explanations.SudoWarningMsg)) if msg.Install && !algod.IsInstalled() { err := algod.Install() if err != nil { diff --git a/cmd/node/catchup/catchup.go b/cmd/catchup/catchup.go similarity index 52% rename from cmd/node/catchup/catchup.go rename to cmd/catchup/catchup.go index d6ae3094..4e7614a1 100644 --- a/cmd/node/catchup/catchup.go +++ b/cmd/catchup/catchup.go @@ -1,9 +1,13 @@ package catchup import ( + "context" + "github.com/algorandfoundation/algorun-tui/api" "github.com/algorandfoundation/algorun-tui/cmd/utils" + "github.com/algorandfoundation/algorun-tui/internal/algod" "github.com/algorandfoundation/algorun-tui/ui/style" "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/log" "github.com/spf13/cobra" ) @@ -11,6 +15,9 @@ var ( // dataDir path to the algorand data folder dataDir string = "" + // defaultLag represents the default minimum catchup delay in milliseconds for the Fast Catchup process. + defaultLag int = 30_000 + // force indicates whether to bypass certain checks or enforcement logic within a function or command execution flow. force bool = false @@ -33,6 +40,35 @@ var ( Use: "catchup", Short: "Manage Fast-Catchup for your node", Long: cmdLong, + Run: func(cmd *cobra.Command, args []string) { + // Create Clients + ctx := context.Background() + httpPkg := new(api.HttpPkg) + client, err := algod.GetClient(dataDir) + cobra.CheckErr(err) + + // Fetch Status from Node + status, response, err := algod.NewStatus(ctx, client, httpPkg) + utils.WithInvalidResponsesExplanations(err, response, cmd.UsageString()) + if status.State == algod.FastCatchupState { + log.Fatal(style.Red.Render("Node is currently catching up")) + } + + // Get the Latest Catchpoint + catchpoint, _, err := algod.GetLatestCatchpoint(httpPkg, status.Network) + if err != nil { + log.Fatal(err) + } + log.Info(style.Green.Render("Latest Catchpoint: " + catchpoint)) + + // Submit the Catchpoint to the Algod Node, using the StartCatchupParams to skip + res, _, err := algod.StartCatchup(ctx, client, catchpoint, &api.StartCatchupParams{Min: &defaultLag}) + if err != nil { + log.Fatal(err) + } + + log.Info(style.Green.Render(res)) + }, }, &dataDir) ) diff --git a/cmd/node/catchup/debug.go b/cmd/catchup/debug.go similarity index 100% rename from cmd/node/catchup/debug.go rename to cmd/catchup/debug.go diff --git a/cmd/node/catchup/start.go b/cmd/catchup/start.go similarity index 95% rename from cmd/node/catchup/start.go rename to cmd/catchup/start.go index b6e72867..e994bbb3 100644 --- a/cmd/node/catchup/start.go +++ b/cmd/catchup/start.go @@ -40,7 +40,7 @@ var startCmd = utils.WithAlgodFlags(&cobra.Command{ utils.WithInvalidResponsesExplanations(err, response, cmd.UsageString()) if status.State == algod.FastCatchupState { - log.Fatal(style.Red.Render("Node is currently catching up. Use --abort to cancel.")) + log.Fatal(style.Red.Render("Node is currently catching up.")) } // Get the latest catchpoint diff --git a/cmd/node/catchup/stop.go b/cmd/catchup/stop.go similarity index 100% rename from cmd/node/catchup/stop.go rename to cmd/catchup/stop.go diff --git a/cmd/node/configure/configure.go b/cmd/configure/configure.go similarity index 100% rename from cmd/node/configure/configure.go rename to cmd/configure/configure.go diff --git a/cmd/node/configure/network.go b/cmd/configure/network.go similarity index 100% rename from cmd/node/configure/network.go rename to cmd/configure/network.go diff --git a/cmd/node/configure/service.go b/cmd/configure/service.go similarity index 100% rename from cmd/node/configure/service.go rename to cmd/configure/service.go diff --git a/cmd/node/configure/utils.go b/cmd/configure/utils.go similarity index 100% rename from cmd/node/configure/utils.go rename to cmd/configure/utils.go diff --git a/cmd/node/debug.go b/cmd/debug.go similarity index 94% rename from cmd/node/debug.go rename to cmd/debug.go index e816ddca..967a12bb 100644 --- a/cmd/node/debug.go +++ b/cmd/debug.go @@ -1,8 +1,9 @@ -package node +package cmd import ( "encoding/json" "fmt" + "github.com/algorandfoundation/algorun-tui/cmd/utils/explanations" "github.com/algorandfoundation/algorun-tui/internal/algod" "github.com/algorandfoundation/algorun-tui/internal/algod/utils" "github.com/algorandfoundation/algorun-tui/internal/system" @@ -47,7 +48,7 @@ var debugCmd = &cobra.Command{ log.Info("Collecting debug information...") // Warn user for prompt - log.Warn(style.Yellow.Render(SudoWarningMsg)) + log.Warn(style.Yellow.Render(explanations.SudoWarningMsg)) paths := utils.GetKnownDataPaths() path, _ := exec.LookPath("algod") diff --git a/cmd/node/install.go b/cmd/install.go similarity index 84% rename from cmd/node/install.go rename to cmd/install.go index 86abf1f8..b2f81a04 100644 --- a/cmd/node/install.go +++ b/cmd/install.go @@ -1,6 +1,7 @@ -package node +package cmd import ( + "github.com/algorandfoundation/algorun-tui/cmd/utils/explanations" "github.com/algorandfoundation/algorun-tui/internal/algod" "github.com/algorandfoundation/algorun-tui/ui/style" "github.com/charmbracelet/log" @@ -15,6 +16,10 @@ const InstallMsg = "Installing Algorand" // InstallExistsMsg is a constant string used to indicate that the Algod is already installed on the system. const InstallExistsMsg = "algod is already installed" +var installShort = "Install the algorand daemon" + +var installLong = style.Purple(style.BANNER) + "\n" + style.LightBlue("Install the algorand daemon on your local machine") + // installCmd is a Cobra command that installs the Algorand daemon on the local machine, ensuring the service is operational. var installCmd = &cobra.Command{ Use: "install", @@ -27,7 +32,7 @@ var installCmd = &cobra.Command{ // TODO: get expected version log.Info(style.Green.Render(InstallMsg)) // Warn user for prompt - log.Warn(style.Yellow.Render(SudoWarningMsg)) + log.Warn(style.Yellow.Render(explanations.SudoWarningMsg)) // TODO: compare expected version to existing version if algod.IsInstalled() && !force { diff --git a/cmd/node/node.go b/cmd/node/node.go deleted file mode 100644 index 3d378042..00000000 --- a/cmd/node/node.go +++ /dev/null @@ -1,90 +0,0 @@ -package node - -import ( - "github.com/algorandfoundation/algorun-tui/cmd/node/catchup" - "github.com/algorandfoundation/algorun-tui/cmd/node/configure" - "github.com/algorandfoundation/algorun-tui/cmd/utils/explanations" - "github.com/algorandfoundation/algorun-tui/internal/algod" - "github.com/algorandfoundation/algorun-tui/ui/style" - "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/log" - "github.com/spf13/cobra" -) - -// SudoWarningMsg is a constant string displayed to warn users that they may be prompted for their password during execution. -const SudoWarningMsg = "(You may be prompted for your password)" - -// PermissionErrorMsg is a constant string that indicates a command requires super-user privileges (sudo) to be executed. -const PermissionErrorMsg = "this command must be run with super-user privileges (sudo)" - -// NotInstalledErrorMsg is the error message displayed when the algod software is not installed on the system. -const NotInstalledErrorMsg = "algod is not installed. please run the *node install* command" - -// RunningErrorMsg represents the error message displayed when algod is running and needs to be stopped before proceeding. -const RunningErrorMsg = "algod is running, please run the *node stop* command" - -// NotRunningErrorMsg is the error message displayed when the algod service is not currently running on the system. -const NotRunningErrorMsg = "algod is not running" - -// force indicates whether actions should be performed forcefully, bypassing checks or confirmations. -var force bool = false - -var short = "Manage your Algorand node using the CLI." -var long = lipgloss.JoinVertical( - lipgloss.Left, - style.Purple(style.BANNER), - "", - style.Bold(short), - "", - style.BoldUnderline("Overview:"), - "A collection of commands for installing, configuring, starting, stopping, and upgrading your node.", - "", - style.Yellow.Render(explanations.ExperimentalWarning), -) - -// Cmd represents the root command for managing an Algorand node, providing subcommands for installation, control, and upgrades. -var Cmd = &cobra.Command{ - Use: "node", - Short: short, - Long: long, -} - -// NeedsToBeRunning ensures the Algod software is installed and running before executing the associated Cobra command. -func NeedsToBeRunning(cmd *cobra.Command, args []string) { - if force { - return - } - if !algod.IsInstalled() { - log.Fatal(NotInstalledErrorMsg) - } - if !algod.IsRunning() { - log.Fatal(NotRunningErrorMsg) - } -} - -// NeedsToBeStopped ensures the operation halts if Algod is not installed or is currently running, unless forced. -func NeedsToBeStopped(cmd *cobra.Command, args []string) { - if force { - return - } - if !algod.IsInstalled() { - log.Fatal(NotInstalledErrorMsg) - } - if algod.IsRunning() { - log.Fatal(RunningErrorMsg) - } -} - -// init initializes the root command by adding subcommands for managing the Algorand node, such as install, start, stop, etc. -func init() { - Cmd.AddCommand(installCmd) - Cmd.AddCommand(startCmd) - Cmd.AddCommand(stopCmd) - Cmd.AddCommand(uninstallCmd) - Cmd.AddCommand(upgradeCmd) - Cmd.AddCommand(syncCmd) - Cmd.AddCommand(debugCmd) - Cmd.AddCommand(bootstrapCmd) - Cmd.AddCommand(catchup.Cmd) - Cmd.AddCommand(configure.Cmd) -} diff --git a/cmd/node/sync.go b/cmd/node/sync.go deleted file mode 100644 index 368bfa0f..00000000 --- a/cmd/node/sync.go +++ /dev/null @@ -1,68 +0,0 @@ -package node - -import ( - "context" - "github.com/algorandfoundation/algorun-tui/api" - "github.com/algorandfoundation/algorun-tui/cmd/utils" - "github.com/algorandfoundation/algorun-tui/internal/algod" - "github.com/algorandfoundation/algorun-tui/ui/style" - "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/log" - "github.com/spf13/cobra" -) - -// dataDir path to the algorand data folder -var dataDir string = "" - -// defaultLag represents the default minimum catchup delay in milliseconds for the Fast Catchup process. -var defaultLag int = 30_000 - -var syncCmdShortTxt = "Quickly catch up your node to the latest block." -var syncCmdLongTxt = lipgloss.JoinVertical( - lipgloss.Left, - style.Purple(style.BANNER), - "", - style.Bold(syncCmdShortTxt), - "", - style.BoldUnderline("Overview:"), - "Fetch the latest catchpoint and use Fast-Catchup to check if the node is caught up to the latest block.", - "", - style.Yellow.Render("Note: Not all networks support Fast-Catchup."), -) - -// syncCmd is a Cobra command used to check the node's sync status and initiate a fast catchup when necessary. -var syncCmd = utils.WithAlgodFlags(&cobra.Command{ - Use: "sync", - Short: syncCmdShortTxt, - Long: syncCmdLongTxt, - SilenceUsage: true, - Run: func(cmd *cobra.Command, args []string) { - // Create Clients - ctx := context.Background() - httpPkg := new(api.HttpPkg) - client, err := algod.GetClient(dataDir) - cobra.CheckErr(err) - - // Fetch Status from Node - status, response, err := algod.NewStatus(ctx, client, httpPkg) - utils.WithInvalidResponsesExplanations(err, response, cmd.UsageString()) - if status.State == algod.FastCatchupState { - log.Fatal(style.Red.Render("Node is currently catching up. Use --abort to cancel.")) - } - - // Get the Latest Catchpoint - catchpoint, _, err := algod.GetLatestCatchpoint(httpPkg, status.Network) - if err != nil { - log.Fatal(err) - } - log.Info(style.Green.Render("Latest Catchpoint: " + catchpoint)) - - // Submit the Catchpoint to the Algod Node, using the StartCatchupParams to skip - res, _, err := algod.StartCatchup(ctx, client, catchpoint, &api.StartCatchupParams{Min: &defaultLag}) - if err != nil { - log.Fatal(err) - } - - log.Info(style.Green.Render(res)) - }, -}, &dataDir) diff --git a/cmd/root.go b/cmd/root.go index 28fa3d87..fd653e27 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,7 +3,8 @@ package cmd import ( "context" "github.com/algorandfoundation/algorun-tui/api" - "github.com/algorandfoundation/algorun-tui/cmd/node" + "github.com/algorandfoundation/algorun-tui/cmd/catchup" + "github.com/algorandfoundation/algorun-tui/cmd/configure" "github.com/algorandfoundation/algorun-tui/cmd/utils" "github.com/algorandfoundation/algorun-tui/cmd/utils/explanations" "github.com/algorandfoundation/algorun-tui/internal/algod" @@ -25,6 +26,9 @@ var ( // Version represents the application version string, which is set during build or defaults to "unknown". Version = "" + // force indicates whether actions should be performed forcefully, bypassing checks or confirmations. + force bool = false + short = "Manage Algorand nodes from the command line" long = lipgloss.JoinVertical( lipgloss.Left, @@ -40,9 +44,10 @@ var ( ) // rootCmd is the primary command for managing Algorand nodes, providing CLI functionality and TUI for interaction. rootCmd = utils.WithAlgodFlags(&cobra.Command{ - Use: "algorun", - Short: short, - Long: long, + Use: "algorun", + Version: Version, + Short: short, + Long: long, CompletionOptions: cobra.CompletionOptions{ DisableDefaultCmd: true, }, @@ -50,7 +55,7 @@ var ( log.SetOutput(cmd.OutOrStdout()) // Create the dependencies ctx := context.Background() - client, err := algod.GetClient("/var/lib/algorand") + client, err := algod.GetClient(algodData) cobra.CheckErr(err) httpPkg := new(api.HttpPkg) t := new(system.Clock) @@ -93,19 +98,46 @@ var ( }, &algodData) ) -// init initializes the application, setting up logging, commands, and version information. -func init() { - log.SetReportTimestamp(false) +// NeedsToBeRunning ensures the Algod software is installed and running before executing the associated Cobra command. +func NeedsToBeRunning(cmd *cobra.Command, args []string) { + if force { + return + } + if !algod.IsInstalled() { + log.Fatal(explanations.NotInstalledErrorMsg) + } + if !algod.IsRunning() { + log.Fatal(explanations.NotRunningErrorMsg) + } +} - // Configure Version - if Version == "" { - Version = "unknown (built from source)" +// NeedsToBeStopped ensures the operation halts if Algod is not installed or is currently running, unless forced. +func NeedsToBeStopped(cmd *cobra.Command, args []string) { + if force { + return + } + if !algod.IsInstalled() { + log.Fatal(explanations.NotInstalledErrorMsg) } - rootCmd.Version = Version + if algod.IsRunning() { + log.Fatal(explanations.RunningErrorMsg) + } +} +// init initializes the application, setting up logging, commands, and version information. +func init() { + log.SetReportTimestamp(false) // Add Commands if runtime.GOOS != "windows" { - rootCmd.AddCommand(node.Cmd) + rootCmd.AddCommand(bootstrapCmd) + rootCmd.AddCommand(debugCmd) + rootCmd.AddCommand(installCmd) + rootCmd.AddCommand(startCmd) + rootCmd.AddCommand(stopCmd) + rootCmd.AddCommand(uninstallCmd) + rootCmd.AddCommand(upgradeCmd) + rootCmd.AddCommand(catchup.Cmd) + rootCmd.AddCommand(configure.Cmd) } } diff --git a/cmd/node/start.go b/cmd/start.go similarity index 88% rename from cmd/node/start.go rename to cmd/start.go index dfb2a0ee..796d6271 100644 --- a/cmd/node/start.go +++ b/cmd/start.go @@ -1,6 +1,7 @@ -package node +package cmd import ( + "github.com/algorandfoundation/algorun-tui/cmd/utils/explanations" "github.com/algorandfoundation/algorun-tui/internal/algod" "github.com/algorandfoundation/algorun-tui/ui/style" "github.com/charmbracelet/log" @@ -17,7 +18,7 @@ var startCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { log.Info(style.Green.Render("Starting Algod 🚀")) // Warn user for prompt - log.Warn(style.Yellow.Render(SudoWarningMsg)) + log.Warn(style.Yellow.Render(explanations.SudoWarningMsg)) err := algod.Start() if err != nil { log.Fatal(err) diff --git a/cmd/node/stop.go b/cmd/stop.go similarity index 90% rename from cmd/node/stop.go rename to cmd/stop.go index b798d4a6..8de0acd4 100644 --- a/cmd/node/stop.go +++ b/cmd/stop.go @@ -1,6 +1,7 @@ -package node +package cmd import ( + "github.com/algorandfoundation/algorun-tui/cmd/utils/explanations" "github.com/algorandfoundation/algorun-tui/internal/algod" "github.com/algorandfoundation/algorun-tui/ui/style" "github.com/charmbracelet/log" @@ -29,7 +30,7 @@ var stopCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { log.Info(style.Green.Render(StoppingAlgodMsg)) // Warn user for prompt - log.Warn(style.Yellow.Render(SudoWarningMsg)) + log.Warn(style.Yellow.Render(explanations.SudoWarningMsg)) err := algod.Stop() if err != nil { diff --git a/cmd/node/uninstall.go b/cmd/uninstall.go similarity index 99% rename from cmd/node/uninstall.go rename to cmd/uninstall.go index 85e687b9..3be97bdc 100644 --- a/cmd/node/uninstall.go +++ b/cmd/uninstall.go @@ -1,4 +1,4 @@ -package node +package cmd import ( "github.com/algorandfoundation/algorun-tui/internal/algod" diff --git a/cmd/node/upgrade.go b/cmd/upgrade.go similarity index 89% rename from cmd/node/upgrade.go rename to cmd/upgrade.go index b1ea0f33..9c84a143 100644 --- a/cmd/node/upgrade.go +++ b/cmd/upgrade.go @@ -1,6 +1,7 @@ -package node +package cmd import ( + "github.com/algorandfoundation/algorun-tui/cmd/utils/explanations" "github.com/algorandfoundation/algorun-tui/internal/algod" "github.com/algorandfoundation/algorun-tui/ui/style" "github.com/charmbracelet/log" @@ -23,7 +24,7 @@ var upgradeCmd = &cobra.Command{ // TODO: get expected version and check if update is required log.Info(style.Green.Render(UpgradeMsg)) // Warn user for prompt - log.Warn(style.Yellow.Render(SudoWarningMsg)) + log.Warn(style.Yellow.Render(explanations.SudoWarningMsg)) // TODO: Check Version from S3 against the local binary err := algod.Update() if err != nil { diff --git a/cmd/utils/explanations/errors.go b/cmd/utils/explanations/errors.go new file mode 100644 index 00000000..32312371 --- /dev/null +++ b/cmd/utils/explanations/errors.go @@ -0,0 +1,16 @@ +package explanations + +// SudoWarningMsg is a constant string displayed to warn users that they may be prompted for their password during execution. +const SudoWarningMsg = "(You may be prompted for your password)" + +// PermissionErrorMsg is a constant string that indicates a command requires super-user privileges (sudo) to be executed. +const PermissionErrorMsg = "this command must be run with super-user privileges (sudo)" + +// NotInstalledErrorMsg is the error message displayed when the algod software is not installed on the system. +const NotInstalledErrorMsg = "algod is not installed. please run the *node install* command" + +// RunningErrorMsg represents the error message displayed when algod is running and needs to be stopped before proceeding. +const RunningErrorMsg = "algod is running, please run the *node stop* command" + +// NotRunningErrorMsg is the error message displayed when the algod service is not currently running on the system. +const NotRunningErrorMsg = "algod is not running" diff --git a/internal/algod/catchpoint.go b/internal/algod/catchpoint.go index da297b1b..00c970c9 100644 --- a/internal/algod/catchpoint.go +++ b/internal/algod/catchpoint.go @@ -14,11 +14,14 @@ func StartCatchup(ctx context.Context, client api.ClientWithResponsesInterface, if err != nil { return "", response, err } - if response.StatusCode() != 200 { + if response.StatusCode() >= 300 { return "", response, errors.New(response.Status()) } + if response.StatusCode() == 200 { + return response.JSON200.CatchupMessage, response, nil + } - return response.JSON200.CatchupMessage, response, nil + return response.JSON201.CatchupMessage, response, nil } // AbortCatchup aborts a ledger catchup process for the specified catchpoint using the provided client interface. diff --git a/internal/algod/client.go b/internal/algod/client.go index 8bcc52c0..52159e4b 100644 --- a/internal/algod/client.go +++ b/internal/algod/client.go @@ -1,14 +1,30 @@ package algod import ( + "errors" "github.com/algorandfoundation/algorun-tui/api" "github.com/algorandfoundation/algorun-tui/internal/algod/utils" "github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider" + "os" ) +const InvalidDataDirMsg = "invalid data directory" + // GetClient initializes and returns a new API client configured with the provided endpoint and access token. func GetClient(dataDir string) (*api.ClientWithResponses, error) { - config, err := utils.ToDataFolderConfig(dataDir) + envDataDir := os.Getenv("ALGORAND_DATA") + if envDataDir == "" && dataDir == "" { + return nil, errors.New(InvalidDataDirMsg) + } + + var resolvedDir string + if dataDir == "" { + resolvedDir = envDataDir + } else { + resolvedDir = dataDir + } + + config, err := utils.ToDataFolderConfig(resolvedDir) if err != nil { return nil, err } diff --git a/playbook.yaml b/playbook.yaml index 097634f7..e5a1d6f2 100644 --- a/playbook.yaml +++ b/playbook.yaml @@ -10,13 +10,13 @@ msg: "Must have algorun installed!" when: not binpath.stat.exists - name: Run installer - command: algorun node install + command: algorun install - name: Run stop - command: algorun node stop + command: algorun stop - name: Run upgrade - command: algorun node upgrade + command: algorun upgrade - name: Run stop - command: algorun node stop + command: algorun stop - name: Run Start - command: algorun node start + command: algorun start # TODO: start a private network, fund TUI account and run TUI integration \ No newline at end of file diff --git a/ui/style/style.go b/ui/style/style.go index ef192ff4..3b0a5e75 100644 --- a/ui/style/style.go +++ b/ui/style/style.go @@ -179,11 +179,27 @@ func TruncateLeft(line string, padding int) string { return ansiStyle + strings.Join(wrapped[1:], "") } -const BANNER = ` - _____ .__ __________ - / _ \ | | ____ ____\______ \__ __ ____ - / /_\ \| | / ___\ / _ \| _/ | \/ \ -/ | \ |__/ /_/ > <_> ) | \ | / | \ -\____|__ /____/\___ / \____/|____|_ /____/|___| / - \/ /_____/ \/ \/ -` +const BANNER_NODE = ` +███ ██ ██████ ██████ ███████ +████ ██ ██ ██ ██ ██ ██ +██ ██ ██ ██ ██ ██ ██ █████ +██ ██ ██ ██ ██ ██ ██ ██ +██ ████ ██████ ██████ ███████ ` +const BANNER_KIT = ` +██╗ ██╗██╗████████╗ +██║ ██╔╝██║╚══██╔══╝ +█████╔╝ ██║ ██║ +██╔═██╗ ██║ ██║ +██║ ██╗██║ ██║ +╚═╝ ╚═╝╚═╝ ╚═╝` + +var BANNER string + +func init() { + var res string + lines := strings.Split(BANNER_KIT, "\n") + for i, line := range strings.Split(BANNER_NODE, "\n") { + res += Purple(line) + Red.Render(lines[i]) + "\n" + } + BANNER = res +} From 9e0ab19acf9bf7d8d9d8d4ef5f720383025a08f7 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 24 Dec 2024 09:02:50 -0500 Subject: [PATCH 04/26] test(internal): skip fragile tests --- internal/algod/accounts_test.go | 2 +- internal/algod/state_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/algod/accounts_test.go b/internal/algod/accounts_test.go index 35f6cd6f..bee34999 100644 --- a/internal/algod/accounts_test.go +++ b/internal/algod/accounts_test.go @@ -12,7 +12,7 @@ import ( ) func Test_AccountsFromState(t *testing.T) { - + t.Skip() // Setup elevated client apiToken, err := securityprovider.NewSecurityProviderApiKey("header", "X-Algo-API-Token", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") if err != nil { diff --git a/internal/algod/state_test.go b/internal/algod/state_test.go index 9b0547aa..64d94ea6 100644 --- a/internal/algod/state_test.go +++ b/internal/algod/state_test.go @@ -10,6 +10,7 @@ import ( ) func Test_StateModel(t *testing.T) { + t.Skip() // Setup elevated client apiToken, err := securityprovider.NewSecurityProviderApiKey("header", "X-Algo-API-Token", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") if err != nil { From 7969133f929ef0dc0db479ee0c7cee551b2432cb Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 24 Dec 2024 09:23:50 -0500 Subject: [PATCH 05/26] chore(mac): fix command from refactor --- internal/algod/mac/mac.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/algod/mac/mac.go b/internal/algod/mac/mac.go index 189c96de..e83bae97 100644 --- a/internal/algod/mac/mac.go +++ b/internal/algod/mac/mac.go @@ -64,7 +64,7 @@ func Install() error { // Create and load the launchd service // TODO: find a clever way to avoid this or make sudo persist for the second call - err = system.RunAll(system.CmdsList{{"sudo", path, "node", "configure", "service"}}) + err = system.RunAll(system.CmdsList{{"sudo", path, "configure", "service"}}) if err != nil { return err } From abc963f4a19c44fe619edebd12fece26cd1e0a4c Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 24 Dec 2024 09:42:23 -0500 Subject: [PATCH 06/26] refactor: convert all clients to data directory --- cmd/bootstrap.go | 10 ++++++-- cmd/catchup/debug.go | 2 +- cmd/catchup/start.go | 2 +- cmd/catchup/stop.go | 2 +- cmd/debug.go | 22 ++++++++++-------- cmd/utils/explanations/explanations.go | 6 ++--- internal/algod/client.go | 32 ++++++++++++++++++++++---- internal/algod/utils/utils.go | 5 +--- 8 files changed, 53 insertions(+), 28 deletions(-) diff --git a/cmd/bootstrap.go b/cmd/bootstrap.go index 9f44f9cf..0c85d477 100644 --- a/cmd/bootstrap.go +++ b/cmd/bootstrap.go @@ -97,14 +97,20 @@ var bootstrapCmd = &cobra.Command{ if !algod.IsRunning() { log.Fatal("algod is not running") } + + dataDir, err := algod.GetDataDir("") + if err != nil { + return err + } // Create the client - client, err := algod.GetClient("/var/lib/algorand") + client, err := algod.GetClient(dataDir) if err != nil { return err } if msg.Catchup { - network, err := utils.GetNetworkFromDataDir("/var/lib/algorand") + + network, err := utils.GetNetworkFromDataDir(dataDir) if err != nil { return err } diff --git a/cmd/catchup/debug.go b/cmd/catchup/debug.go index c4049d36..4c181b96 100644 --- a/cmd/catchup/debug.go +++ b/cmd/catchup/debug.go @@ -55,7 +55,7 @@ var debugCmd = utils.WithAlgodFlags(&cobra.Command{ Run: func(cmd *cobra.Command, args []string) { ctx := context.Background() httpPkg := new(api.HttpPkg) - client, err := algod.GetClient("/var/lib/algorand") + client, err := algod.GetClient(dataDir) cobra.CheckErr(err) status, response, err := algod.NewStatus(ctx, client, httpPkg) diff --git a/cmd/catchup/start.go b/cmd/catchup/start.go index e994bbb3..c43c4501 100644 --- a/cmd/catchup/start.go +++ b/cmd/catchup/start.go @@ -33,7 +33,7 @@ var startCmd = utils.WithAlgodFlags(&cobra.Command{ Run: func(cmd *cobra.Command, args []string) { ctx := context.Background() httpPkg := new(api.HttpPkg) - client, err := algod.GetClient("/var/lib/algorand") + client, err := algod.GetClient(dataDir) cobra.CheckErr(err) status, response, err := algod.NewStatus(ctx, client, httpPkg) diff --git a/cmd/catchup/stop.go b/cmd/catchup/stop.go index 58ade548..c1e29e2c 100644 --- a/cmd/catchup/stop.go +++ b/cmd/catchup/stop.go @@ -33,7 +33,7 @@ var stopCmd = utils.WithAlgodFlags(&cobra.Command{ Run: func(cmd *cobra.Command, args []string) { ctx := context.Background() httpPkg := new(api.HttpPkg) - client, err := algod.GetClient("/var/lib/algorand") + client, err := algod.GetClient(dataDir) cobra.CheckErr(err) status, response, err := algod.NewStatus(ctx, client, httpPkg) diff --git a/cmd/debug.go b/cmd/debug.go index 967a12bb..8407a017 100644 --- a/cmd/debug.go +++ b/cmd/debug.go @@ -3,6 +3,7 @@ package cmd import ( "encoding/json" "fmt" + cmdutils "github.com/algorandfoundation/algorun-tui/cmd/utils" "github.com/algorandfoundation/algorun-tui/cmd/utils/explanations" "github.com/algorandfoundation/algorun-tui/internal/algod" "github.com/algorandfoundation/algorun-tui/internal/algod/utils" @@ -32,14 +33,11 @@ type DebugInfo struct { // Algod holds the path to the `algod` executable if found on the system, or an empty string if not found. Algod string `json:"algod"` - // Data contains a list of string entries providing additional paths or diagnostic information about the `algod` service. - Data []string `json:"data"` - - DataFolder utils.DataFolderConfig + DataFolder utils.DataFolderConfig `json:"data"` } // debugCmd defines the "debug" command used to display diagnostic information for developers, including debug data. -var debugCmd = &cobra.Command{ +var debugCmd = cmdutils.WithAlgodFlags(&cobra.Command{ Use: "debug", Short: "Display debug information for developers", Long: "Prints debug data to be copy and pasted to a bug report.", @@ -50,18 +48,22 @@ var debugCmd = &cobra.Command{ // Warn user for prompt log.Warn(style.Yellow.Render(explanations.SudoWarningMsg)) - paths := utils.GetKnownDataPaths() path, _ := exec.LookPath("algod") - folderDebug, err := utils.ToDataFolderConfig("/var/lib/algorand") - + dataDir, err := algod.GetDataDir("") + if err != nil { + return err + } + folderDebug, err := utils.ToDataFolderConfig(dataDir) + if err != nil { + return err + } info := DebugInfo{ InPath: system.CmdExists("algod"), IsRunning: algod.IsRunning(), IsService: algod.IsService(), IsInstalled: algod.IsInstalled(), Algod: path, - Data: paths, DataFolder: folderDebug, } data, err := json.MarshalIndent(info, "", " ") @@ -73,4 +75,4 @@ var debugCmd = &cobra.Command{ fmt.Println(style.Bold(string(data))) return nil }, -} +}, &algodData) diff --git a/cmd/utils/explanations/explanations.go b/cmd/utils/explanations/explanations.go index a1fa4fe3..216c680b 100644 --- a/cmd/utils/explanations/explanations.go +++ b/cmd/utils/explanations/explanations.go @@ -10,12 +10,10 @@ var NodeNotFound = lipgloss.JoinHorizontal(lipgloss.Left, style.Cyan.Render("Explanation"), style.Bold(": "), ) + - "algorun could not find your node automatically.\n\n" + + "algorun could not find your node automatically. (ensure the node is running)\n\n" + lipgloss.JoinHorizontal(lipgloss.Left, "Provide ", - style.Bold("--algod-endpoint"), - " and ", - style.Bold("--algod-token"), + style.Bold("--datadir"), " or set the goal-compatible ", style.Bold("ALGORAND_DATA"), " environment variable to the algod data directory, ", diff --git a/internal/algod/client.go b/internal/algod/client.go index 52159e4b..828f11d2 100644 --- a/internal/algod/client.go +++ b/internal/algod/client.go @@ -6,24 +6,46 @@ import ( "github.com/algorandfoundation/algorun-tui/internal/algod/utils" "github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider" "os" + "path/filepath" + "runtime" ) const InvalidDataDirMsg = "invalid data directory" -// GetClient initializes and returns a new API client configured with the provided endpoint and access token. -func GetClient(dataDir string) (*api.ClientWithResponses, error) { +func GetDataDir(dataDir string) (string, error) { envDataDir := os.Getenv("ALGORAND_DATA") - if envDataDir == "" && dataDir == "" { - return nil, errors.New(InvalidDataDirMsg) + + var defaultDataDir string + switch runtime.GOOS { + case "darwin": + defaultDataDir = filepath.Join(os.Getenv("HOME"), ".algorand") + case "linux": + defaultDataDir = "/var/lib/algorand" + default: + return "", errors.New(UnsupportedOSError) } var resolvedDir string + if dataDir == "" { - resolvedDir = envDataDir + if envDataDir == "" { + resolvedDir = defaultDataDir + } else { + resolvedDir = envDataDir + } } else { resolvedDir = dataDir } + return resolvedDir, nil +} + +// GetClient initializes and returns a new API client configured with the provided endpoint and access token. +func GetClient(dataDir string) (*api.ClientWithResponses, error) { + resolvedDir, err := GetDataDir(dataDir) + if err != nil { + return nil, err + } config, err := utils.ToDataFolderConfig(resolvedDir) if err != nil { return nil, err diff --git a/internal/algod/utils/utils.go b/internal/algod/utils/utils.go index 9a71dd11..1244de23 100644 --- a/internal/algod/utils/utils.go +++ b/internal/algod/utils/utils.go @@ -40,10 +40,7 @@ func ToDataFolderConfig(path string) (DataFolderConfig, error) { if err != nil { return dataFolderConfig, err } - dataFolderConfig.PID, err = GetPidFromDataDir(path) - if err != nil { - return dataFolderConfig, err - } + dataFolderConfig.PID, _ = GetPidFromDataDir(path) return dataFolderConfig, nil } From 4088acd0ee93cab9582358bf817dd86fcdbc81d4 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 24 Dec 2024 09:44:10 -0500 Subject: [PATCH 07/26] ci: dogfood installer --- .github/workflows/code_test.yaml | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/.github/workflows/code_test.yaml b/.github/workflows/code_test.yaml index df125f2e..4fd67ab7 100644 --- a/.github/workflows/code_test.yaml +++ b/.github/workflows/code_test.yaml @@ -16,18 +16,6 @@ jobs: with: go-version: 1.22 - - name: setup .algorun.yaml - run: | - touch .algorun.yaml - echo 'server: http://localhost:8080' >> .algorun.yaml - echo 'token: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' >> .algorun.yaml - - - name: Start Docker Compose - run: docker compose up -d - - - name: Wait for the server to start - run: npx wait-on tcp:8080 - - name: Install dependencies run: go get . @@ -55,6 +43,12 @@ jobs: - name: Build run: go build -o bin/algorun *.go + - name: Install Algod + run: ./bin/algorun install + + - name: Wait for the server to start + run: npx wait-on tcp:8080 + - name: Unit Tests run: make unit From 1bfafbd8acdd2b79b13472c219d19e8a83447714 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 24 Dec 2024 09:46:41 -0500 Subject: [PATCH 08/26] ci: simplify workflow --- .docker/Fedora.dockerfile | 43 ----------------------- .docker/Ubuntu.dockerfile | 47 -------------------------- .github/workflows/code_test.yaml | 23 +------------ .github/workflows/node_test.yaml | 20 +++++++++-- Makefile | 14 ++------ docker-compose.integration.yaml | 58 -------------------------------- 6 files changed, 22 insertions(+), 183 deletions(-) delete mode 100644 .docker/Fedora.dockerfile delete mode 100644 .docker/Ubuntu.dockerfile delete mode 100644 docker-compose.integration.yaml diff --git a/.docker/Fedora.dockerfile b/.docker/Fedora.dockerfile deleted file mode 100644 index 1891af84..00000000 --- a/.docker/Fedora.dockerfile +++ /dev/null @@ -1,43 +0,0 @@ -FROM golang:1.23-bookworm as BUILDER - -WORKDIR /app - -ADD . . - -RUN CGO_ENABLED=0 go build -cover -o ./bin/algorun *.go - - -FROM fedora:39 as legacy - -ADD playbook.yaml /root/playbook.yaml -COPY --from=BUILDER /app/bin/algorun /usr/bin/algorun -RUN dnf install systemd ansible-core -y && \ - mkdir -p /app/coverage/int/fedora/39 && \ - echo GOCOVERDIR=/app/coverage/int/fedora/39 >> /etc/environment - -STOPSIGNAL SIGRTMIN+3 -CMD ["/usr/lib/systemd/systemd"] - -FROM fedora:40 as previous - -ADD playbook.yaml /root/playbook.yaml -COPY --from=BUILDER /app/bin/algorun /usr/bin/algorun - -RUN dnf install systemd ansible-core -y && \ - mkdir -p /app/coverage/int/fedora/40 && \ - echo GOCOVERDIR=/app/coverage/int/fedora/40 >> /etc/environment - -STOPSIGNAL SIGRTMIN+3 -CMD ["/usr/lib/systemd/systemd"] - -FROM fedora:41 as latest - -ADD playbook.yaml /root/playbook.yaml -COPY --from=BUILDER /app/bin/algorun /usr/bin/algorun - -RUN dnf install systemd ansible-core -y && \ - mkdir -p /app/coverage/int/fedora/41 && \ - echo GOCOVERDIR=/app/coverage/int/fedora/41 >> /etc/environment - -STOPSIGNAL SIGRTMIN+3 -CMD ["/usr/lib/systemd/systemd"] diff --git a/.docker/Ubuntu.dockerfile b/.docker/Ubuntu.dockerfile deleted file mode 100644 index f5cca2f7..00000000 --- a/.docker/Ubuntu.dockerfile +++ /dev/null @@ -1,47 +0,0 @@ -FROM golang:1.23-bookworm as BUILDER - -WORKDIR /app - -ADD . . - -RUN CGO_ENABLED=0 go build -cover -o ./bin/algorun *.go - -FROM ubuntu:18.04 as bionic - -RUN apt-get update && apt-get install systemd software-properties-common -y && add-apt-repository --yes --update ppa:ansible/ansible - -ADD playbook.yaml /root/playbook.yaml -COPY --from=BUILDER /app/bin/algorun /usr/bin/algorun -RUN mkdir -p /app/coverage/int/ubuntu/18.04 && \ - echo GOCOVERDIR=/app/coverage/int/ubuntu/18.04 >> /etc/environment && \ - apt-get install ansible -y && \ - chmod 0 /usr/bin/apt # Liam Neeson - -STOPSIGNAL SIGRTMIN+3 -CMD ["/bin/systemd"] - -FROM ubuntu:22.04 as jammy - -RUN apt-get update && apt-get install systemd software-properties-common -y && add-apt-repository --yes --update ppa:ansible/ansible - -ADD playbook.yaml /root/playbook.yaml -COPY --from=BUILDER /app/bin/algorun /usr/bin/algorun -RUN mkdir -p /app/coverage/int/ubuntu/22.04 && \ - echo GOCOVERDIR=/app/coverage/int/ubuntu/22.04 >> /etc/environment && \ - apt-get install ansible -y - -STOPSIGNAL SIGRTMIN+3 -CMD ["/usr/lib/systemd/systemd"] - -FROM ubuntu:24.04 as noble - -RUN apt-get update && apt-get install systemd software-properties-common -y && add-apt-repository --yes --update ppa:ansible/ansible - -ADD playbook.yaml /root/playbook.yaml -COPY --from=BUILDER /app/bin/algorun /usr/bin/algorun -RUN mkdir -p /app/coverage/int/ubuntu/24.04 && \ - echo GOCOVERDIR=/app/coverage/int/ubuntu/24.04 >> /etc/environment && \ - apt-get install ansible -y - -STOPSIGNAL SIGRTMIN+3 -CMD ["/usr/lib/systemd/systemd"] \ No newline at end of file diff --git a/.github/workflows/code_test.yaml b/.github/workflows/code_test.yaml index 4fd67ab7..9cd454f3 100644 --- a/.github/workflows/code_test.yaml +++ b/.github/workflows/code_test.yaml @@ -43,29 +43,8 @@ jobs: - name: Build run: go build -o bin/algorun *.go - - name: Install Algod - run: ./bin/algorun install - - - name: Wait for the server to start - run: npx wait-on tcp:8080 - - name: Unit Tests - run: make unit - - - name: Kill docker - run: docker compose down - - - name: Start Integration - run: docker compose -f docker-compose.integration.yaml up -d - - - name: Wait for mount - run: npx wait-on ./coverage/int/fedora/40 ./coverage/int/ubuntu/24.04/ - - - name: Integration tests - run: make integration - - - name: Combine coverage - run: make combine-coverage + run: make test - name: Upload results to Codecov uses: codecov/codecov-action@v4 diff --git a/.github/workflows/node_test.yaml b/.github/workflows/node_test.yaml index 1a4d4be4..4f72ca43 100644 --- a/.github/workflows/node_test.yaml +++ b/.github/workflows/node_test.yaml @@ -23,7 +23,9 @@ jobs: go-version: 1.22 - name: Run Ubuntu commands run: | - go build . + export GOCOVERDIR=$(pwd)/coverage + mkdir -p $GOCOVERDIR + go build -cover . ./algorun-tui install systemctl status algorand.service export TOKEN=$(cat /var/lib/algorand/algod.admin.token) @@ -32,6 +34,12 @@ jobs: ./algorun-tui upgrade ./algorun-tui stop ./algorun-tui uninstall + go tool covdata textfmt -i=$GOCOVERDIR -o coverage.txt + + - name: Upload results to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} macos: runs-on: macos-latest @@ -44,7 +52,9 @@ jobs: - name: Run MacOs commands run: | - go build . + export GOCOVERDIR=$(pwd)/coverage + mkdir -p $GOCOVERDIR + go build -cover . ./algorun-tui install sudo launchctl print system/com.algorand.algod sleep 5 @@ -54,3 +64,9 @@ jobs: ./algorun-tui upgrade ./algorun-tui stop ./algorun-tui uninstall + go tool covdata textfmt -i=$GOCOVERDIR -o coverage.txt + + - name: Upload results to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/Makefile b/Makefile index 195d9e3a..a9c60543 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,6 @@ build: - CGO_ENABLED=0 go build -o bin/algorun *.go + CGO_ENABLED=0 go build -o bin/algorun . test: - go test -coverpkg=./... -covermode=atomic ./... + go test -coverprofile=coverage.out -coverpkg=./... -covermode=atomic ./... generate: - oapi-codegen -config generate.yaml https://raw.githubusercontent.com/algorand/go-algorand/v3.26.0-stable/daemon/algod/api/algod.oas3.yml -unit: - mkdir -p $(CURDIR)/coverage/unit && go test -cover ./... -args -test.gocoverdir=$(CURDIR)/coverage/unit -integration: - for service in $(shell docker compose -f docker-compose.integration.yaml ps --services) ; do \ - docker compose exec -it "$$service" ansible-playbook --connection=local /root/playbook.yaml ; \ - done -combine-coverage: - go tool covdata textfmt -i=./coverage/unit,./coverage/int/ubuntu/24.04,./coverage/int/fedora/40 -o coverage.txt && sed -i 2,3d coverage.txt \ No newline at end of file + oapi-codegen -config generate.yaml https://raw.githubusercontent.com/algorand/go-algorand/v3.26.0-stable/daemon/algod/api/algod.oas3.yml \ No newline at end of file diff --git a/docker-compose.integration.yaml b/docker-compose.integration.yaml deleted file mode 100644 index 9a89539d..00000000 --- a/docker-compose.integration.yaml +++ /dev/null @@ -1,58 +0,0 @@ -services: - # Legacy with apt disabled - ubuntu.18.04: - deploy: - replicas: 0 - privileged: true - environment: - - GOCOVERDIR=/app/coverage/int/ubuntu/18.04 - build: - context: . - target: bionic - dockerfile: .docker/Ubuntu.dockerfile - volumes: - - "./coverage/int/ubuntu/18.04:/app/coverage/int/ubuntu/18.04" - ubuntu.22.04: - deploy: - replicas: 0 - privileged: true - environment: - - GOCOVERDIR=/app/coverage/int/ubuntu/22.04 - build: - context: . - target: jammy - dockerfile: .docker/Ubuntu.dockerfile - volumes: - - "./coverage/int/ubuntu/22.04:/app/coverage/int/ubuntu/22.04" - ubuntu.24.04: - privileged: true - environment: - - GOCOVERDIR=/app/coverage/int/ubuntu/24.04 - build: - context: . - target: noble - dockerfile: .docker/Ubuntu.dockerfile - volumes: - - "./coverage/int/ubuntu/24.04:/app/coverage/int/ubuntu/24.04" - fedora.39: - deploy: - replicas: 0 - privileged: true - environment: - - GOCOVERDIR=/app/coverage/int/fedora/39 - build: - context: . - target: legacy - dockerfile: .docker/Fedora.dockerfile - volumes: - - "./coverage/int/fedora/39:/app/coverage/int/fedora/39" - fedora.40: - privileged: true - environment: - - GOCOVERDIR=/app/coverage/int/fedora/40 - build: - context: . - target: previous - dockerfile: .docker/Fedora.dockerfile - volumes: - - "./coverage/int/fedora/40:/app/coverage/int/fedora/40" \ No newline at end of file From f0db7b913338db4db35ea41f3f8ea29e87d463b2 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 24 Dec 2024 10:12:06 -0500 Subject: [PATCH 09/26] ci: add catchup to tests --- .github/workflows/node_test.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/node_test.yaml b/.github/workflows/node_test.yaml index 4f72ca43..17505de2 100644 --- a/.github/workflows/node_test.yaml +++ b/.github/workflows/node_test.yaml @@ -32,6 +32,8 @@ jobs: curl http://localhost:8080/v2/participation -H "X-Algo-API-Token: $TOKEN" | grep "null" ./algorun-tui stop ./algorun-tui upgrade + ./algorun-tui catchup + ./algorun-tui catchup stop ./algorun-tui stop ./algorun-tui uninstall go tool covdata textfmt -i=$GOCOVERDIR -o coverage.txt @@ -62,6 +64,8 @@ jobs: curl http://localhost:8080/v2/participation -H "X-Algo-API-Token: $TOKEN" | grep "null" ./algorun-tui stop ./algorun-tui upgrade + ./algorun-tui catchup + ./algorun-tui catchup stop ./algorun-tui stop ./algorun-tui uninstall go tool covdata textfmt -i=$GOCOVERDIR -o coverage.txt From a94d31c79dccc074ee10b6467d0fab1b1533789a Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 24 Dec 2024 10:17:54 -0500 Subject: [PATCH 10/26] ci: sleep before catchup --- .github/workflows/node_test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/node_test.yaml b/.github/workflows/node_test.yaml index 17505de2..c1d5e932 100644 --- a/.github/workflows/node_test.yaml +++ b/.github/workflows/node_test.yaml @@ -32,6 +32,7 @@ jobs: curl http://localhost:8080/v2/participation -H "X-Algo-API-Token: $TOKEN" | grep "null" ./algorun-tui stop ./algorun-tui upgrade + sleep 10 ./algorun-tui catchup ./algorun-tui catchup stop ./algorun-tui stop @@ -64,6 +65,7 @@ jobs: curl http://localhost:8080/v2/participation -H "X-Algo-API-Token: $TOKEN" | grep "null" ./algorun-tui stop ./algorun-tui upgrade + sleep 10 ./algorun-tui catchup ./algorun-tui catchup stop ./algorun-tui stop From 1674033edcb329638851f067282c307adf2bc503 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 24 Dec 2024 10:21:54 -0500 Subject: [PATCH 11/26] ci: test debug commands --- .github/workflows/node_test.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/node_test.yaml b/.github/workflows/node_test.yaml index c1d5e932..35d91d43 100644 --- a/.github/workflows/node_test.yaml +++ b/.github/workflows/node_test.yaml @@ -32,8 +32,10 @@ jobs: curl http://localhost:8080/v2/participation -H "X-Algo-API-Token: $TOKEN" | grep "null" ./algorun-tui stop ./algorun-tui upgrade + ./algorun-tui debug sleep 10 ./algorun-tui catchup + ./algorun-tui catchup debug ./algorun-tui catchup stop ./algorun-tui stop ./algorun-tui uninstall @@ -65,8 +67,10 @@ jobs: curl http://localhost:8080/v2/participation -H "X-Algo-API-Token: $TOKEN" | grep "null" ./algorun-tui stop ./algorun-tui upgrade + ./algorun-tui debug sleep 10 ./algorun-tui catchup + ./algorun-tui catchup debug ./algorun-tui catchup stop ./algorun-tui stop ./algorun-tui uninstall From ed97a64eb39861b98ebb242ceb77c20d153f3804 Mon Sep 17 00:00:00 2001 From: Michael J Feher Date: Thu, 26 Dec 2024 11:54:22 -0500 Subject: [PATCH 12/26] chore: update explanations for not installed Co-authored-by: Tasos Bitsios --- cmd/utils/explanations/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/utils/explanations/errors.go b/cmd/utils/explanations/errors.go index 32312371..6a1d566e 100644 --- a/cmd/utils/explanations/errors.go +++ b/cmd/utils/explanations/errors.go @@ -7,7 +7,7 @@ const SudoWarningMsg = "(You may be prompted for your password)" const PermissionErrorMsg = "this command must be run with super-user privileges (sudo)" // NotInstalledErrorMsg is the error message displayed when the algod software is not installed on the system. -const NotInstalledErrorMsg = "algod is not installed. please run the *node install* command" +const NotInstalledErrorMsg = "algod is not installed. please run the *install* command" // RunningErrorMsg represents the error message displayed when algod is running and needs to be stopped before proceeding. const RunningErrorMsg = "algod is running, please run the *node stop* command" From 4d9f00576f9ce3fda21d1f0b4c8432dc0523a64d Mon Sep 17 00:00:00 2001 From: Michael J Feher Date: Thu, 26 Dec 2024 11:54:47 -0500 Subject: [PATCH 13/26] chore: update explanations for running error Co-authored-by: Tasos Bitsios --- cmd/utils/explanations/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/utils/explanations/errors.go b/cmd/utils/explanations/errors.go index 6a1d566e..97b1d47f 100644 --- a/cmd/utils/explanations/errors.go +++ b/cmd/utils/explanations/errors.go @@ -10,7 +10,7 @@ const PermissionErrorMsg = "this command must be run with super-user privileges const NotInstalledErrorMsg = "algod is not installed. please run the *install* command" // RunningErrorMsg represents the error message displayed when algod is running and needs to be stopped before proceeding. -const RunningErrorMsg = "algod is running, please run the *node stop* command" +const RunningErrorMsg = "algod is running, please run the *stop* command" // NotRunningErrorMsg is the error message displayed when the algod service is not currently running on the system. const NotRunningErrorMsg = "algod is not running" From 4491978964e17d685c9d653f0ac6169698aaa486 Mon Sep 17 00:00:00 2001 From: Michael J Feher Date: Thu, 26 Dec 2024 11:55:37 -0500 Subject: [PATCH 14/26] chore: update verbiage for catch up in bootstrap Co-authored-by: Tasos Bitsios --- ui/bootstrap/model.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/bootstrap/model.go b/ui/bootstrap/model.go index 9c6bd9ba..5934c0e9 100644 --- a/ui/bootstrap/model.go +++ b/ui/bootstrap/model.go @@ -22,7 +22,9 @@ It looks like you're running this for the first time. Would you like to install const CatchupQuestionMsg = `# Catching Up -Would you like to preform a fast-catchup? (Y/n) +Regular sync with the network usually takes multiple days to weeks. You can optionally perform fast-catchup to sync within minutes instead. + +Would you like to preform a fast-catchup after installation? (Y/n) ` type Model struct { From 2af63bb2586e9ceba048c9d4b745b72ec1640165 Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Thu, 2 Jan 2025 13:17:28 +0100 Subject: [PATCH 15/26] override github channel dev->beta --- api/github.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/github.go b/api/github.go index 89292f06..ebf375d3 100644 --- a/api/github.go +++ b/api/github.go @@ -24,6 +24,9 @@ func (r GithubVersionResponse) Status() string { } func GetGoAlgorandReleaseWithResponse(http HttpPkgInterface, channel string) (*GithubVersionResponse, error) { + if channel == "dev" { + channel = "beta" + } var versions GithubVersionResponse resp, err := http.Get("https://api.github.com/repos/algorand/go-algorand/releases") versions.HTTPResponse = resp @@ -54,7 +57,6 @@ func GetGoAlgorandReleaseWithResponse(http HttpPkgInterface, channel string) (*G versionResponse = &tn break } - } // If the tag was not found, return an error From 91e58d46f02eb2b09d1bc37d0f9b55714211de2f Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Thu, 2 Jan 2025 15:23:42 +0100 Subject: [PATCH 16/26] Revert "override github channel dev->beta" This reverts commit 2af63bb2586e9ceba048c9d4b745b72ec1640165. --- api/github.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/api/github.go b/api/github.go index ebf375d3..89292f06 100644 --- a/api/github.go +++ b/api/github.go @@ -24,9 +24,6 @@ func (r GithubVersionResponse) Status() string { } func GetGoAlgorandReleaseWithResponse(http HttpPkgInterface, channel string) (*GithubVersionResponse, error) { - if channel == "dev" { - channel = "beta" - } var versions GithubVersionResponse resp, err := http.Get("https://api.github.com/repos/algorand/go-algorand/releases") versions.HTTPResponse = resp @@ -57,6 +54,7 @@ func GetGoAlgorandReleaseWithResponse(http HttpPkgInterface, channel string) (*G versionResponse = &tn break } + } // If the tag was not found, return an error From 5623ee2197b697dd48919cbe6effee79dcbc9963 Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Thu, 2 Jan 2025 15:24:14 +0100 Subject: [PATCH 17/26] github: only check release when current build channel is "stable" or "beta" --- internal/algod/status.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/internal/algod/status.go b/internal/algod/status.go index 8297609d..bdf8a14b 100644 --- a/internal/algod/status.go +++ b/internal/algod/status.go @@ -3,6 +3,7 @@ package algod import ( "context" "errors" + "github.com/algorandfoundation/algorun-tui/api" ) @@ -138,18 +139,19 @@ func NewStatus(ctx context.Context, client api.ClientWithResponsesInterface, htt } status.Network = v.Network status.Version = v.Version - - // TODO: last checked - releaseResponse, err := api.GetGoAlgorandReleaseWithResponse(httpPkg, v.Channel) - // Return the error and response - if err != nil { - return status, releaseResponse, err - } - // Update status update field - if releaseResponse != nil && status.Version != releaseResponse.JSON200 { - status.NeedsUpdate = true - } else { - status.NeedsUpdate = false + status.NeedsUpdate = false + + if v.Channel == "beta" || v.Channel == "stable" { + // TODO: last checked + releaseResponse, err := api.GetGoAlgorandReleaseWithResponse(httpPkg, v.Channel) + // Return the error and response + if err != nil { + return status, releaseResponse, err + } + // Update status update field + if releaseResponse != nil && status.Version != releaseResponse.JSON200 { + status.NeedsUpdate = true + } } return status.Get(ctx) From 4c75760fa92a4eb5b0d04c28c037d6e77d1fc6a5 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Thu, 2 Jan 2025 10:14:15 -0500 Subject: [PATCH 18/26] fix: remove error handler for endpoint file --- internal/algod/utils/utils.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/algod/utils/utils.go b/internal/algod/utils/utils.go index 1244de23..f64433d2 100644 --- a/internal/algod/utils/utils.go +++ b/internal/algod/utils/utils.go @@ -12,6 +12,8 @@ import ( "time" ) +const AlgodNetEndpointFileMissingAddress = "missing://endpoint" + type DataFolderConfig struct { Path string `json:"path"` Token string `json:"token"` @@ -36,10 +38,7 @@ func ToDataFolderConfig(path string) (DataFolderConfig, error) { return dataFolderConfig, err } - dataFolderConfig.Endpoint, err = GetEndpointFromDataDir(path) - if err != nil { - return dataFolderConfig, err - } + dataFolderConfig.Endpoint, _ = GetEndpointFromDataDir(path) dataFolderConfig.PID, _ = GetPidFromDataDir(path) return dataFolderConfig, nil @@ -153,7 +152,7 @@ func GetEndpointFromDataDir(path string) (string, error) { var endpoint string file, err := os.ReadFile(path + "/algod.net") if err != nil { - return endpoint, err + return AlgodNetEndpointFileMissingAddress, nil } endpoint = "http://" + ReplaceEndpointUrl(string(file)) From e781e42e1ee2d5ae4b75a2dc9e1bf94b8b51a51e Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Thu, 2 Jan 2025 11:26:24 -0500 Subject: [PATCH 19/26] feat: add fast-catchup state to status --- internal/algod/state.go | 9 ++++++++- internal/algod/status.go | 18 +++++++++++++++++- ui/modal/controller.go | 23 +++++++++++++++++++++-- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/internal/algod/state.go b/internal/algod/state.go index e154f107..6111577c 100644 --- a/internal/algod/state.go +++ b/internal/algod/state.go @@ -118,11 +118,18 @@ func (s *StateModel) Watch(cb func(model *StateModel, err error), ctx context.Co } // Abort on Fast-Catchup if s.Status.State == FastCatchupState { - time.Sleep(time.Second * 10) + // Update current render + cb(s, nil) + // Wait for a while + time.Sleep(time.Second * 2) + // Check status s.Status, _, err = s.Status.Get(ctx) + // Report errors if err != nil { cb(nil, err) } + // Update render after status fetch + cb(s, nil) continue } diff --git a/internal/algod/status.go b/internal/algod/status.go index 8297609d..1a2d94e7 100644 --- a/internal/algod/status.go +++ b/internal/algod/status.go @@ -46,7 +46,15 @@ type Status struct { LastRound uint64 `json:"lastRound"` // Catchpoint is a pointer to a string that identifies the current catchpoint for node synchronization or fast catchup. - Catchpoint *string `json:"catchpoint"` + Catchpoint *string `json:"catchpoint"` + CatchpointAccountsTotal int `json:"catchpointAccountsTotal"` + CatchpointAccountsProcessed int `json:"catchpointAccountsProcessed"` + CatchpointAccountsVerified int `json:"catchpointAccountsVerified"` + CatchpointKeyValueTotal int `json:"catchpointKeyValueTotal"` + CatchpointKeyValueProcessed int `json:"catchpointKeyValueProcessed"` + CatchpointKeyValueVerified int `json:"catchpointKeyValueVerified"` + + SyncTime int `json:"syncTime"` // Client provides methods for interacting with the API, adhering to ClientWithResponsesInterface specifications. Client api.ClientWithResponsesInterface `json:"-"` @@ -100,7 +108,15 @@ func (s Status) Merge(res api.StatusLike) Status { if catchpoint != nil && *catchpoint != "" { s.State = FastCatchupState s.Catchpoint = catchpoint + s.SyncTime = res.CatchupTime + s.CatchpointAccountsTotal = *res.CatchpointTotalAccounts + s.CatchpointAccountsProcessed = *res.CatchpointProcessedAccounts + s.CatchpointAccountsVerified = *res.CatchpointVerifiedAccounts + s.CatchpointKeyValueTotal = *res.CatchpointTotalKvs + s.CatchpointKeyValueProcessed = *res.CatchpointProcessedKvs + s.CatchpointKeyValueVerified = *res.CatchpointVerifiedKvs } else if res.CatchupTime > 0 { + s.SyncTime = res.CatchupTime s.State = SyncingState } else { s.State = StableState diff --git a/ui/modal/controller.go b/ui/modal/controller.go index d8095ff1..5a1fb6ae 100644 --- a/ui/modal/controller.go +++ b/ui/modal/controller.go @@ -1,6 +1,7 @@ package modal import ( + "fmt" "github.com/algorandfoundation/algorun-tui/internal/algod" "github.com/algorandfoundation/algorun-tui/internal/algod/participation" "github.com/algorandfoundation/algorun-tui/ui/app" @@ -8,6 +9,7 @@ import ( "github.com/algorandfoundation/algorun-tui/ui/style" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "time" ) // Init initializes the current ViewModel by batching initialization commands for all associated modal ViewModels. @@ -41,8 +43,25 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (*ViewModel, tea.Cmd) { m.transactionModal.State = msg m.infoModal.State = msg - // When the state changes, and we are displaying a valid QR Code/Transaction Modal - if m.Type == app.TransactionModal && m.transactionModal.Participation != nil { + if m.State.Status.State == algod.FastCatchupState { + m.Open = true + m.SetType(app.ExceptionModal) + m.exceptionModal.Message = style.LightBlue(lipgloss.JoinVertical(lipgloss.Top, + fmt.Sprintf("Last committed block: %d", m.State.Status.LastRound), + fmt.Sprintf("Sync Time: %ds", m.State.Status.SyncTime/int(time.Second)), + fmt.Sprintf("Catchpoint: %s", *m.State.Status.Catchpoint), + fmt.Sprintf("Total Accounts: %d", m.State.Status.CatchpointAccountsTotal), + fmt.Sprintf("Accounts Processed: %d", m.State.Status.CatchpointAccountsProcessed), + fmt.Sprintf("Accounts Verified: %d", m.State.Status.CatchpointAccountsVerified), + fmt.Sprintf("Total Key Values: %d", m.State.Status.CatchpointKeyValueTotal), + fmt.Sprintf("Key Values Processed: %d", m.State.Status.CatchpointKeyValueProcessed), + fmt.Sprintf("Key Values Verified: %d", m.State.Status.CatchpointKeyValueVerified), + )) + m.borderColor = "7" + m.controls = "" + m.title = "Fast Catchup" + + } else if m.Type == app.TransactionModal && m.transactionModal.Participation != nil { acct, ok := msg.Accounts[m.Address] // If the previous state is not active if ok { From c322aca5e03def3da47d0d248c26994d55732f4b Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Thu, 2 Jan 2025 11:34:24 -0500 Subject: [PATCH 20/26] test: add fast-catchup state to status --- internal/algod/status_test.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/internal/algod/status_test.go b/internal/algod/status_test.go index 3fb6c7ef..0b817bbc 100644 --- a/internal/algod/status_test.go +++ b/internal/algod/status_test.go @@ -29,7 +29,23 @@ func Test_StatusModel(t *testing.T) { } catchpoint := "catchpoint" - m = m.Merge(api.StatusLike{LastRound: 10, Catchpoint: &catchpoint, CatchupTime: 0}) + accountsTotal := 1000000 + processedAccounts := 0 + verifiedAccounts := 0 + keyValueTotal := 1000 + keyValueProcessed := 0 + keyValueVerified := 0 + m = m.Merge(api.StatusLike{ + LastRound: 10, + Catchpoint: &catchpoint, + CatchupTime: 0, + CatchpointTotalAccounts: &accountsTotal, + CatchpointVerifiedAccounts: &verifiedAccounts, + CatchpointProcessedAccounts: &processedAccounts, + CatchpointTotalKvs: &keyValueTotal, + CatchpointProcessedKvs: &keyValueProcessed, + CatchpointVerifiedKvs: &keyValueVerified, + }) if m.State != FastCatchupState { t.Errorf("expected State: %s, got %s", FastCatchupState, m.State) } From 73c548320880115c83a8073ac10c4a69e6963a4a Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Thu, 2 Jan 2025 17:54:01 +0100 Subject: [PATCH 21/26] fast catchup modal styling --- internal/algod/status.go | 4 ++++ ui/modal/controller.go | 17 +++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/internal/algod/status.go b/internal/algod/status.go index 1a2d94e7..e55ac76c 100644 --- a/internal/algod/status.go +++ b/internal/algod/status.go @@ -53,6 +53,8 @@ type Status struct { CatchpointKeyValueTotal int `json:"catchpointKeyValueTotal"` CatchpointKeyValueProcessed int `json:"catchpointKeyValueProcessed"` CatchpointKeyValueVerified int `json:"catchpointKeyValueVerified"` + CatchpointBlocksTotal int `json:"catchpointTotalBlocks"` + CatchpointBlocksAcquired int `json:"catchpointAcquiredBlocks"` SyncTime int `json:"syncTime"` @@ -115,6 +117,8 @@ func (s Status) Merge(res api.StatusLike) Status { s.CatchpointKeyValueTotal = *res.CatchpointTotalKvs s.CatchpointKeyValueProcessed = *res.CatchpointProcessedKvs s.CatchpointKeyValueVerified = *res.CatchpointVerifiedKvs + s.CatchpointBlocksAcquired = *res.CatchpointAcquiredBlocks + s.CatchpointBlocksTotal = *res.CatchpointTotalBlocks } else if res.CatchupTime > 0 { s.SyncTime = res.CatchupTime s.State = SyncingState diff --git a/ui/modal/controller.go b/ui/modal/controller.go index 5a1fb6ae..e37f1c40 100644 --- a/ui/modal/controller.go +++ b/ui/modal/controller.go @@ -47,15 +47,16 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (*ViewModel, tea.Cmd) { m.Open = true m.SetType(app.ExceptionModal) m.exceptionModal.Message = style.LightBlue(lipgloss.JoinVertical(lipgloss.Top, - fmt.Sprintf("Last committed block: %d", m.State.Status.LastRound), + "Please wait while your node syncs with the network.", + "This process can take up to an hour.", + "", + fmt.Sprintf("Accounts Processed: %d / %d", m.State.Status.CatchpointAccountsProcessed, m.State.Status.CatchpointAccountsTotal), + fmt.Sprintf("Accounts Verified: %d / %d", m.State.Status.CatchpointAccountsVerified, m.State.Status.CatchpointAccountsTotal), + fmt.Sprintf("Key Values Processed: %d / %d", m.State.Status.CatchpointKeyValueProcessed, m.State.Status.CatchpointKeyValueTotal), + fmt.Sprintf("Key Values Verified: %d / %d", m.State.Status.CatchpointKeyValueVerified, m.State.Status.CatchpointKeyValueTotal), + fmt.Sprintf("Downloaded blocks: %d / %d", m.State.Status.CatchpointBlocksAcquired, m.State.Status.CatchpointBlocksTotal), + "", fmt.Sprintf("Sync Time: %ds", m.State.Status.SyncTime/int(time.Second)), - fmt.Sprintf("Catchpoint: %s", *m.State.Status.Catchpoint), - fmt.Sprintf("Total Accounts: %d", m.State.Status.CatchpointAccountsTotal), - fmt.Sprintf("Accounts Processed: %d", m.State.Status.CatchpointAccountsProcessed), - fmt.Sprintf("Accounts Verified: %d", m.State.Status.CatchpointAccountsVerified), - fmt.Sprintf("Total Key Values: %d", m.State.Status.CatchpointKeyValueTotal), - fmt.Sprintf("Key Values Processed: %d", m.State.Status.CatchpointKeyValueProcessed), - fmt.Sprintf("Key Values Verified: %d", m.State.Status.CatchpointKeyValueVerified), )) m.borderColor = "7" m.controls = "" From 66faa100ff26ef385e7035ce98437a0ddd40266a Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Thu, 2 Jan 2025 11:55:01 -0500 Subject: [PATCH 22/26] fix: use auto style for glamour --- cmd/bootstrap.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/bootstrap.go b/cmd/bootstrap.go index 0c85d477..37f833de 100644 --- a/cmd/bootstrap.go +++ b/cmd/bootstrap.go @@ -54,9 +54,11 @@ var bootstrapCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() httpPkg := new(api.HttpPkg) - + r, _ := glamour.NewTermRenderer( + glamour.WithAutoStyle(), + ) fmt.Print(style.Purple(style.BANNER)) - out, err := glamour.Render(tutorial, "dark") + out, err := r.Render(tutorial) if err != nil { return err } From accf6c072bbd3a4252bc4dc5dbbc2c0c2d1b6163 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Thu, 2 Jan 2025 12:03:48 -0500 Subject: [PATCH 23/26] test: fix acquired block pointers --- internal/algod/status_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/algod/status_test.go b/internal/algod/status_test.go index 0b817bbc..57d76688 100644 --- a/internal/algod/status_test.go +++ b/internal/algod/status_test.go @@ -35,10 +35,14 @@ func Test_StatusModel(t *testing.T) { keyValueTotal := 1000 keyValueProcessed := 0 keyValueVerified := 0 + acquiredBlocks := 1000000 + blocksTotal := 10000000 m = m.Merge(api.StatusLike{ LastRound: 10, Catchpoint: &catchpoint, CatchupTime: 0, + CatchpointAcquiredBlocks: &acquiredBlocks, + CatchpointTotalBlocks: &blocksTotal, CatchpointTotalAccounts: &accountsTotal, CatchpointVerifiedAccounts: &verifiedAccounts, CatchpointProcessedAccounts: &processedAccounts, From cdf3d45ef8cb8ddd6cccf232300f6ef5307517bb Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Thu, 2 Jan 2025 14:22:23 -0500 Subject: [PATCH 24/26] fix: clear fast catchup modal --- ui/modal/controller.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/modal/controller.go b/ui/modal/controller.go index e37f1c40..b90c9393 100644 --- a/ui/modal/controller.go +++ b/ui/modal/controller.go @@ -39,6 +39,12 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (*ViewModel, tea.Cmd) { m.SetShortLink(msg) m.SetType(app.TransactionModal) case *algod.StateModel: + // Clear the catchup modal + if msg.Status.State != algod.FastCatchupState && m.Type == app.ExceptionModal && m.title == "Fast Catchup" { + m.Open = false + m.SetType(app.InfoModal) + } + m.State = msg m.transactionModal.State = msg m.infoModal.State = msg From 4bb2b44933a9cede025a7b58363f64909a5abef7 Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Mon, 6 Jan 2025 16:18:57 +0100 Subject: [PATCH 25/26] "node not found" explanation --- cmd/utils/explanations/explanations.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/utils/explanations/explanations.go b/cmd/utils/explanations/explanations.go index 216c680b..167cae3f 100644 --- a/cmd/utils/explanations/explanations.go +++ b/cmd/utils/explanations/explanations.go @@ -10,9 +10,9 @@ var NodeNotFound = lipgloss.JoinHorizontal(lipgloss.Left, style.Cyan.Render("Explanation"), style.Bold(": "), ) + - "algorun could not find your node automatically. (ensure the node is running)\n\n" + +"algorun could not find your node automatically. Ensure the node is installed and running: If you have not installed algod yet, run \"nodekit bootstrap\". If your node is installed, start it with \"nodekit start\". \n\n" + lipgloss.JoinHorizontal(lipgloss.Left, - "Provide ", + "Otherwise for custom installations, provide ", style.Bold("--datadir"), " or set the goal-compatible ", style.Bold("ALGORAND_DATA"), From 1d4b59684ae39ec1ba1235d34b105028aecf8aea Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Mon, 6 Jan 2025 16:46:03 +0100 Subject: [PATCH 26/26] go fmt --- cmd/utils/explanations/explanations.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/utils/explanations/explanations.go b/cmd/utils/explanations/explanations.go index 167cae3f..8ca94b8b 100644 --- a/cmd/utils/explanations/explanations.go +++ b/cmd/utils/explanations/explanations.go @@ -10,7 +10,7 @@ var NodeNotFound = lipgloss.JoinHorizontal(lipgloss.Left, style.Cyan.Render("Explanation"), style.Bold(": "), ) + -"algorun could not find your node automatically. Ensure the node is installed and running: If you have not installed algod yet, run \"nodekit bootstrap\". If your node is installed, start it with \"nodekit start\". \n\n" + + "algorun could not find your node automatically. Ensure the node is installed and running: If you have not installed algod yet, run \"nodekit bootstrap\". If your node is installed, start it with \"nodekit start\". \n\n" + lipgloss.JoinHorizontal(lipgloss.Left, "Otherwise for custom installations, provide ", style.Bold("--datadir"),