From 3472ca997596d674ad4e7261c5503e11a0fa4f1b Mon Sep 17 00:00:00 2001 From: Sam Bukowski Date: Mon, 22 Apr 2024 15:44:04 -0600 Subject: [PATCH] Feature: Add ability to control which binaries are run (#52) * add run-mono-repo command * remove all logging related changes * refactor run command * removed unneeded additional run commands * path cleanup * updated panic message * flag name updates, readme updates * remove unneeded constant * readme cleanup * reduce complexity (#53) * reduce complexity * add -path suffix back --------- Co-authored-by: Sam Bukowski * update helper functions * getFlagPathOrPanic comment update Co-authored-by: jesse snyder * getFlagPathOrPanic comment update Co-authored-by: jesse snyder * Refactor/monorepo usage docs (#55) * move some sections around * Update README.md * lint cleanup * Update README.md * add generated files to readme --------- Co-authored-by: Sam Bukowski * update helper functions * update helper function logging --------- Co-authored-by: jesse snyder --- README.md | 126 ++++- cmd/devtools/helpers.go | 47 ++ cmd/devtools/init.go | 2 +- cmd/devtools/run-legacy.go | 939 ------------------------------------- cmd/devtools/run.go | 299 +++++++----- 5 files changed, 335 insertions(+), 1078 deletions(-) delete mode 100644 cmd/devtools/run-legacy.go diff --git a/README.md b/README.md index 30bab391..49811fe0 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,13 @@ the Astria stack and interact with the Sequencer. * [Available Commands](#available-commands) * [Installation](#installation) - * [Install and Run CLI from GitHub release](#install-and-run-cli-from-github-release) - * [Locally Build and Run the CLI](#locally-build-and-run-the-cli) + * [Install from GitHub release](#install-from-github-release) + * [Build Locally from Source](#build-locally-from-source) +* [Running the Astria Sequencer](#running-the-astria-sequencer) + * [Initialize Configuration](#initialize-configuration) + * [Usage](#usage) + * [Run a Local Sequencer](#run-a-local-sequencer) + * [Run Against a Remote Sequencer](#run-against-a-remote-sequencer) * [Instances](#instances) * [Development](#development) * [Testing](#testing) @@ -31,7 +36,7 @@ the Astria stack and interact with the Sequencer. ## Installation -### Install and Run CLI from GitHub release +### Install from GitHub release The CLI binaries are available for download from the [releases page](https://github.com/astriaorg/astria-cli-go/releases). There are @@ -50,7 +55,7 @@ tar -xzvf astria-go.tar.gz mv astria-go /usr/local/bin/ ``` -### Locally Build and Run the CLI +### Build Locally from Source Dependencies: (only required for development) @@ -58,26 +63,121 @@ Dependencies: (only required for development) * [just](https://github.com/casey/just) ```bash +# checkout repo git clone git@github.com:astriaorg/astria-cli-go.git cd astria-cli-go + +# run build command just build -just run "dev init" -just run "dev run" + +# check the version +just run "version" +# or +go run main.go version +``` + +## Running the Astria Sequencer + +### Initialize Configuration + +```bash +astria-go dev init ``` -This will download, configure, and run the following binaries of these -applications: +The `init` command downloads binaries, generates environment and +configuration files, and initializes CometBFT. + +The following files are generated: + +* In `~/.astria//config-local`: + * A `.env` file for local configuration + * `genesis.json` for the sequencer genesis + * `priv_validator_key.json` for configuring the sequencer validators +* In `~/.astria//config-remote`: + * A `.env` file for remote configuration + +The following binaries are downloaded: | App | Version | -| ---------------- |---------| +|------------------|---------| | Cometbft | v0.37.4 | | Astria-Sequencer | v0.10.1 | | Astria-Conductor | v0.13.1 | | Astria-Composer | v0.5.0 | +The `init` command will also run the initialization steps required by CometBFT, using the `genesis.json` and +`priv_validator_key.json` files in the `config-local` directory. This will +create a `.cometbft` directory in `~/.astria//data`. + +### Usage + The cli runs the minimum viable components for testing a rollup against the Astria stack, allowing developers to confirm that their rollup interacts with -Astria's apis correctly. +Astria's APIs correctly. + +You can choose to run the Sequencer locally, or you can run the stack against +the remote Sequencer. You may also run local binaries instead of downloaded +pre-built binaries. + +#### Run a Local Sequencer + +The simplest way to run Astria: + +```bash +astria-go dev run +``` + +This will spin up a Sequencer (Cometbft and Astria-Sequencer), a Conductor, +and a Composer -- all on your local machine, using pre-built binaries of the +dependencies. + +> NOTE: Running a local Sequencer is the default behavior of `dev run` command. +> Thus, `astria-go dev run` is effectively an alias of +> `astria-go dev run --local`. + +#### Run Against a Remote Sequencer + +If you want to run Composer and Conductor locally against a remote Astria +Sequencer: + +```bash +astria-go dev run --remote +``` + +Using the `--remote` flag, the cli will handle configuration of the components +running on your local machine, but you will need to create an account on the +remote sequencer. More details can be +[found here](https://docs.astria.org/developer/tutorials/1-using-astria-go-cli#setup-and-run-the-local-astria-components-to-communicate-with-the-remote-sequencer). + +### Run Custom Binaries + +You can also run components of Astria from a local monorepo during development +of Astria core itself. For example if you are developing a new feature in the +[`astria-conductor` crate](https://github.com/astriaorg/astria/tree/main/crates/astria-conductor) +in the [Astria mono repo](https://github.com/astriaorg/astria) you can use the +cli to run your locally compiled Conductor with the other components using the +`--conductor-path` flag: + +```bash +astria-go dev run --local \ + --conductor-path /target/debug/astria-conductor +``` + +This will run Composer, Cometbft, and Sequencer using the downloaded pre-built +binaries, while using a locally built version of the Conductor binary. You can +swap out some or all binaries for the Astria stack with their appropriate flags: + +```bash +astria-go dev run --local \ + --sequencer-path \ + --cometbft-path \ + --composer-path \ + --conductor-path +``` + +If additional configuration is required, you can update the `.env` files in +`~/.astria//config-local/` or +`~/.astria//config-remote/` based on your needs. ## Instances @@ -99,9 +199,9 @@ You will see the following in the `~/.astria` directory: ```bash .astria/ - default/ - hello/ - world/ + default/ + hello/ + world/ ``` Each of these directories will contain configs and binaries for diff --git a/cmd/devtools/helpers.go b/cmd/devtools/helpers.go index 86e13a52..11cc3af3 100644 --- a/cmd/devtools/helpers.go +++ b/cmd/devtools/helpers.go @@ -5,9 +5,29 @@ import ( "os" "regexp" + "github.com/joho/godotenv" log "github.com/sirupsen/logrus" ) +// GetEnvironment reads the environment variables from the file at filePath and +// returns a list of environment variables in the form key=value. It will panic +// if the file can't be loaded. +func GetEnvironment(filePath string) []string { + envMap, err := godotenv.Read(filePath) + if err != nil { + log.Fatalf("Error loading environment file: %v", err) + } + if err != nil { + panic(fmt.Sprintf("Error loading environment file: %v", err)) + } + var envList []string + for key, value := range envMap { + envList = append(envList, key+"="+value) + } + return envList +} + +// IsInstanceNameValidOrPanic checks if the instance name is valid and panics if it's not. func IsInstanceNameValidOrPanic(instance string) { re, err := regexp.Compile(`^[a-z]+[a-z0-9]*(-[a-z0-9]+)*$`) if err != nil { @@ -32,5 +52,32 @@ func CreateDirOrPanic(dirName string) { log.WithError(err).Error("Error creating data directory") panic(err) } +} + +// PathExists checks if the file or binary for the input path is a regular file +// and is executable. A regular file is one where no mode type bits are set. +func PathExists(path string) bool { + fileInfo, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + log.WithError(err).Error("File does not exist") + } else { + log.WithError(err).Error("Error checking file") + } + return false + } + + // Check if it's a regular file + if !fileInfo.Mode().IsRegular() { + log.WithField("path", path).Error("The path is not a regular file") + return false + } + + // Check if the file is executable + if fileInfo.Mode().Perm()&0111 == 0 { + log.WithField("path", path).Error("The file is not executable") + return false + } + return true } diff --git a/cmd/devtools/init.go b/cmd/devtools/init.go index c358f4b9..fdc6efa8 100644 --- a/cmd/devtools/init.go +++ b/cmd/devtools/init.go @@ -306,7 +306,7 @@ func initCometbft(defaultDir string, dataDirName string, binDirName string, conf // verify that cometbft was downloaded and extracted to the correct location cometbftCmdPath := filepath.Join(defaultDir, binDirName, "cometbft") - if !exists(cometbftCmdPath) { + if !PathExists(cometbftCmdPath) { log.Error("Error: cometbft binary not found here", cometbftCmdPath) log.Error("\tCannot continue with initialization.") return diff --git a/cmd/devtools/run-legacy.go b/cmd/devtools/run-legacy.go deleted file mode 100644 index 64f186e0..00000000 --- a/cmd/devtools/run-legacy.go +++ /dev/null @@ -1,939 +0,0 @@ -package devtools - -import ( - "bufio" - "fmt" - "io" - "log" - "net" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" - "syscall" - - "github.com/gdamore/tcell/v2" - "github.com/joho/godotenv" - "github.com/rivo/tview" - "github.com/spf13/cobra" -) - -// runLegacyCmd represents the run-legacy command -var runLegacyCmd = &cobra.Command{ - Use: "run-legacy", - Short: "TODO: short description", - Long: `TODO: long description`, - Run: func(cmd *cobra.Command, args []string) { - run() - }, -} - -// loadEnvVariables loads the environment variables from the src file -func loadEnvVariables(src string) { - err := godotenv.Load(src) - if err != nil { - log.Fatalf("Error loading .env file: %v", err) - } -} - -// getEnvList returns a list of environment variables in the form key=value -func getEnvList() []string { - var envList []string - for _, env := range os.Environ() { - // Each string is in the form key=value - pair := strings.SplitN(env, "=", 2) - key := pair[0] - envList = append(envList, key+"="+os.Getenv(key)) - } - return envList -} - -// loadAndGetEnvVariables loads the environment variables from the src file and returns a list of environment variables in the form key=value -func loadAndGetEnvVariables(filePath string) []string { - loadEnvVariables(filePath) - return getEnvList() -} - -func checkPortInUse(port int) bool { - // TODO: make this check more sophisticated then just "is the port in use" - address := fmt.Sprintf(":%d", port) - ln, err := net.Listen("tcp", address) - if err != nil { - // If we cannot listen on the port, it is likely in use - return true - } - // Don't forget to close the listener if the port is not in use - ln.Close() - return false -} - -// exists checks if a file or directory exists -func exists(path string) bool { - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - return false // The file or directory does not exist - } - } - return true // The file or directory exists -} - -// checkIfInitialized checks if the files required for local development are present -func checkIfInitialized(path string) bool { - // all dirs/files that should exist - filePaths := []string{ - "local-dev-astria/.env", - "local-dev-astria/astria-sequencer", - "local-dev-astria/astria-conductor", - "local-dev-astria/astria-composer", - "local-dev-astria/cometbft", - "local-dev-astria/genesis.json", - "local-dev-astria/priv_validator_key.json", - "data", - } - status := true - - for _, fp := range filePaths { - expandedPath := filepath.Join(path, fp) - if !exists(expandedPath) { - fmt.Println("no", fp, "found") - status = false - } - } - return status -} - -func run() { - // Create channels to properly control the start order of all processes - sequencerStartComplete := make(chan bool) - cometbftStartComplete := make(chan bool) - composerStartComplete := make(chan bool) - - // TODO: make the home dir name configuratble - homePath, err := os.UserHomeDir() - if err != nil { - fmt.Println("error getting home dir:", err) - return - } - defaultDir := filepath.Join(homePath, ".astria") - - // Load the .env file and get the environment variables - envPath := filepath.Join(defaultDir, "local-dev-astria/.env") - environment := loadAndGetEnvVariables(envPath) - - // Check if a rollup is running on the default port - // TODO: make the port configurable - rollupExecutionPort := 50051 - if !checkPortInUse(rollupExecutionPort) { - fmt.Printf("Error: no rollup execution rpc detected on port %d\n", rollupExecutionPort) - return - } - // TODO: make the port configurable - rollupRpcPort := 8546 - if !checkPortInUse(rollupRpcPort) { - fmt.Printf("Error: no rollup rpc detected on port %d\n", rollupRpcPort) - return - } - // check if the `dev init` command has been run - if !checkIfInitialized(defaultDir) { - fmt.Println("Error: one or more required files not present. Did you run 'astria-dev init'?") - return - } - - app := tview.NewApplication() - - // create text view object for the sequencer - sequencerTextView := tview.NewTextView(). - SetDynamicColors(true). - SetScrollable(true). - SetChangedFunc(func() { - app.Draw() - }) - sequencerTextView.SetTitle(" Sequencer ").SetBorder(true) - sequencerTVRowCount := 0 - - // create text view object for the cometbft - cometbftTextView := tview.NewTextView(). - SetDynamicColors(true). - SetScrollable(true). - SetChangedFunc(func() { - app.Draw() - }) - cometbftTextView.SetTitle(" Cometbft ").SetBorder(true) - cometbftTVRowCount := 0 - - // create text view object for the composer - composerTextView := tview.NewTextView(). - SetDynamicColors(true). - SetScrollable(true). - SetChangedFunc(func() { - app.Draw() - }) - composerTextView.SetTitle(" Composer ").SetBorder(true) - composerTVRowCount := 0 - - // create text view object for the conductor - conductorTextView := tview.NewTextView(). - SetDynamicColors(true). - SetScrollable(true). - SetChangedFunc(func() { - app.Draw() - }) - conductorTextView.SetTitle(" Conductor ").SetBorder(true) - conductorTVRowCount := 0 - - // app settings - isFullscreen := false // controlled by the 'enter' and 'esc' keys - isAutoScrolling := true // controlled by the 's' key - wordWrapEnabled := true // controlled by the 'w' key - isBorderlessLog := false // controlled by the 'b' key - var focusedItem tview.Primitive = sequencerTextView - - helpTextHelp := "(h)elp" - helpTextQuit := "(q)uit" - helpTextFocus := "(up/down) arrows to select app focus" - helpTextEnterFullscreen := "(enter) to go fullscreen on focused app" - helpTextExitFullscreen := "(esc) to exit fullscreen" - helpTextWordWrap := "(w)ord wrap" - helpTextAutoScroll := "(a)utoscroll" - helpTextBorderless := "(b)oarderless" - helpTextLogScroll := "if not auto scrolling: (up/down) arrows or mousewheel to scroll" - helpTextHead := "(0) jump to head" - helpTextTail := "(1) jump to tail" - - appendStatus := func(text string, status bool) string { - output := "" - output += text - if status { - output += " - [black:green] on [-:-]" - } else { - output += " - [white:darkred] off [-:-]" - } - return output - } - - buildMainWindowHelpInfo := func() string { - output := " " - output += helpTextHelp + " | " - output += helpTextQuit + " | " - output += helpTextFocus + " | " - output += helpTextEnterFullscreen + " | " - output += appendStatus(helpTextWordWrap, wordWrapEnabled) + " | " - output += appendStatus(helpTextAutoScroll, isAutoScrolling) - return output - } - - buildFullscreenHelpInfo := func() string { - output := " " - output += helpTextHelp + " | " - output += helpTextQuit + " | " - output += helpTextExitFullscreen + " | " - output += appendStatus(helpTextWordWrap, wordWrapEnabled) + " | " - output += appendStatus(helpTextAutoScroll, isAutoScrolling) + " | " - output += appendStatus(helpTextBorderless, isBorderlessLog) + " | " - output += helpTextTail + " | " - output += helpTextHead + " | " - output += helpTextLogScroll - return output - } - - buildMainHelpScreenText := func() string { - output := "Navigation:\t\n" - output += "\t[:darkslategray]tab[:-]: Cycle the focus to the next app.\n" - output += "\t[:darkslategray]up/down[:-] arrows: [yellow:][When in main window][-:] Change focus to the previous or next app.\n" - output += "\t[:darkslategray]up/down[:-] arrows: [yellow:][When in focued window with autoscroll OFF][-:] Go up or down a line in the focued logs.\n" - output += "\t[:darkslategray]mouse scroll[:-]: [yellow:][When in focued window with autoscroll OFF][-:] Scroll up or down in the focued logs.\n\n" - output += "Focus Control:\n" - output += "\t[:darkslategray]enter[:-]: Go from the main screen to fullscreen on the focused app's logs.\n" - output += "\t[:darkslategray]esc[:-]: Go from the fullscreened log view back to the main window.\n\n" - output += "Word wrap:\n" - output += "\t[:darkslategray]w[:-]: Toggle if word wrap is on or off.\n\n" - output += "Logs Controls:\n" - output += "\t[:darkslategray]a[:-]: Toggle if autoscroll is on or off.\n" - output += "\t[:darkslategray]1[:-]: [yellow:][When in focued window][-:] Jump to tail of logs and disable autoscrolling.\n" - output += "\t[:darkslategray]0[:-]: [yellow:][When in focued window][-:] Jump to head of logs and disable autoscrolling.\n\n" - output += "Borderless:\n" - output += "\t[:darkslategray]b[:-]: [yellow:][When in focued window][-:] Toggle the border around the logs on or off.\n\n" - output += "Quitting:\n" - output += "\t[:darkslategray]q[:-]: Quit the app.\n" - output += "\t[:darkslategray]ctrl-c[:-]: Quit the app.\n\n" - output += "Help:\n" - output += "\t[:darkslategray]h[:-]: Show this help screen or return to previous window.\n\n" - return output - } - - helpscreenHelpInfo := tview.NewTextView(). - SetDynamicColors(true). - SetText("(q)uit | (h) to return to previous window"). - SetChangedFunc(func() { - app.Draw() - }) - - helpMainWindowInfo := tview.NewTextView(). - SetDynamicColors(true). - SetText(buildMainHelpScreenText()). - SetWrap(true). - SetChangedFunc(func() { - app.Draw() - }) - helpMainWindowInfo.SetTitle(" Astria CLI Help ").SetBorder(true).SetBorderColor(tcell.ColorBlue).SetBorderPadding(0, 0, 1, 0) - helpScreenFlex := tview.NewFlex().AddItem(tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(helpMainWindowInfo, 0, 1, true). - AddItem(helpscreenHelpInfo, 1, 0, false), 0, 4, false) - - mainWindowHelpInfo := tview.NewTextView(). - SetDynamicColors(true). - SetText(buildMainWindowHelpInfo()). - SetChangedFunc(func() { - app.Draw() - }) - - fullscreenHelpInfo := tview.NewTextView(). - SetDynamicColors(true). - SetText(buildFullscreenHelpInfo()). - SetChangedFunc(func() { - app.Draw() - }) - - mainWindow := tview.NewFlex(). - AddItem(tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(sequencerTextView, 0, 1, true). - AddItem(cometbftTextView, 0, 1, false). - AddItem(composerTextView, 0, 1, false). - AddItem(conductorTextView, 0, 1, false), 0, 4, false).SetDirection(tview.FlexRow). - AddItem(mainWindowHelpInfo, 1, 0, false) - mainWindow.SetTitle(" Astria Dev ").SetBorder(true) - - // prevWindow is used for toggling in and out of the help window - var prevWindow tview.Primitive = mainWindow - - // Create ANSI writers for the text views - aWriterSequencerTextView := tview.ANSIWriter(sequencerTextView) - aWriterCometbftTextView := tview.ANSIWriter(cometbftTextView) - aWriterComposerTextView := tview.ANSIWriter(composerTextView) - aWriterConductorTextView := tview.ANSIWriter(conductorTextView) - - // start the app with auto scrolling enabled - sequencerTextView.ScrollToEnd() - cometbftTextView.ScrollToEnd() - composerTextView.ScrollToEnd() - conductorTextView.ScrollToEnd() - appendText := func(text string, writer io.Writer) { - writer.Write([]byte(text + "\n")) - } - - // Create a list of items to cycle through - items := []tview.Primitive{sequencerTextView, cometbftTextView, composerTextView, conductorTextView} - currentIndex := 0 - - setFocus := func(index int) { - currentIndex = index - for i, item := range items { - // Use a type assertion to convert the tview.Primitive back to *tview.TextView - frame, ok := item.(*tview.TextView) - if !ok { - // The item is not a *tview.TextView, so skip it. - continue - } - if i == index { - title := frame.GetTitle() - title = "[black:green]" + title + "[::-]" - frame.SetBorderColor(tcell.ColorGreen).SetTitle(title) - } else { - title := frame.GetTitle() - regexPattern := `\[.*?\]` - re, err := regexp.Compile(regexPattern) - if err != nil { - fmt.Println("Error compiling regex:", err) - return - } - title = re.ReplaceAllString(title, "") - frame.SetBorderColor(tcell.ColorGray).SetTitle(title) - } - } - - app.SetFocus(items[index]) - } - setFocus(currentIndex) - - // create the sequencer run command - sequencerBinPath := filepath.Join(homePath, ".astria/local-dev-astria/astria-sequencer") - seqCmd := exec.Command(sequencerBinPath) - seqCmd.Env = environment - - // create the cometbft run command - cometbftDataPath := filepath.Join(homePath, ".astria/data/.cometbft") - cometbftCmdPath := filepath.Join(homePath, ".astria/local-dev-astria/cometbft") - nodeCmdArgs := []string{"node", "--home", cometbftDataPath} - cometbftCmd := exec.Command(cometbftCmdPath, nodeCmdArgs...) - cometbftCmd.Env = environment - - // create the composer run command - composerBinPath := filepath.Join(homePath, ".astria/local-dev-astria/astria-composer") - composerCmd := exec.Command(composerBinPath) - composerCmd.Env = environment - - // create the conductor run command - conductorBinPath := filepath.Join(homePath, ".astria/local-dev-astria/astria-conductor") - conductorCmd := exec.Command(conductorBinPath) - conductorCmd.Env = environment - - var mainWindowInputCapture, focusWindowInputCapture, helpWindowInputCapture func(event *tcell.EventKey) *tcell.EventKey - var prevInputCapture func(event *tcell.EventKey) *tcell.EventKey - - // create the input capture for the app in fullscreen - mainWindowInputCapture = func(event *tcell.EventKey) *tcell.EventKey { - // properly handle ctrl-c and pass SIGINT to the running processes - if event.Key() == tcell.KeyCtrlC { - if err := seqCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := cometbftCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := composerCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := conductorCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - app.Stop() - return nil - } - // set 'q' to exit the app and pass SIGINT to the running processes - if event.Key() == tcell.KeyRune && (event.Rune() == 'q' || event.Rune() == 'Q') { - if err := seqCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := cometbftCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := composerCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := conductorCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - app.Stop() - return nil - } - // set 'w' to toggle word wrap - if event.Key() == tcell.KeyRune && (event.Rune() == 'w' || event.Rune() == 'W') { - sequencerTextView.SetWrap(!wordWrapEnabled) - cometbftTextView.SetWrap(!wordWrapEnabled) - composerTextView.SetWrap(!wordWrapEnabled) - conductorTextView.SetWrap(!wordWrapEnabled) - wordWrapEnabled = !wordWrapEnabled - - mainWindowHelpInfo.SetText(buildMainWindowHelpInfo()) - fullscreenHelpInfo.SetText(buildFullscreenHelpInfo()) - - return nil - } - // set 'a' to toggle auto scrolling - if event.Key() == tcell.KeyRune && (event.Rune() == 'a' || event.Rune() == 'A') { - isAutoScrolling = !isAutoScrolling - if isAutoScrolling { - sequencerTextView.ScrollToEnd() - cometbftTextView.ScrollToEnd() - composerTextView.ScrollToEnd() - conductorTextView.ScrollToEnd() - } else { - currentOffset, _ := sequencerTextView.GetScrollOffset() - sequencerTextView.ScrollTo(currentOffset, 0) - currentOffset, _ = cometbftTextView.GetScrollOffset() - cometbftTextView.ScrollTo(currentOffset, 0) - currentOffset, _ = composerTextView.GetScrollOffset() - composerTextView.ScrollTo(currentOffset, 0) - currentOffset, _ = conductorTextView.GetScrollOffset() - conductorTextView.ScrollTo(currentOffset, 0) - } - - mainWindowHelpInfo.SetText(buildMainWindowHelpInfo()) - fullscreenHelpInfo.SetText(buildFullscreenHelpInfo()) - - return nil - } - - // TODO: add a key to just to head of logs (automatically turn off auto scrolling) - - // set 'tab' to cycle through the apps - if event.Key() == tcell.KeyTab && !isFullscreen { - newIndex := (currentIndex + 1) % len(items) - setFocus(newIndex) - return nil - } - // set 'enter' to go fullscreen on the selected app - if event.Key() == tcell.KeyEnter && !isFullscreen { - isFullscreen = true - frame, ok := items[currentIndex].(*tview.TextView) - if !ok { - return event - } - fullscreenFlex := tview.NewFlex().AddItem(tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(frame, 0, 1, true). - AddItem(fullscreenHelpInfo, 1, 0, false), 0, 4, false) - prevWindow = fullscreenFlex - prevInputCapture = focusWindowInputCapture - app.SetRoot(fullscreenFlex, true) - app.SetInputCapture(nil) - app.SetInputCapture(focusWindowInputCapture) - return nil - } - - // set 'up' arrow to cycle through the apps - if event.Key() == tcell.KeyUp && !isFullscreen { - if currentIndex > 0 { - newIndex := (currentIndex - 1) % len(items) - setFocus(newIndex) - } else { - setFocus(0) - } - return nil - } - // set 'down' arrow to cycle through the apps - if event.Key() == tcell.KeyDown && !isFullscreen { - if currentIndex == len(items)-1 { - setFocus(currentIndex) - } else { - newIndex := (currentIndex + 1) % len(items) - setFocus(newIndex) - } - return nil - - } - - if event.Key() == tcell.KeyRune && (event.Rune() == 'h' || event.Rune() == 'H') { - prevWindow = mainWindow - prevInputCapture = mainWindowInputCapture - app.SetInputCapture(nil) - app.SetInputCapture(helpWindowInputCapture) - app.SetRoot(helpScreenFlex, true) - } - return event - } - prevInputCapture = mainWindowInputCapture - - // set the input capture for the app in app focus mode - focusWindowInputCapture = func(event *tcell.EventKey) *tcell.EventKey { - // get the focused item - frame, ok := items[currentIndex].(*tview.TextView) - if !ok { - return event - } - // properly handle ctrl-c and pass SIGINT to the running processes - if event.Key() == tcell.KeyCtrlC { - if err := seqCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := cometbftCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := composerCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := conductorCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - app.Stop() - return nil - } - // set 'q' to exit the app and pass SIGINT to the running processes - if event.Key() == tcell.KeyRune && (event.Rune() == 'q' || event.Rune() == 'Q') { - if err := seqCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := cometbftCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := composerCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := conductorCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - app.Stop() - return nil - } - // set 'w' to toggle word wrap - if event.Key() == tcell.KeyRune && (event.Rune() == 'w' || event.Rune() == 'W') { - sequencerTextView.SetWrap(!wordWrapEnabled) - cometbftTextView.SetWrap(!wordWrapEnabled) - composerTextView.SetWrap(!wordWrapEnabled) - conductorTextView.SetWrap(!wordWrapEnabled) - wordWrapEnabled = !wordWrapEnabled - - mainWindowHelpInfo.SetText(buildMainWindowHelpInfo()) - fullscreenHelpInfo.SetText(buildFullscreenHelpInfo()) - - return nil - } - // set 'a' to toggle auto scrolling - if event.Key() == tcell.KeyRune && (event.Rune() == 'a' || event.Rune() == 'A') { - isAutoScrolling = !isAutoScrolling - if isAutoScrolling { - sequencerTextView.ScrollToEnd() - cometbftTextView.ScrollToEnd() - composerTextView.ScrollToEnd() - conductorTextView.ScrollToEnd() - } else { - // stop auto scrolling and allow the user to scroll manually - currentOffset, _ := sequencerTextView.GetScrollOffset() - sequencerTextView.ScrollTo(currentOffset, 0) - currentOffset, _ = cometbftTextView.GetScrollOffset() - cometbftTextView.ScrollTo(currentOffset, 0) - currentOffset, _ = composerTextView.GetScrollOffset() - composerTextView.ScrollTo(currentOffset, 0) - currentOffset, _ = conductorTextView.GetScrollOffset() - conductorTextView.ScrollTo(currentOffset, 0) - } - - mainWindowHelpInfo.SetText(buildMainWindowHelpInfo()) - fullscreenHelpInfo.SetText(buildFullscreenHelpInfo()) - - return nil - } - - // TODO: add a key to just to head of logs (automatically turn off auto scrolling) - - // set 'esc' to exit fullscreen - if event.Key() == tcell.KeyEscape && isFullscreen { - isFullscreen = false - frame, ok := items[currentIndex].(*tview.TextView) - if !ok { - return event - } - // clear settings on the focused item - frame.SetInputCapture(nil) - frame.SetMouseCapture(nil) - // reenable the border on the focused item so it shows up in the main window - frame.SetBorder(true) - isBorderlessLog = false - fullscreenHelpInfo.SetText(buildFullscreenHelpInfo()) - // set the app back to the main window and update the prev input and - // window for working with the help window - prevInputCapture = mainWindowInputCapture - prevWindow = mainWindow - app.SetRoot(mainWindow, true) - app.SetInputCapture(mainWindowInputCapture) - return nil - } - - switch event.Key() { - case tcell.KeyUp: - if !isAutoScrolling { - row, _ := frame.GetScrollOffset() - frame.ScrollTo(row-1, 0) - return nil - } - case tcell.KeyDown: - if !isAutoScrolling { - row, _ := frame.GetScrollOffset() - frame.ScrollTo(row+1, 0) - return nil - } - } - frame.SetMouseCapture(func(action tview.MouseAction, event *tcell.EventMouse) (tview.MouseAction, *tcell.EventMouse) { - switch action { - case tview.MouseScrollUp: - if !isAutoScrolling { - row, _ := frame.GetScrollOffset() - frame.ScrollTo(row-1, 0) - } - return action, event - case tview.MouseScrollDown: - if !isAutoScrolling { - row, _ := frame.GetScrollOffset() - frame.ScrollTo(row+1, 0) - } - return action, event - } - return action, event - }) - // jump to help screen - if event.Key() == tcell.KeyRune && (event.Rune() == 'h' || event.Rune() == 'H') { - app.SetInputCapture(nil) - app.SetInputCapture(helpWindowInputCapture) - app.SetRoot(helpScreenFlex, true) - } - // toggle the border on the longs with 'b' - if event.Key() == tcell.KeyRune && (event.Rune() == 'b' || event.Rune() == 'B') { - isBorderlessLog = !isBorderlessLog - if isBorderlessLog { - frame, ok := items[currentIndex].(*tview.TextView) - if !ok { - return event - } - // TODO: make the set focus stuff below into a function - fullscreenHelpInfo.SetText(buildFullscreenHelpInfo()) - frame.SetBorder(false) - fullscreenFlex := tview.NewFlex().AddItem(tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(frame, 0, 1, true). - AddItem(fullscreenHelpInfo, 1, 0, false), 0, 4, false) - prevWindow = fullscreenFlex - prevInputCapture = focusWindowInputCapture - app.SetRoot(fullscreenFlex, true) - } else { - frame, ok := items[currentIndex].(*tview.TextView) - if !ok { - return event - } - // TODO: make the set focus stuff below into a function - fullscreenHelpInfo.SetText(buildFullscreenHelpInfo()) - frame.SetBorder(true) - fullscreenFlex := tview.NewFlex().AddItem(tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(frame, 0, 1, true). - AddItem(fullscreenHelpInfo, 1, 0, false), 0, 4, false) - prevWindow = fullscreenFlex - prevInputCapture = focusWindowInputCapture - app.SetRoot(fullscreenFlex, true) - } - return nil - } - // using '0' for head, 'h' already in use for help - if event.Key() == tcell.KeyRune && (event.Rune() == '0' || event.Rune() == ')') { - // disable auto scrolling and jump to the head of the logs - isAutoScrolling = false - sequencerTextView.ScrollToBeginning() - cometbftTextView.ScrollToBeginning() - composerTextView.ScrollToBeginning() - conductorTextView.ScrollToBeginning() - - mainWindowHelpInfo.SetText(buildMainWindowHelpInfo()) - fullscreenHelpInfo.SetText(buildFullscreenHelpInfo()) - return nil - } - if event.Key() == tcell.KeyRune && (event.Rune() == '1' || event.Rune() == '!') { - // disable auto scrolling and jump to the tail of the logs - // stop auto scrolling and allow the user to scroll manually - isAutoScrolling = false - - sequencerTextView.ScrollTo(sequencerTVRowCount, 0) - cometbftTextView.ScrollTo(cometbftTVRowCount, 0) - composerTextView.ScrollTo(composerTVRowCount, 0) - conductorTextView.ScrollTo(conductorTVRowCount, 0) - - mainWindowHelpInfo.SetText(buildMainWindowHelpInfo()) - fullscreenHelpInfo.SetText(buildFullscreenHelpInfo()) - return nil - } - return event - } - - helpWindowInputCapture = func(event *tcell.EventKey) *tcell.EventKey { - // properly handle ctrl-c and pass SIGINT to the running processes - if event.Key() == tcell.KeyCtrlC { - if err := seqCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := cometbftCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := composerCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := conductorCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - app.Stop() - return nil - } - // set 'q' to exit the app and pass SIGINT to the running processes - if event.Key() == tcell.KeyRune && (event.Rune() == 'q' || event.Rune() == 'Q') { - if err := seqCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := cometbftCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := composerCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - if err := conductorCmd.Process.Signal(syscall.SIGINT); err != nil { - fmt.Println("Failed to send SIGINT to the process:", err) - } - app.Stop() - return nil - } - if event.Key() == tcell.KeyRune && (event.Rune() == 'h' || event.Rune() == 'H') { - app.SetInputCapture(nil) - app.SetInputCapture(prevInputCapture) - app.SetRoot(prevWindow, true) - } - return event - } - - // set the input capture for the app - app.SetInputCapture(mainWindowInputCapture) - - // go routine for running the sequencer - go func() { - // Get a pipe to the command's output. - stdout, err := seqCmd.StdoutPipe() - if err != nil { - panic(err) - } - stderr, err := seqCmd.StderrPipe() - if err != nil { - panic(err) - } - // Create a scanner to read the output line by line. - output := io.MultiReader(stdout, stderr) - outputScanner := bufio.NewScanner(output) - - if err := seqCmd.Start(); err != nil { - panic(err) - } - - // let the cometbft go routine know that it can start - sequencerStartComplete <- true - - for outputScanner.Scan() { - line := outputScanner.Text() - app.QueueUpdateDraw(func() { - appendText(line, aWriterSequencerTextView) - sequencerTVRowCount++ - }) - } - if err := outputScanner.Err(); err != nil { - panic(err) - } - if err := seqCmd.Wait(); err != nil { - panic(err) - } - }() - - // go routine for running cometbft - go func() { - <-sequencerStartComplete - - stdout, err := cometbftCmd.StdoutPipe() - if err != nil { - panic(err) - } - stderr, err := cometbftCmd.StderrPipe() - if err != nil { - panic(err) - } - // Create a scanner to read the output line by line. - output := io.MultiReader(stdout, stderr) - outputScanner := bufio.NewScanner(output) - - if err := cometbftCmd.Start(); err != nil { - panic(err) - } - - // let the composer go routine know that it can start - cometbftStartComplete <- true - - for outputScanner.Scan() { - line := outputScanner.Text() - app.QueueUpdateDraw(func() { - appendText(line, aWriterCometbftTextView) - cometbftTVRowCount++ - - }) - } - if err := outputScanner.Err(); err != nil { - panic(err) - } - if err := cometbftCmd.Wait(); err != nil { - panic(err) - } - }() - - // go func() for running the composer - go func() { - <-cometbftStartComplete - - stdout, err := composerCmd.StdoutPipe() - if err != nil { - panic(err) - } - stderr, err := composerCmd.StderrPipe() - if err != nil { - panic(err) - } - // Create a scanner to read the output line by line. - output := io.MultiReader(stdout, stderr) - outputScanner := bufio.NewScanner(output) - - if err := composerCmd.Start(); err != nil { - panic(err) - } - - // let the conductor go routine know that it can start - composerStartComplete <- true - - for outputScanner.Scan() { - line := outputScanner.Text() - app.QueueUpdateDraw(func() { - appendText(line, aWriterComposerTextView) - composerTVRowCount++ - }) - } - if err := outputScanner.Err(); err != nil { - panic(err) - } - if err := composerCmd.Wait(); err != nil { - panic(err) - } - }() - - // go func() for running the conductor - go func() { - <-composerStartComplete - - // Get a pipe to the command's output. - stdout, err := conductorCmd.StdoutPipe() - if err != nil { - panic(err) - } - stderr, err := conductorCmd.StderrPipe() - if err != nil { - panic(err) - } - // Create a scanner to read the output line by line. - output := io.MultiReader(stdout, stderr) - outputScanner := bufio.NewScanner(output) - - if err := conductorCmd.Start(); err != nil { - panic(err) - } - - for outputScanner.Scan() { - line := outputScanner.Text() - app.QueueUpdateDraw(func() { - appendText(line, aWriterConductorTextView) - conductorTVRowCount++ - }) - } - if err := outputScanner.Err(); err != nil { - panic(err) - } - if err := conductorCmd.Wait(); err != nil { - panic(err) - } - }() - - prevWindow = mainWindow - prevInputCapture = mainWindowInputCapture - app.SetFocus(focusedItem) - if err := app.SetRoot(mainWindow, true).Run(); err != nil { - panic(err) - } -} - -func init() { - devCmd.AddCommand(runLegacyCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // runLegacyCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // runLegacyCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") -} diff --git a/cmd/devtools/run.go b/cmd/devtools/run.go index 0fe51948..66e83740 100644 --- a/cmd/devtools/run.go +++ b/cmd/devtools/run.go @@ -1,7 +1,7 @@ package devtools import ( - "context" + "fmt" "os" "path/filepath" @@ -12,8 +12,11 @@ import ( "github.com/spf13/cobra" ) -var IsRunLocal bool -var IsRunRemote bool +// boolean flags +var ( + isRunLocal bool + isRunRemote bool +) // runCmd represents the run command var runCmd = &cobra.Command{ @@ -21,43 +24,160 @@ var runCmd = &cobra.Command{ Short: "Run all the Astria services locally.", Long: `Run all the Astria services locally. This will start the sequencer, cometbft, composer, and conductor.`, PreRun: cmd.SetLogLevel, - Run: runRun, + Run: runCmdHandler, } func init() { devCmd.AddCommand(runCmd) runCmd.Flags().StringP("instance", "i", DefaultInstanceName, "Used as directory name in ~/.astria to enable running separate instances of the sequencer stack.") - runCmd.Flags().BoolVarP(&IsRunLocal, "local", "l", false, "Run the Astria stack using a locally running sequencer.") - runCmd.Flags().BoolVarP(&IsRunRemote, "remote", "r", false, "Run the Astria stack using a remote sequencer.") + runCmd.Flags().BoolVarP(&isRunLocal, "local", "l", false, "Run the Astria stack using a locally running sequencer.") + runCmd.Flags().BoolVarP(&isRunRemote, "remote", "r", false, "Run the Astria stack using a remote sequencer.") runCmd.MarkFlagsMutuallyExclusive("local", "remote") + + runCmd.Flags().String("environment-path", "", "Provide an override path to a specific environment file.") + runCmd.Flags().String("conductor-path", "", "Provide an override path to a specific conductor binary.") + runCmd.Flags().String("cometbft-path", "", "Provide an override path to a specific cometbft binary.") + runCmd.Flags().String("composer-path", "", "Provide an override path to a specific composer binary.") + runCmd.Flags().String("sequencer-path", "", "Provide an override path to a specific sequencer binary.") } -func runRun(c *cobra.Command, args []string) { +func runCmdHandler(c *cobra.Command, args []string) { ctx := c.Context() - instance := c.Flag("instance").Value.String() - IsInstanceNameValidOrPanic(instance) - homePath, err := os.UserHomeDir() if err != nil { log.WithError(err).Error("Error getting home dir") panic(err) } - defaultDir := filepath.Join(homePath, ".astria") - instanceDir := filepath.Join(defaultDir, instance) + // astriaDir is the directory where all the astria instances data is stored + astriaDir := filepath.Join(homePath, ".astria") + + // get instance name and check if it's valid + instance := c.Flag("instance").Value.String() + IsInstanceNameValidOrPanic(instance) + + // we will set runners after we decide which binaries we need to run var runners []processrunner.ProcessRunner - switch { - case !IsRunLocal && !IsRunRemote: - log.Info("No --local or --remote flag provided. Defaulting to --local.") - IsRunLocal = true - runners = runLocal(ctx, instanceDir) - case IsRunLocal: - log.Info("--local flag provided. Running local sequencer.") - runners = runLocal(ctx, instanceDir) - case IsRunRemote: - log.Info("--remote flag provided. Connecting to remote sequencer.") - runners = runRemote(ctx, instanceDir) + + // check if running local or remote sequencer. + isLocalSequencer := isLocalSequencer() + if isLocalSequencer { + log.Debug("Running local sequencer") + confDir := filepath.Join(astriaDir, instance, LocalConfigDirName) + dataDir := filepath.Join(astriaDir, instance, DataDirName) + binDir := filepath.Join(astriaDir, instance, BinariesDirName) + // env path + envPath := getFlagPathOrPanic(c, "environment-path", filepath.Join(confDir, ".env")) + env := GetEnvironment(envPath) + + // get the binary paths + conductorPath := getFlagPathOrPanic(c, "conductor-path", filepath.Join(binDir, "astria-conductor")) + cometbftPath := getFlagPathOrPanic(c, "cometbft-path", filepath.Join(binDir, "cometbft")) + composerPath := getFlagPathOrPanic(c, "composer-path", filepath.Join(binDir, "astria-composer")) + sequencerPath := getFlagPathOrPanic(c, "sequencer-path", filepath.Join(binDir, "astria-sequencer")) + log.Debugf("Using binaries from %s", binDir) + + // sequencer + seqOpts := processrunner.NewProcessRunnerOpts{ + Title: "Sequencer", + BinPath: sequencerPath, + Env: env, + Args: nil, + } + seqRunner := processrunner.NewProcessRunner(ctx, seqOpts) + + // cometbft + cometDataPath := filepath.Join(dataDir, ".cometbft") + cometOpts := processrunner.NewProcessRunnerOpts{ + Title: "Comet BFT", + BinPath: cometbftPath, + Env: env, + Args: []string{"node", "--home", cometDataPath}, + } + cometRunner := processrunner.NewProcessRunner(ctx, cometOpts) + + // composer + composerOpts := processrunner.NewProcessRunnerOpts{ + Title: "Composer", + BinPath: composerPath, + Env: env, + Args: nil, + } + compRunner := processrunner.NewProcessRunner(ctx, composerOpts) + + // conductor + conductorOpts := processrunner.NewProcessRunnerOpts{ + Title: "Conductor", + BinPath: conductorPath, + Env: env, + Args: nil, + } + condRunner := processrunner.NewProcessRunner(ctx, conductorOpts) + + // shouldStart acts as a control channel to start this first process + shouldStart := make(chan bool) + close(shouldStart) + err := seqRunner.Start(ctx, shouldStart) + if err != nil { + log.WithError(err).Error("Error running sequencer") + } + err = cometRunner.Start(ctx, seqRunner.GetDidStart()) + if err != nil { + log.WithError(err).Error("Error running cometbft") + } + err = compRunner.Start(ctx, cometRunner.GetDidStart()) + if err != nil { + log.WithError(err).Error("Error running composer") + } + err = condRunner.Start(ctx, compRunner.GetDidStart()) + if err != nil { + log.WithError(err).Error("Error running conductor") + } + + runners = []processrunner.ProcessRunner{seqRunner, cometRunner, compRunner, condRunner} + } else { + log.Debug("Running remote sequencer") + confDir := filepath.Join(astriaDir, instance, RemoteConfigDirName) + binDir := filepath.Join(astriaDir, instance, BinariesDirName) + // env path + envPath := getFlagPathOrPanic(c, "environment-path", filepath.Join(confDir, ".env")) + env := GetEnvironment(envPath) + + // get the binary paths + conductorPath := getFlagPathOrPanic(c, "conductor-path", filepath.Join(binDir, "astria-conductor")) + composerPath := getFlagPathOrPanic(c, "composer-path", filepath.Join(binDir, "astria-composer")) + + // composer + composerOpts := processrunner.NewProcessRunnerOpts{ + Title: "Composer", + BinPath: composerPath, + Env: env, + Args: nil, + } + compRunner := processrunner.NewProcessRunner(ctx, composerOpts) + + // conductor + conductorOpts := processrunner.NewProcessRunnerOpts{ + Title: "Conductor", + BinPath: conductorPath, + Env: env, + Args: nil, + } + condRunner := processrunner.NewProcessRunner(ctx, conductorOpts) + + // shouldStart acts as a control channel to start this first process + shouldStart := make(chan bool) + close(shouldStart) + err := compRunner.Start(ctx, shouldStart) + if err != nil { + log.WithError(err).Error("Error running composer") + } + err = condRunner.Start(ctx, compRunner.GetDidStart()) + if err != nil { + log.WithError(err).Error("Error running conductor") + } + runners = []processrunner.ProcessRunner{compRunner, condRunner} } // create and start ui app @@ -65,110 +185,39 @@ func runRun(c *cobra.Command, args []string) { app.Start() } -func runLocal(ctx context.Context, instanceDir string) []processrunner.ProcessRunner { - // load the .env file and get the environment variables - // TODO - move config to own package w/ structs w/ defaults. still use .env for overrides. - envPath := filepath.Join(instanceDir, LocalConfigDirName, ".env") - - environment := loadAndGetEnvVariables(envPath) - - // sequencer - seqOpts := processrunner.NewProcessRunnerOpts{ - Title: "Sequencer", - BinPath: filepath.Join(instanceDir, BinariesDirName, "astria-sequencer"), - Env: environment, - Args: nil, - } - seqRunner := processrunner.NewProcessRunner(ctx, seqOpts) - - // cometbft - cometDataPath := filepath.Join(instanceDir, DataDirName, ".cometbft") - cometOpts := processrunner.NewProcessRunnerOpts{ - Title: "Comet BFT", - BinPath: filepath.Join(instanceDir, BinariesDirName, "cometbft"), - Env: environment, - Args: []string{"node", "--home", cometDataPath}, - } - cometRunner := processrunner.NewProcessRunner(ctx, cometOpts) - - // composer - composerOpts := processrunner.NewProcessRunnerOpts{ - Title: "Composer", - BinPath: filepath.Join(instanceDir, BinariesDirName, "astria-composer"), - Env: environment, - Args: nil, - } - compRunner := processrunner.NewProcessRunner(ctx, composerOpts) - - // conductor - conductorOpts := processrunner.NewProcessRunnerOpts{ - Title: "Conductor", - BinPath: filepath.Join(instanceDir, BinariesDirName, "astria-conductor"), - Env: environment, - Args: nil, - } - condRunner := processrunner.NewProcessRunner(ctx, conductorOpts) - - // shouldStart acts as a control channel to start this first process - shouldStart := make(chan bool) - close(shouldStart) - err := seqRunner.Start(ctx, shouldStart) - if err != nil { - log.WithError(err).Error("Error running sequencer") - } - err = cometRunner.Start(ctx, seqRunner.GetDidStart()) - if err != nil { - log.WithError(err).Error("Error running cometbft") - } - err = compRunner.Start(ctx, cometRunner.GetDidStart()) - if err != nil { - log.WithError(err).Error("Error running composer") - } - err = condRunner.Start(ctx, compRunner.GetDidStart()) - if err != nil { - log.WithError(err).Error("Error running conductor") +// isLocalSequencer returns true if we should run the local sequencer +func isLocalSequencer() bool { + switch { + case !isRunLocal && !isRunRemote: + log.Debug("No --local or --remote flag provided. Defaulting to --local.") + return true + case isRunLocal: + log.Debug("--local flag provided. Running local sequencer.") + return true + case isRunRemote: + log.Debug("--remote flag provided. Connecting to remote sequencer.") + return false + default: + // this should never happen + log.Debug("Unknown run configuration found. Defaulting to --local.") + return true } - - runners := []processrunner.ProcessRunner{seqRunner, cometRunner, compRunner, condRunner} - return runners } -func runRemote(ctx context.Context, instanceDir string) []processrunner.ProcessRunner { - // load the .env file and get the environment variables - // TODO - move config to own package w/ structs w/ defaults. still use .env for overrides. - envPath := filepath.Join(instanceDir, RemoteConfigDirName, ".env") - environment := loadAndGetEnvVariables(envPath) - - // composer - composerOpts := processrunner.NewProcessRunnerOpts{ - Title: "Composer", - BinPath: filepath.Join(instanceDir, BinariesDirName, "astria-composer"), - Env: environment, - Args: nil, - } - compRunner := processrunner.NewProcessRunner(ctx, composerOpts) - - // conductor - conductorOpts := processrunner.NewProcessRunnerOpts{ - Title: "Conductor", - BinPath: filepath.Join(instanceDir, BinariesDirName, "astria-conductor"), - Env: environment, - Args: nil, - } - condRunner := processrunner.NewProcessRunner(ctx, conductorOpts) - - // shouldStart acts as a control channel to start this first process - shouldStart := make(chan bool) - close(shouldStart) - err := compRunner.Start(ctx, shouldStart) - if err != nil { - log.WithError(err).Error("Error running composer") - } - err = condRunner.Start(ctx, compRunner.GetDidStart()) - if err != nil { - log.WithError(err).Error("Error running conductor") +// getFlagPathOrPanic gets the override path from the flag. It returns the default +// value if the flag was not set, or panics if no file exists at the provided path. +func getFlagPathOrPanic(c *cobra.Command, flagName string, defaultValue string) string { + flag := c.Flags().Lookup(flagName) + if flag != nil && flag.Changed { + path := flag.Value.String() + if PathExists(path) { + log.Info(fmt.Sprintf("Override path provided for %s binary: %s", flagName, path)) + return path + } else { + panic(fmt.Sprintf("Invalid input path provided for --%s flag", flagName)) + } + } else { + log.Debug(fmt.Sprintf("No path provided for %s binary. Using default path: %s", flagName, defaultValue)) + return defaultValue } - - runners := []processrunner.ProcessRunner{compRunner, condRunner} - return runners }