diff --git a/cli/cmd/flags.go b/cli/cmd/flags.go index fd61049..d27560c 100644 --- a/cli/cmd/flags.go +++ b/cli/cmd/flags.go @@ -3,12 +3,11 @@ package cmd import ( "encoding/json" "os" - "path/filepath" - "github.com/mitchellh/go-homedir" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/vulpemventures/nigiri/cli/config" + "github.com/vulpemventures/nigiri/cli/constants" ) var ( @@ -18,38 +17,25 @@ var ( flagAttachLiquid bool flagLiquidService bool flagEnv string - - defaultPorts = map[string]map[string]int{ - "bitcoin": { - "node": 18443, - "electrs_rpc": 60401, - "chopsticks": 3000, - "esplora": 5000, - }, - "liquid": { - "node": 7041, - "electrs_rpc": 51401, - "chopsticks": 3001, - "esplora": 5001, - }, - } ) var RootCmd = &cobra.Command{ Use: "nigiri", Short: "Nigiri lets you manage a full dockerized bitcoin environment", Long: "Nigiri lets you create your dockerized environment with a bitcoin and optionally a liquid node + block explorer powered by an electrum server for every network", - Version: "0.0.3", + Version: "0.0.4", } func init() { - defaultDir := getDefaultDir() - ports, _ := json.Marshal(defaultPorts) + c := &config.Config{} + viper := c.Viper() + defaultDir := c.GetPath() + defaultJSON, _ := json.Marshal(constants.DefaultEnv) RootCmd.PersistentFlags().StringVar(&flagDatadir, "datadir", defaultDir, "Set nigiri default directory") StartCmd.PersistentFlags().StringVar(&flagNetwork, "network", "regtest", "Set bitcoin network - regtest only for now") StartCmd.PersistentFlags().BoolVar(&flagAttachLiquid, "liquid", false, "Enable liquid sidechain") - StartCmd.PersistentFlags().StringVar(&flagEnv, "ports", string(ports), "Set services ports in JSON format") + StartCmd.PersistentFlags().StringVar(&flagEnv, "env", string(defaultJSON), "Set compose env in JSON format") StopCmd.PersistentFlags().BoolVar(&flagDelete, "delete", false, "Stop and delete nigiri") LogsCmd.PersistentFlags().BoolVar(&flagLiquidService, "liquid", false, "Set to see logs of a liquid service") @@ -57,18 +43,12 @@ func init() { RootCmd.AddCommand(StopCmd) RootCmd.AddCommand(LogsCmd) - viper := config.Viper() - viper.BindPFlag(config.Datadir, RootCmd.PersistentFlags().Lookup("datadir")) - viper.BindPFlag(config.Network, StartCmd.PersistentFlags().Lookup("network")) - viper.BindPFlag(config.AttachLiquid, StartCmd.PersistentFlags().Lookup("liquid")) + viper.BindPFlag(constants.Datadir, RootCmd.PersistentFlags().Lookup("datadir")) + viper.BindPFlag(constants.Network, StartCmd.PersistentFlags().Lookup("network")) + viper.BindPFlag(constants.AttachLiquid, StartCmd.PersistentFlags().Lookup("liquid")) cobra.OnInitialize(func() { log.SetOutput(os.Stdout) log.SetLevel(log.InfoLevel) }) } - -func getDefaultDir() string { - home, _ := homedir.Expand("~") - return filepath.Join(home, ".nigiri") -} diff --git a/cli/cmd/logs.go b/cli/cmd/logs.go index 192ebe0..07b168b 100644 --- a/cli/cmd/logs.go +++ b/cli/cmd/logs.go @@ -1,12 +1,11 @@ package cmd import ( - "fmt" "os" "os/exec" - "reflect" - "github.com/vulpemventures/nigiri/cli/config" + "github.com/vulpemventures/nigiri/cli/constants" + "github.com/vulpemventures/nigiri/cli/controller" "github.com/spf13/cobra" ) @@ -18,42 +17,39 @@ var LogsCmd = &cobra.Command{ PreRunE: logsChecks, } -var services = map[string]bool{ - "node": true, - "electrs": true, - "esplora": true, - "chopsticks": true, -} - func logsChecks(cmd *cobra.Command, args []string) error { datadir, _ := cmd.Flags().GetString("datadir") isLiquidService, _ := cmd.Flags().GetBool("liquid") - if !isDatadirOk(datadir) { - return fmt.Errorf("Invalid datadir, it must be an absolute path: %s", datadir) - } - if len(args) != 1 { - return fmt.Errorf("Invalid number of args, expected 1, got: %d", len(args)) - } - - service := args[0] - if !services[service] { - return fmt.Errorf("Invalid service, must be one of %s. Got: %s", reflect.ValueOf(services).MapKeys(), service) - } - isRunning, err := nigiriIsRunning() + ctl, err := controller.NewController() if err != nil { return err } - if !isRunning { - return fmt.Errorf("Nigiri is not running") + + if err := ctl.ParseDatadir(datadir); err != nil { + return err + } + if len(args) != 1 { + return constants.ErrInvalidArgs } - if err := config.ReadFromFile(datadir); err != nil { + service := args[0] + if err := ctl.ParseServiceName(service); err != nil { return err } - if isLiquidService && isLiquidService != config.GetBool(config.AttachLiquid) { - return fmt.Errorf("Nigiri has been started with no Liquid sidechain.\nPlease stop and restart it using the --liquid flag") + if isRunning, err := ctl.IsNigiriRunning(); err != nil { + return err + } else if !isRunning { + return constants.ErrNigiriNotRunning + } + + if err := ctl.ReadConfigFile(datadir); err != nil { + return err + } + + if isLiquidService && isLiquidService != ctl.GetConfigBoolField(constants.AttachLiquid) { + return constants.ErrNigiriLiquidNotEnabled } return nil @@ -64,10 +60,15 @@ func logs(cmd *cobra.Command, args []string) error { datadir, _ := cmd.Flags().GetString("datadir") isLiquidService, _ := cmd.Flags().GetBool("liquid") - serviceName := getServiceName(service, isLiquidService) - composePath := getPath(datadir, "compose") - envPath := getPath(datadir, "env") - env := loadEnv(envPath) + ctl, err := controller.NewController() + if err != nil { + return err + } + + serviceName := ctl.GetServiceName(service, isLiquidService) + composePath := ctl.GetResourcePath(datadir, "compose") + envPath := ctl.GetResourcePath(datadir, "env") + env := ctl.LoadComposeEnvironment(envPath) bashCmd := exec.Command("docker-compose", "-f", composePath, "logs", serviceName) bashCmd.Stdout = os.Stdout @@ -80,19 +81,3 @@ func logs(cmd *cobra.Command, args []string) error { return nil } - -func getServiceName(name string, liquid bool) string { - service := name - if service == "node" { - service = "bitcoin" - } - if liquid { - if service == "bitcoin" { - service = "liquid" - } else { - service = fmt.Sprintf("%s-liquid", service) - } - } - - return service -} diff --git a/cli/cmd/logs_test.go b/cli/cmd/logs_test.go index ba9e28d..36b06cf 100644 --- a/cli/cmd/logs_test.go +++ b/cli/cmd/logs_test.go @@ -2,6 +2,8 @@ package cmd import ( "testing" + + "github.com/vulpemventures/nigiri/cli/constants" ) var ( @@ -41,7 +43,7 @@ func TestLogLiquidServices(t *testing.T) { } func TestLogShouldFail(t *testing.T) { - expectedError := "Nigiri is not running" + expectedError := constants.ErrNigiriNotRunning.Error() err := testCommand("logs", serviceList[0], bitcoin) if err == nil { @@ -65,7 +67,7 @@ func TestStartBitcoinAndLogNigiriServicesShouldFail(t *testing.T) { t.Fatal(err) } - expectedError := "Nigiri has been started with no Liquid sidechain.\nPlease stop and restart it using the --liquid flag" + expectedError := constants.ErrNigiriLiquidNotEnabled.Error() err := testCommand("logs", serviceList[0], liquid) if err == nil { diff --git a/cli/cmd/start.go b/cli/cmd/start.go index 7553efd..4883868 100644 --- a/cli/cmd/start.go +++ b/cli/cmd/start.go @@ -1,25 +1,16 @@ package cmd import ( - "bufio" - "context" - "encoding/json" "fmt" - "io/ioutil" "os" "os/exec" - "path/filepath" - "strconv" "strings" - "github.com/vulpemventures/nigiri/cli/config" - "github.com/docker/docker/api/types" - "github.com/docker/docker/client" "github.com/spf13/cobra" + "github.com/vulpemventures/nigiri/cli/constants" + "github.com/vulpemventures/nigiri/cli/controller" ) -const listAll = true - var StartCmd = &cobra.Command{ Use: "start", Short: "Build and start Nigiri", @@ -30,27 +21,30 @@ var StartCmd = &cobra.Command{ func startChecks(cmd *cobra.Command, args []string) error { network, _ := cmd.Flags().GetString("network") datadir, _ := cmd.Flags().GetString("datadir") - ports, _ := cmd.Flags().GetString("ports") + env, _ := cmd.Flags().GetString("env") - // check flags - if !isNetworkOk(network) { - return fmt.Errorf("Invalid network: %s", network) - } - - if !isDatadirOk(datadir) { - return fmt.Errorf("Invalid datadir, it must be an absolute path: %s", datadir) - } - if !isEnvOk(ports) { - return fmt.Errorf("Invalid env JSON, it must contain a \"bitcoin\" object with at least one service specified. It can optionally contain a \"liquid\" object with at least one service specified.\nGot: %s", ports) - } - - // if nigiri is already running return error - isRunning, err := nigiriIsRunning() + ctl, err := controller.NewController() if err != nil { return err } - if isRunning { - return fmt.Errorf("Nigiri is already running, please stop it first") + + if err := ctl.ParseNetwork(network); err != nil { + return err + } + + if err := ctl.ParseDatadir(datadir); err != nil { + return err + } + composeEnv, err := ctl.ParseEnv(env) + if err != nil { + return err + } + + // if nigiri is already running return error + if isRunning, err := ctl.IsNigiriRunning(); err != nil { + return err + } else if isRunning { + return constants.ErrNigiriAlreadyRunning } // scratch datadir if not exists @@ -60,65 +54,77 @@ func startChecks(cmd *cobra.Command, args []string) error { // if datadir is set we must copy the resources directory from ~/.nigiri // to the new one - if datadir != getDefaultDir() { - if err := copyResources(datadir); err != nil { + if datadir != ctl.GetDefaultDatadir() { + if err := ctl.NewDatadirFromDefault(datadir); err != nil { return err } } // if nigiri not exists, we need to write the configuration file and then // read from it to get viper updated, otherwise we just read from it. - exists, err := nigiriExistsAndNotRunning() - if err != nil { + if isStopped, err := ctl.IsNigiriStopped(); err != nil { return err - } - if !exists { - filedir := getPath(datadir, "config") - if err := config.WriteConfig(filedir); err != nil { + } else if isStopped { + if err := ctl.ReadConfigFile(datadir); err != nil { + return err + } + } else { + filedir := ctl.GetResourcePath(datadir, "config") + if err := ctl.WriteConfigFile(filedir); err != nil { return err } // .env must be in the directory where docker-compose is run from, not where YAML files are placed // https://docs.docker.com/compose/env-file/ - filedir = getPath(datadir, "env") - if err := writeComposeEnvFile(filedir, ports); err != nil { + filedir = ctl.GetResourcePath(datadir, "env") + if err := ctl.WriteComposeEnvironment(filedir, composeEnv); err != nil { return err } } - if err := config.ReadFromFile(datadir); err != nil { - return err - } return nil } func start(cmd *cobra.Command, args []string) error { - datadir, _ := cmd.Flags().GetString("datadir") - liquidEnabled, _ := cmd.Flags().GetBool("liquid") - - bashCmd, err := getStartBashCmd(datadir) + ctl, err := controller.NewController() if err != nil { return err } + datadir, _ := cmd.Flags().GetString("datadir") + liquidEnabled := ctl.GetConfigBoolField(constants.AttachLiquid) + + envPath := ctl.GetResourcePath(datadir, "env") + composePath := ctl.GetResourcePath(datadir, "compose") + + bashCmd := exec.Command("docker-compose", "-f", composePath, "up", "-d") + if isStopped, err := ctl.IsNigiriStopped(); err != nil { + return err + } else if isStopped { + bashCmd = exec.Command("docker-compose", "-f", composePath, "start") + } + bashCmd.Stdout = os.Stdout + bashCmd.Stderr = os.Stderr + bashCmd.Env = ctl.LoadComposeEnvironment(envPath) + if err := bashCmd.Run(); err != nil { return err } - path := getPath(datadir, "env") - ports, err := readComposeEnvFile(path) + path := ctl.GetResourcePath(datadir, "env") + env, err := ctl.ReadComposeEnvironment(path) if err != nil { return err } prettyPrintServices := func(chain string, services map[string]int) { - fmt.Printf("%s services:\n", chain) + fmt.Printf("%s services:\n", strings.Title(chain)) for name, port := range services { formatName := fmt.Sprintf("%s:", name) fmt.Printf(" %-14s localhost:%d\n", formatName, port) } } - for chain, services := range ports { + for chain, services := range env["ports"].(map[string]map[string]int) { if chain == "bitcoin" { prettyPrintServices(chain, services) } else if liquidEnabled { @@ -128,226 +134,3 @@ func start(cmd *cobra.Command, args []string) error { return nil } - -var images = map[string]bool{ - "vulpemventures/bitcoin:latest": true, - "vulpemventures/liquid:latest": true, - "vulpemventures/electrs:latest": true, - "vulpemventures/electrs-liquid:latest": true, - "vulpemventures/esplora:latest": true, - "vulpemventures/esplora-liquid:latest": true, - "vulpemventures/nigiri-chopsticks:latest": true, -} - -func copyResources(datadir string) error { - defaultDatadir := getDefaultDir() - cmd := exec.Command("cp", "-R", filepath.Join(defaultDatadir, "resources"), datadir) - return cmd.Run() - -} - -func nigiriExists(listAll bool) (bool, error) { - cli, err := client.NewEnvClient() - if err != nil { - return false, err - } - - containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{All: listAll}) - if err != nil { - return false, err - } - - for _, container := range containers { - if images[container.Image] { - return true, nil - } - } - - return false, nil -} - -func isNetworkOk(network string) bool { - var ok bool - for _, n := range []string{"regtest"} { - if network == n { - ok = true - } - } - - return ok -} - -func isDatadirOk(datadir string) bool { - return filepath.IsAbs(datadir) -} - -func isEnvOk(stringifiedJSON string) bool { - var parsedJSON map[string]map[string]int - err := json.Unmarshal([]byte(stringifiedJSON), &parsedJSON) - if err != nil { - return false - } - - if len(parsedJSON) <= 0 { - return false - } - if len(parsedJSON["bitcoin"]) <= 0 { - return false - } - if parsedJSON["bitcoin"]["node"] <= 0 && - parsedJSON["bitcoin"]["electrs"] <= 0 && - parsedJSON["bitcoin"]["esplora"] <= 0 && - parsedJSON["bitcoin"]["chopsticks"] <= 0 { - return false - } - - if len(parsedJSON["liquid"]) > 0 && - parsedJSON["liquid"]["node"] <= 0 && - parsedJSON["liquid"]["electrs"] <= 0 && - parsedJSON["liquid"]["esplora"] <= 0 && - parsedJSON["liquid"]["chopsticks"] <= 0 { - return false - } - - return true -} - -func getPath(datadir, t string) string { - viper := config.Viper() - - if t == "compose" { - network := viper.GetString("network") - attachLiquid := viper.GetBool("attachLiquid") - if attachLiquid { - network += "-liquid" - } - return filepath.Join(datadir, "resources", fmt.Sprintf("docker-compose-%s.yml", network)) - } - - if t == "env" { - return filepath.Join(datadir, ".env") - } - - if t == "config" { - return filepath.Join(datadir, "nigiri.config.json") - } - - return "" -} - -func nigiriIsRunning() (bool, error) { - listOnlyRunningContainers := !listAll - return nigiriExists(listOnlyRunningContainers) -} - -func nigiriExistsAndNotRunning() (bool, error) { - return nigiriExists(listAll) -} - -func getStartBashCmd(datadir string) (*exec.Cmd, error) { - composePath := getPath(datadir, "compose") - envPath := getPath(datadir, "env") - env := loadEnv(envPath) - - bashCmd := exec.Command("docker-compose", "-f", composePath, "up", "-d") - - isStopped, err := nigiriExistsAndNotRunning() - if err != nil { - return nil, err - } - if isStopped { - bashCmd = exec.Command("docker-compose", "-f", composePath, "start") - } - bashCmd.Stdout = os.Stdout - bashCmd.Stderr = os.Stderr - bashCmd.Env = env - - return bashCmd, nil -} - -func writeComposeEnvFile(path string, stringifiedJSON string) error { - defaultJSON, _ := json.Marshal(defaultPorts) - env := map[string]map[string]int{} - json.Unmarshal([]byte(stringifiedJSON), &env) - - if stringifiedJSON != string(defaultJSON) { - env = mergeComposeEnvFiles([]byte(stringifiedJSON)) - } - - fileContent := "" - for chain, services := range env { - for k, v := range services { - fileContent += fmt.Sprintf("%s_%s_PORT=%d\n", strings.ToUpper(chain), strings.ToUpper(k), v) - } - } - - return ioutil.WriteFile(path, []byte(fileContent), os.ModePerm) -} - -func readComposeEnvFile(path string) (map[string]map[string]int, error) { - file, err := os.Open(path) - if err != nil { - return nil, err - } - defer file.Close() - - scanner := bufio.NewScanner(file) - scanner.Split(bufio.ScanLines) - - ports := map[string]map[string]int{ - "bitcoin": map[string]int{}, - "liquid": map[string]int{}, - } - // Each line is in the format PREFIX_SERVICE_NAME_SUFFIX=value - // PREFIX is either 'BITCOIN' or 'LIQUID', while SUFFIX is always 'PORT' - for scanner.Scan() { - line := scanner.Text() - splitLine := strings.Split(line, "=") - key := splitLine[0] - value, _ := strconv.Atoi(splitLine[1]) - chain := "bitcoin" - if strings.HasPrefix(key, strings.ToUpper("liquid")) { - chain = "liquid" - } - - suffix := "_PORT" - prefix := strings.ToUpper(fmt.Sprintf("%s_", chain)) - trimmedKey := strings.ToLower(strings.TrimSuffix(strings.TrimPrefix(key, prefix), suffix)) - ports[chain][trimmedKey] = value - } - - return ports, nil -} - -func mergeComposeEnvFiles(rawJSON []byte) map[string]map[string]int { - newPorts := map[string]map[string]int{} - json.Unmarshal(rawJSON, &newPorts) - - mergedPorts := map[string]map[string]int{} - for chain, services := range defaultPorts { - mergedPorts[chain] = make(map[string]int) - for name, port := range services { - newPort := newPorts[chain][name] - if newPort > 0 && newPort != port { - mergedPorts[chain][name] = newPort - } else { - mergedPorts[chain][name] = port - } - } - } - - return mergedPorts -} - -func loadEnv(path string) []string { - content, _ := ioutil.ReadFile(path) - lines := strings.Split(string(content), "\n") - env := os.Environ() - for _, line := range lines { - if line != "" { - env = append(env, line) - } - } - - return env -} diff --git a/cli/cmd/start_stop_test.go b/cli/cmd/start_stop_test.go index ae3a10c..5cff502 100644 --- a/cli/cmd/start_stop_test.go +++ b/cli/cmd/start_stop_test.go @@ -3,6 +3,9 @@ package cmd import ( "fmt" "testing" + + "github.com/vulpemventures/nigiri/cli/constants" + "github.com/vulpemventures/nigiri/cli/controller" ) const ( @@ -37,7 +40,7 @@ func TestStartStopBitcoin(t *testing.T) { } func TestStopBeforeStartShouldFail(t *testing.T) { - expectedError := "Nigiri is neither running nor stopped, please create it first" + expectedError := constants.ErrNigiriNotRunning.Error() err := testCommand("stop", "", !delete) if err == nil { @@ -47,6 +50,7 @@ func TestStopBeforeStartShouldFail(t *testing.T) { t.Fatalf("Expected error: %s, got: %s", expectedError, err) } + expectedError = constants.ErrNigiriNotExisting.Error() err = testCommand("stop", "", delete) if err == nil { t.Fatal("Should return error when trying to delete before starting") @@ -57,7 +61,7 @@ func TestStopBeforeStartShouldFail(t *testing.T) { } func TestStartAfterStartShouldFail(t *testing.T) { - expectedError := "Nigiri is already running, please stop it first" + expectedError := constants.ErrNigiriAlreadyRunning.Error() if err := testCommand("start", "", bitcoin); err != nil { t.Fatal(err) @@ -85,30 +89,48 @@ func TestStartAfterStartShouldFail(t *testing.T) { } func testStart(t *testing.T, flag bool) { + ctl, err := controller.NewController() + if err != nil { + t.Fatal(err) + } if err := testCommand("start", "", flag); err != nil { t.Fatal(err) } - if isRunning, _ := nigiriIsRunning(); !isRunning { - t.Fatal("Nigiri should be started but services have not been found among running containers") + if isRunning, err := ctl.IsNigiriRunning(); err != nil { + t.Fatal(err) + } else if !isRunning { + t.Fatal("Nigiri should have been started but services have not been found among running containers") } } func testStop(t *testing.T) { - fmt.Println(!delete) + ctl, err := controller.NewController() + if err != nil { + t.Fatal(err) + } if err := testCommand("stop", "", !delete); err != nil { t.Fatal(err) } - if isStopped, _ := nigiriExistsAndNotRunning(); !isStopped { - t.Fatal("Nigiri should be stopped but services have not been found among stopped containers") + if isStopped, err := ctl.IsNigiriStopped(); err != nil { + t.Fatal(err) + } else if !isStopped { + t.Fatal("Nigiri should have been stopped but services have not been found among stopped containers") } } func testDelete(t *testing.T) { + ctl, err := controller.NewController() + if err != nil { + t.Fatal(err) + } + if err := testCommand("stop", "", delete); err != nil { t.Fatal(err) } - if isStopped, _ := nigiriExistsAndNotRunning(); isStopped { - t.Fatal("Nigiri should be terminated at this point but services have found among stopped containers") + if isStopped, err := ctl.IsNigiriStopped(); err != nil { + t.Fatal(err) + } else if isStopped { + t.Fatal("Nigiri should have been terminated at this point but services have been found among stopped containers") } } diff --git a/cli/cmd/stop.go b/cli/cmd/stop.go index 5ad5110..b915727 100644 --- a/cli/cmd/stop.go +++ b/cli/cmd/stop.go @@ -2,13 +2,12 @@ package cmd import ( "fmt" - "io/ioutil" "os" "os/exec" - "path/filepath" - "github.com/vulpemventures/nigiri/cli/config" "github.com/spf13/cobra" + "github.com/vulpemventures/nigiri/cli/constants" + "github.com/vulpemventures/nigiri/cli/controller" ) var StopCmd = &cobra.Command{ @@ -20,24 +19,36 @@ var StopCmd = &cobra.Command{ func stopChecks(cmd *cobra.Command, args []string) error { datadir, _ := cmd.Flags().GetString("datadir") + delete, _ := cmd.Flags().GetBool("delete") - if !isDatadirOk(datadir) { - return fmt.Errorf("Invalid datadir, it must be an absolute path: %s", datadir) - } - - if _, err := os.Stat(datadir); os.IsNotExist(err) { - return fmt.Errorf("Datadir do not exists: %s", datadir) - } - - nigiriExists, err := nigiriExistsAndNotRunning() + ctl, err := controller.NewController() if err != nil { return err } - if !nigiriExists { - return fmt.Errorf("Nigiri is neither running nor stopped, please create it first") + + if err := ctl.ParseDatadir(datadir); err != nil { + return err } - if err := config.ReadFromFile(datadir); err != nil { + if _, err := os.Stat(datadir); os.IsNotExist(err) { + return constants.ErrDatadirNotExisting + } + + if isRunning, err := ctl.IsNigiriRunning(); err != nil { + return err + } else if !isRunning { + if delete { + if isStopped, err := ctl.IsNigiriStopped(); err != nil { + return err + } else if !isStopped { + return constants.ErrNigiriNotExisting + } + } else { + return constants.ErrNigiriNotRunning + } + } + + if err := ctl.ReadConfigFile(datadir); err != nil { return err } return nil @@ -47,40 +58,15 @@ func stop(cmd *cobra.Command, args []string) error { delete, _ := cmd.Flags().GetBool("delete") datadir, _ := cmd.Flags().GetString("datadir") - bashCmd := getStopBashCmd(datadir, delete) - if err := bashCmd.Run(); err != nil { + ctl, err := controller.NewController() + if err != nil { return err } - if delete { - fmt.Println("Removing data from volumes...") - if err := cleanVolumes(datadir); err != nil { - return err - } - - configFile := getPath(datadir, "config") - envFile := getPath(datadir, "env") - - fmt.Println("Removing configuration file...") - if err := os.Remove(configFile); err != nil { - return err - } - - fmt.Println("Removing environmet file...") - if err := os.Remove(envFile); err != nil { - return err - } - - fmt.Println("Nigiri has been cleaned up successfully.") - } - - return nil -} - -func getStopBashCmd(datadir string, delete bool) *exec.Cmd { - composePath := getPath(datadir, "compose") - envPath := getPath(datadir, "env") - env := loadEnv(envPath) + composePath := ctl.GetResourcePath(datadir, "compose") + configPath := ctl.GetResourcePath(datadir, "config") + envPath := ctl.GetResourcePath(datadir, "env") + env := ctl.LoadComposeEnvironment(envPath) bashCmd := exec.Command("docker-compose", "-f", composePath, "stop") if delete { @@ -90,34 +76,27 @@ func getStopBashCmd(datadir string, delete bool) *exec.Cmd { bashCmd.Stderr = os.Stderr bashCmd.Env = env - return bashCmd -} - -// cleanVolumes navigates into /resources/volumes/ -// and deletes all files and directories but the *.conf config files. -func cleanVolumes(datadir string) error { - network := config.GetString(config.Network) - attachLiquid := config.GetBool(config.AttachLiquid) - if attachLiquid { - network = fmt.Sprintf("liquid%s", network) - } - volumedir := filepath.Join(datadir, "resources", "volumes", network) - - subdirs, err := ioutil.ReadDir(volumedir) - if err != nil { + if err := bashCmd.Run(); err != nil { return err } - for _, d := range subdirs { - volumedir := filepath.Join(volumedir, d.Name()) - subsubdirs, _ := ioutil.ReadDir(volumedir) - for _, sd := range subsubdirs { - if sd.IsDir() { - if err := os.RemoveAll(filepath.Join(volumedir, sd.Name())); err != nil { - return err - } - } + if delete { + fmt.Println("Removing data from volumes...") + if err := ctl.CleanResourceVolumes(datadir); err != nil { + return err } + + fmt.Println("Removing configuration file...") + if err := os.Remove(configPath); err != nil { + return err + } + + fmt.Println("Removing environmet file...") + if err := os.Remove(envPath); err != nil { + return err + } + + fmt.Println("Nigiri has been cleaned up successfully.") } return nil diff --git a/cli/config/main.go b/cli/config/main.go index fa71c74..54b2893 100644 --- a/cli/config/main.go +++ b/cli/config/main.go @@ -5,14 +5,7 @@ import ( homedir "github.com/mitchellh/go-homedir" "github.com/spf13/viper" -) - -const ( - Datadir = "datadir" - Network = "network" - Filename = "nigiri.config.json" - AttachLiquid = "attachLiquid" - Version = "version" + "github.com/vulpemventures/nigiri/cli/constants" ) var vip *viper.Viper @@ -25,37 +18,43 @@ func init() { setConfigFromDefaults(vip, defaults) } -func Viper() *viper.Viper { +type Config struct{} + +func (c *Config) Viper() *viper.Viper { return vip } -func ReadFromFile(path string) error { - vip.SetConfigFile(filepath.Join(path, Filename)) +func (c *Config) ReadFromFile(path string) error { + vip.SetConfigFile(filepath.Join(path, constants.Filename)) return vip.ReadInConfig() } -func WriteConfig(path string) error { +func (c *Config) WriteConfig(path string) error { vip.SetConfigFile(path) return vip.WriteConfig() } -func GetString(str string) string { +func (c *Config) GetString(str string) string { return vip.GetString(str) } -func GetBool(str string) bool { +func (c *Config) GetBool(str string) bool { return vip.GetBool(str) } -func GetPath() string { +func (c *Config) GetPath() string { + return getPath() +} + +func getPath() string { home, _ := homedir.Expand("~") return filepath.Join(home, ".nigiri") } func newDefaultConfig(v *viper.Viper) { - v.SetDefault(Datadir, GetPath()) - v.SetDefault(Network, "regtest") - v.SetDefault(AttachLiquid, false) + v.SetDefault(constants.Datadir, getPath()) + v.SetDefault(constants.Network, "regtest") + v.SetDefault(constants.AttachLiquid, false) } func setConfigFromDefaults(v *viper.Viper, d *viper.Viper) { diff --git a/cli/constants/constants.go b/cli/constants/constants.go new file mode 100644 index 0000000..eb1a8a3 --- /dev/null +++ b/cli/constants/constants.go @@ -0,0 +1,66 @@ +package constants + +import ( + "errors" +) + +const ( + // Datadir key in config json + Datadir = "datadir" + // Network key in config json + Network = "network" + // Filename key in config json + Filename = "nigiri.config.json" + // AttachLiquid key in config json + AttachLiquid = "attachLiquid" + // Version key in config json + Version = "version" +) + +var ( + AvaliableNetworks = []string{"regtest"} + NigiriImages = []string{ + "vulpemventures/bitcoin:latest", + "vulpemventures/electrs:latest", + "vulpemventures/esplora:latest", + "vulpemventures/nigiri-chopsticks:latest", + "vulpemventures/liquid:latest", + "vulpemventures/electrs-liquid:latest", + } + DefaultEnv = map[string]interface{}{ + "ports": map[string]map[string]int{ + "bitcoin": map[string]int{ + "node": 18433, + "esplora": 5000, + "electrs": 3002, + "electrs_rpc": 51401, + "chopsticks": 3000, + }, + "liquid": map[string]int{ + "node": 7041, + "esplora": 5001, + "electrs": 3012, + "electrs_rpc": 60401, + "chopsticks": 3001, + }, + }, + "urls": map[string]string{ + "bitcoin_esplora": "http://localhost:3000", + "liquid_esplora": "http://localhost:3001", + }, + } + + ErrInvalidNetwork = errors.New("Network provided is not valid") + ErrInvalidDatadir = errors.New("Datadir provided is not valid: it must be an absolute path") + ErrInvalidServiceName = errors.New("Service provided is not valid") + ErrInvalidArgs = errors.New("Invalid number of args") + ErrInvalidJSON = errors.New("JSON environment provided is not valid: missing required fields") + ErrMalformedJSON = errors.New("Failed to parse malformed JSON environment") + ErrEmptyJSON = errors.New("JSON environment provided is not valid: it must not be empty") + ErrDatadirNotExisting = errors.New("Datadir provided is not valid: it must be an existing path") + ErrNigiriNotRunning = errors.New("Nigiri is not running") + ErrNigiriNotExisting = errors.New("Nigiri does not exists, cannot delete") + ErrNigiriAlreadyRunning = errors.New("Nigiri is already running, please stop it first") + ErrNigiriLiquidNotEnabled = errors.New("Nigiri has been started with no Liquid sidechain.\nPlease stop and restart it using the --liquid flag") + ErrDockerNotRunning = errors.New("Nigiri requires the Docker daemon to be running, but it not seems to be started") +) diff --git a/cli/controller/controller.go b/cli/controller/controller.go new file mode 100644 index 0000000..bc7d7fd --- /dev/null +++ b/cli/controller/controller.go @@ -0,0 +1,187 @@ +package controller + +import ( + "fmt" + "os/exec" + "path/filepath" + + "github.com/vulpemventures/nigiri/cli/config" + "github.com/vulpemventures/nigiri/cli/constants" +) + +var services = map[string]bool{ + "node": true, + "esplora": true, + "electrs": true, + "chopsticks": true, +} + +// Controller implements useful functions to securely parse flags provided at run-time +// and to interact with the resources used by Nigiri: +// * docker +// * .env for docker-compose +// * nigiri.config.json config file +type Controller struct { + config *config.Config + parser *Parser + docker *Docker + env *Env +} + +// NewController returns a new Controller instance or error +func NewController() (*Controller, error) { + c := &Controller{} + + dockerClient := &Docker{} + if err := dockerClient.New(); err != nil { + return nil, err + } + c.env = &Env{} + c.parser = newParser(services) + c.docker = dockerClient + c.config = &config.Config{} + return c, nil +} + +// ParseNetwork checks if a valid network has been provided +func (c *Controller) ParseNetwork(network string) error { + return c.parser.parseNetwork(network) +} + +// ParseDatadir checks if a valid datadir has been provided +func (c *Controller) ParseDatadir(path string) error { + return c.parser.parseDatadir(path) +} + +// ParseEnv checks if a valid JSON format for docker compose has been provided +func (c *Controller) ParseEnv(env string) (string, error) { + return c.parser.parseEnvJSON(env) +} + +// ParseServiceName checks if a valid service has been provided +func (c *Controller) ParseServiceName(name string) error { + return c.parser.parseServiceName(name) +} + +// IsNigiriRunning checks if nigiri is running by looking if the bitcoin +// services are in the list of docker running containers +func (c *Controller) IsNigiriRunning() (bool, error) { + if !c.docker.isDockerRunning() { + return false, constants.ErrDockerNotRunning + } + return c.docker.isNigiriRunning(), nil +} + +// IsNigiriStopped checks that nigiri is not actually running and that +// the bitcoin services appear in the list of non running containers +func (c *Controller) IsNigiriStopped() (bool, error) { + if !c.docker.isDockerRunning() { + return false, constants.ErrDockerNotRunning + } + return c.docker.isNigiriStopped(), nil +} + +// WriteComposeEnvironment creates a .env in datadir used by +// the docker-compose YAML file resource +func (c *Controller) WriteComposeEnvironment(datadir, env string) error { + return c.env.writeEnvForCompose(datadir, env) +} + +// ReadComposeEnvironment reads from .env and returns it as a useful type +func (c *Controller) ReadComposeEnvironment(datadir string) (map[string]interface{}, error) { + return c.env.readEnvForCompose(datadir) +} + +// LoadComposeEnvironment returns an os.Environ created from datadir/.env resource +func (c *Controller) LoadComposeEnvironment(datadir string) []string { + return c.env.load(datadir) +} + +// WriteConfigFile writes the configuration handled by the underlying viper +// into the file at filedir path +func (c *Controller) WriteConfigFile(filedir string) error { + return c.config.WriteConfig(filedir) +} + +// ReadConfigFile reads the configuration of the file at filedir path +func (c *Controller) ReadConfigFile(filedir string) error { + return c.config.ReadFromFile(filedir) +} + +// GetConfigBoolField returns a bool field of the config file +func (c *Controller) GetConfigBoolField(field string) bool { + return c.config.GetBool(field) +} + +// GetConfigStringField returns a string field of the config file +func (c *Controller) GetConfigStringField(field string) string { + return c.config.GetString(field) +} + +// NewDatadirFromDefault copies the default ~/.nigiri at the desidered path +// and cleans the docker volumes to make a fresh Nigiri instance +func (c *Controller) NewDatadirFromDefault(datadir string) error { + defaultDatadir := c.config.GetPath() + cmd := exec.Command("cp", "-R", filepath.Join(defaultDatadir, "resources"), datadir) + if err := cmd.Run(); err != nil { + return err + } + c.CleanResourceVolumes(datadir) + return nil +} + +// GetResourcePath returns the absolute path of the requested resource +func (c *Controller) GetResourcePath(datadir, resource string) string { + if resource == "compose" { + network := c.config.GetString(constants.Network) + if c.config.GetBool(constants.AttachLiquid) { + network += "-liquid" + } + return filepath.Join(datadir, "resources", fmt.Sprintf("docker-compose-%s.yml", network)) + } + if resource == "env" { + return filepath.Join(datadir, ".env") + } + if resource == "config" { + return filepath.Join(datadir, "nigiri.config.json") + } + return "" +} + +// CleanResourceVolumes recursively deletes the content of the +// docker volumes in the resource path +func (c *Controller) CleanResourceVolumes(datadir string) error { + network := c.config.GetString(constants.Network) + attachLiquid := c.config.GetBool(constants.AttachLiquid) + if attachLiquid { + network = fmt.Sprintf("liquid%s", network) + } + volumedir := filepath.Join(datadir, "resources", "volumes", network) + + return c.docker.cleanVolumes(volumedir) +} + +// GetDefaultDatadir returns the absolute path of Nigiri default directory +func (c *Controller) GetDefaultDatadir() string { + return c.config.GetPath() +} + +// GetServiceName returns the right name of the requested service +// If requesting a name for a Liquid service, then the suffix -liquid +// is appended to the canonical name except for "node" that is mapped +// to either "bitcoin" or "liquid" +func (c *Controller) GetServiceName(name string, liquid bool) string { + service := name + if service == "node" { + service = "bitcoin" + } + if liquid { + if service == "bitcoin" { + service = "liquid" + } else { + service = fmt.Sprintf("%s-liquid", service) + } + } + + return service +} diff --git a/cli/controller/docker.go b/cli/controller/docker.go new file mode 100644 index 0000000..3c8a16d --- /dev/null +++ b/cli/controller/docker.go @@ -0,0 +1,101 @@ +package controller + +import ( + "context" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "github.com/vulpemventures/nigiri/cli/constants" +) + +// Docker type handles interfaction with containers via docker and docker-compose +type Docker struct { + cli *client.Client +} + +// New initialize a new Docker handler +func (d *Docker) New() error { + cli, err := client.NewEnvClient() + if err != nil { + return err + } + d.cli = cli + return nil +} + +func (d *Docker) isDockerRunning() bool { + _, err := d.cli.ContainerList(context.Background(), types.ContainerListOptions{All: false}) + if err != nil { + return false + } + return true +} + +func (d *Docker) findNigiriContainers(listAllContainers bool) bool { + containers, _ := d.cli.ContainerList(context.Background(), types.ContainerListOptions{All: listAllContainers}) + + if len(containers) <= 0 { + return false + } + + images := []string{} + for _, c := range containers { + images = append(images, c.Image) + } + + for _, nigiriImage := range constants.NigiriImages { + // just check if services for bitcoin chain are up and running + if !strings.Contains(nigiriImage, "liquid") { + if !contains(images, nigiriImage) { + return false + } + } + } + return true +} + +func (d *Docker) isNigiriRunning() bool { + return d.findNigiriContainers(false) +} + +func (d *Docker) isNigiriStopped() bool { + isRunning := d.isNigiriRunning() + if !isRunning { + return d.findNigiriContainers(true) + } + return false +} + +func (d *Docker) cleanVolumes(path string) error { + subdirs, err := ioutil.ReadDir(path) + if err != nil { + return err + } + + for _, d := range subdirs { + path := filepath.Join(path, d.Name()) + subsubdirs, _ := ioutil.ReadDir(path) + for _, sd := range subsubdirs { + if sd.IsDir() { + if err := os.RemoveAll(filepath.Join(path, sd.Name())); err != nil { + return err + } + } + } + } + + return nil +} + +func contains(list []string, elem string) bool { + for _, l := range list { + if l == elem { + return true + } + } + return false +} diff --git a/cli/controller/env.go b/cli/controller/env.go new file mode 100644 index 0000000..c319c46 --- /dev/null +++ b/cli/controller/env.go @@ -0,0 +1,124 @@ +package controller + +import ( + "bufio" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "strconv" + "strings" + + "github.com/vulpemventures/nigiri/cli/constants" +) + +// Env implements functions for interacting with the environment file used by +// docker-compose to dynamically set service ports and variables +type Env struct{} + +func (e *Env) writeEnvForCompose(path, strJSON string) error { + var env map[string]interface{} + err := json.Unmarshal([]byte(strJSON), &env) + if err != nil { + return constants.ErrMalformedJSON + } + + fileContent := "" + for chain, services := range env["ports"].(map[string]interface{}) { + for k, v := range services.(map[string]interface{}) { + fileContent += fmt.Sprintf("%s_%s_PORT=%d\n", strings.ToUpper(chain), strings.ToUpper(k), int(v.(float64))) + } + } + for hostname, url := range env["urls"].(map[string]interface{}) { + fileContent += fmt.Sprintf("%s_URL=%s\n", strings.ToUpper(hostname), url.(string)) + } + + return ioutil.WriteFile(path, []byte(fileContent), os.ModePerm) +} + +func (e *Env) readEnvForCompose(path string) (map[string]interface{}, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + + ports := map[string]map[string]int{ + "bitcoin": map[string]int{}, + "liquid": map[string]int{}, + } + urls := map[string]string{} + // Each line is in the format PREFIX_SERVICE_NAME_SUFFIX=value + // PREFIX is either 'BITCOIN' or 'LIQUID', while SUFFIX is either 'PORT' or 'URL' + for scanner.Scan() { + line := scanner.Text() + splitLine := strings.Split(line, "=") + key := splitLine[0] + + if strings.Contains(key, "PORT") { + value, _ := strconv.Atoi(splitLine[1]) + chain := "bitcoin" + if strings.HasPrefix(key, strings.ToUpper("liquid")) { + chain = "liquid" + } + prefix := strings.ToUpper(fmt.Sprintf("%s_", chain)) + suffix := "_PORT" + trimmedKey := strings.ToLower( + strings.TrimSuffix(strings.TrimPrefix(key, prefix), suffix), + ) + ports[chain][trimmedKey] = value + } else { + // Here the prefix is not trimmed + value := splitLine[1] + suffix := "_URL" + trimmedKey := strings.ToLower(strings.TrimSuffix(key, suffix)) + urls[trimmedKey] = value + } + } + + return map[string]interface{}{"ports": ports, "urls": urls}, nil +} + +func (e *Env) load(path string) []string { + content, _ := ioutil.ReadFile(path) + lines := strings.Split(string(content), "\n") + env := os.Environ() + for _, line := range lines { + if line != "" { + env = append(env, line) + } + } + + return env +} + +type envPortsData struct { + Node int `json:"node,omitempty"` + Esplora int `json:"esplora,omitempty"` + Electrs int `json:"electrs,omitempty"` + ElectrsRPC int `json:"electrs_rpc,omitempty"` + Chopsticks int `json:"chopsticks,omitempty"` +} +type envPorts struct { + Bitcoin *envPortsData `json:"bitcoin,omitempty"` + Liquid *envPortsData `json:"liquid,omitempty"` +} +type envUrls struct { + BitcoinEsplora string `json:"bitcoin_esplora,omitempty"` + LiquidEsplora string `json:"liquid_esplora,omitempty"` +} +type envJSON struct { + Ports *envPorts `json:"ports,omitempty"` + Urls *envUrls `json:"urls,omitempty"` +} + +func (e envJSON) copy() envJSON { + var v envJSON + bytes, _ := json.Marshal(e) + json.Unmarshal(bytes, &v) + + return v +} diff --git a/cli/controller/parser.go b/cli/controller/parser.go new file mode 100644 index 0000000..aeb512f --- /dev/null +++ b/cli/controller/parser.go @@ -0,0 +1,58 @@ +package controller + +import ( + "encoding/json" + "fmt" + "path/filepath" + + "github.com/vulpemventures/nigiri/cli/constants" +) + +// Parser implements functions for parsing flags, JSON files +// and system directories passed to the CLI commands +type Parser struct { + services map[string]bool +} + +func newParser(services map[string]bool) *Parser { + p := &Parser{services} + return p +} + +func (p *Parser) parseNetwork(network string) error { + for _, n := range constants.AvaliableNetworks { + if network == n { + return nil + } + } + return constants.ErrInvalidNetwork +} + +func (p *Parser) parseDatadir(path string) error { + if !filepath.IsAbs(path) { + return constants.ErrInvalidDatadir + } + return nil +} + +func (p *Parser) parseEnvJSON(strJSON string) (string, error) { + // merge default json and incoming json by parsing DefaultEnv to + // envJSON type and then parsing the incoming json using the same variable + var parsedJSON envJSON + defaultJSON, _ := json.Marshal(constants.DefaultEnv) + json.Unmarshal(defaultJSON, &parsedJSON) + err := json.Unmarshal([]byte(strJSON), &parsedJSON) + if err != nil { + fmt.Println(err) + return "", constants.ErrMalformedJSON + } + merged, _ := json.Marshal(parsedJSON) + return string(merged), nil +} + +func (p *Parser) parseServiceName(name string) error { + if !p.services[name] { + return constants.ErrInvalidServiceName + } + return nil +} diff --git a/cli/controller/parser_test.go b/cli/controller/parser_test.go new file mode 100644 index 0000000..8acee1e --- /dev/null +++ b/cli/controller/parser_test.go @@ -0,0 +1,159 @@ +package controller + +import ( + "encoding/json" + "os" + "testing" + + "github.com/vulpemventures/nigiri/cli/constants" +) + +func TestParserParseNetwork(t *testing.T) { + p := &Parser{} + + validNetworks := []string{"regtest"} + for _, n := range validNetworks { + err := p.parseNetwork(n) + if err != nil { + t.Fatal(err) + } + } +} + +func TestParserParseDatadir(t *testing.T) { + p := &Parser{} + + currentDir, _ := os.Getwd() + validDatadirs := []string{currentDir} + for _, n := range validDatadirs { + err := p.parseDatadir(n) + if err != nil { + t.Fatal(err) + } + } +} + +func TestParserParseEnvJSON(t *testing.T) { + p := &Parser{} + + for _, e := range testJSONs { + parsedJSON, _ := json.Marshal(e) + mergedJSON, err := p.parseEnvJSON(string(parsedJSON)) + if err != nil { + t.Fatal(err) + } + t.Log(mergedJSON) + } +} + +func TestParserParseNetworkShouldFail(t *testing.T) { + p := &Parser{} + + invalidNetworks := []string{"simnet", "testnet"} + for _, n := range invalidNetworks { + err := p.parseNetwork(n) + if err == nil { + t.Fatalf("Should have been failed before") + } + if err != constants.ErrInvalidNetwork { + t.Fatalf("Got: %s, wanted: %s", err, constants.ErrInvalidNetwork) + } + } +} + +func TestParserParseDatadirShouldFail(t *testing.T) { + p := &Parser{} + + invalidDatadirs := []string{"."} + for _, d := range invalidDatadirs { + err := p.parseDatadir(d) + if err == nil { + t.Fatalf("Should have been failed before") + } + if err != constants.ErrInvalidDatadir { + t.Fatalf("Got: %s, wanted: %s", err, constants.ErrInvalidDatadir) + } + } +} + +var testJSONs = []map[string]interface{}{ + // only btc services + { + "urls": map[string]string{ + "bitcoin_esplora": "https://blockstream.info/", + }, + "ports": map[string]map[string]int{ + "bitcoin": map[string]int{ + "node": 1111, + "esplora": 2222, + "electrs": 3333, + "chopsticks": 4444, + }, + }, + }, + // btc and liquid services + { + "urls": map[string]string{ + "bitcoin_esplora": "https://blockstream.info/", + "liquid_esplora": "http://blockstream.info/liquid", + }, + "ports": map[string]map[string]int{ + "bitcoin": map[string]int{ + "node": 1111, + "esplora": 2222, + "electrs": 3333, + "chopsticks": 4444, + }, + "liquid": map[string]int{ + "node": 5555, + "esplora": 6666, + "electrs": 7777, + "chopsticks": 8888, + }, + }, + }, + // incomplete examples: + // incomplete bitcoin services + { + "ports": map[string]map[string]int{ + "bitcoin": map[string]int{ + "esplora": 1111, + "electrs": 2222, + "chopsticks": 3333, + }, + }, + "urls": map[string]string{ + "bitcoin_esplora": "http://test.com/api", + }, + }, + // bitcoin services ports and liquid service url + { + "ports": map[string]map[string]int{ + "bitcoin": map[string]int{ + "node": 1111, + "esplora": 2222, + "electrs": 3333, + "chopsticks": 4444, + }, + }, + "urls": map[string]string{ + "liquid_esplora": "http://test.com/liquid/api", + }, + }, + // liquid services ports and bitcoin service url + { + "ports": map[string]map[string]int{ + "liquid": map[string]int{ + "node": 1111, + "esplora": 2222, + "electrs": 3333, + "chopsticks": 4444, + }, + }, + "urls": map[string]string{ + "bitcoin_esplora": "http://test.com/api", + }, + }, + // empty config + {}, +} diff --git a/resources/docker-compose-regtest-liquid.yml b/resources/docker-compose-regtest-liquid.yml index e51e18d..1d90f79 100644 --- a/resources/docker-compose-regtest-liquid.yml +++ b/resources/docker-compose-regtest-liquid.yml @@ -52,7 +52,7 @@ services: - bitcoin ports: - ${BITCOIN_ELECTRS_RPC_PORT}:60401 - - 3002:3002 + - ${BITCOIN_ELECTRS_PORT}:3002 volumes: - ./volumes/liquidregtest/config/:/config restart: unless-stopped @@ -85,7 +85,7 @@ services: - liquid ports: - ${LIQUID_ELECTRS_RPC_PORT}:60401 - - 3022:3002 + - ${LIQUID_ELECTRS_PORT}:3002 volumes: - ./volumes/liquidregtest/liquid-config/:/config restart: unless-stopped @@ -96,21 +96,25 @@ services: local: ipv4_address: 10.10.0.14 links: - - electrs + - chopsticks depends_on: - - electrs + - chopsticks + environment: + API_URL: ${BITCOIN_ESPLORA_URL} ports: - ${BITCOIN_ESPLORA_PORT}:5000 restart: unless-stopped esplora-liquid: - image: vulpemventures/esplora-liquid:latest + image: vulpemventures/esplora:latest networks: local: ipv4_address: 10.10.0.15 links: - - electrs-liquid + - chopsticks-liquid depends_on: - - electrs-liquid + - chopsticks-liquid + environment: + API_URL: ${LIQUID_ESPLORA_URL} ports: - ${LIQUID_ESPLORA_PORT}:5000 restart: unless-stopped diff --git a/resources/docker-compose-regtest.yml b/resources/docker-compose-regtest.yml index 57eefa8..b23b502 100644 --- a/resources/docker-compose-regtest.yml +++ b/resources/docker-compose-regtest.yml @@ -41,7 +41,7 @@ services: - bitcoin ports: - ${BITCOIN_ELECTRS_RPC_PORT}:60401 - - 3002:3002 + - ${BITCOIN_ELECTRS_PORT}:3002 volumes: - ./volumes/regtest/config/:/config restart: unless-stopped @@ -52,9 +52,11 @@ services: local: ipv4_address: 10.10.0.12 links: - - electrs + - chopsticks depends_on: - - electrs + - chopsticks + environment: + API_URL: ${BITCOIN_ESPLORA_URL} ports: - ${BITCOIN_ESPLORA_PORT}:5000 restart: unless-stopped