diff --git a/.github/workflows/artifacts.yaml b/.github/workflows/artifacts.yaml deleted file mode 100644 index 6222290..0000000 --- a/.github/workflows/artifacts.yaml +++ /dev/null @@ -1,45 +0,0 @@ -name: Artifacts - -on: - push: - tags: - - v* - -jobs: - build: - name: Build artifacts - strategy: - matrix: - platform: [ ubuntu-latest ] - go-version: [ 1.15.x ] - os: [ linux, darwin, windows ] - architecture: [ amd64 ] - - runs-on: ${{ matrix.platform }} - - steps: - - name: Install Go - uses: actions/setup-go@v1 - with: - go-version: ${{ matrix.go-version }} - - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - - - name: Fetch dependencies - run: go mod vendor - - - name: Get the version - id: get_version - run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} - - - name: Build - run: GOOS=${{ matrix.os }} GOARCH=${{ matrix.architecture }} make VERSION=${{ steps.get_version.outputs.VERSION }} build - - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - name: ${{ matrix.os }}-${{ matrix.architecture }} - path: | - xud-launcher - xud-launcher.exe diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..89e0564 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,78 @@ +name: Release + +on: + push: + tags: + - v* + +jobs: + create_release: + name: Create a release + runs-on: ubuntu-latest + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + steps: + - name: Create a release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + prerelease: true + draft: true + + build: + needs: create_release + name: Build artifacts + strategy: + matrix: + platform: [ ubuntu-latest ] + go-version: [ 1.15.x ] + os: [ linux, darwin, windows ] + arch: [ amd64 ] + + runs-on: ${{ matrix.platform }} + + steps: + - name: Setup Go + uses: actions/setup-go@v1 + with: + go-version: ${{ matrix.go-version }} + + - name: Checkout + uses: actions/checkout@v2 + + - name: Fetch dependencies + run: go mod vendor + + - name: Get the version + id: get_version + run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} + + - name: Build + env: + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + run: | + make VERSION=${{ steps.get_version.outputs.VERSION }} build + make zip + + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: ${{ matrix.os }}-${{ matrix.architecture }} + path: | + xud-launcher + xud-launcher.exe + + - name: Upload release assets + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create_release.outputs.upload_url }} + asset_path: xud-launcher.zip + asset_name: xud-launcher-${{ matrix.os }}-${{ matrix.arch }}.zip + asset_content_type: application/zip diff --git a/Makefile b/Makefile index c5819eb..c82d89a 100644 --- a/Makefile +++ b/Makefile @@ -4,13 +4,21 @@ GO_BIN := ${GOPATH}/bin GOBUILD := go build -v -VERSION := v1.0.0 +VERSION := local COMMIT := $(shell git rev-parse HEAD) ifeq ($(OS),Windows_NT) TIMESTAMP := $(shell powershell.exe scripts\get_timestamp.ps1) else TIMESTAMP := $(shell date +%s) endif + +ifeq ($(GOOS), windows) + OUTPUT := xud-launcher.exe +else + OUTPUT := xud-launcher +endif + + LDFLAGS := -ldflags "-w -s \ -X $(PKG)/build.Version=$(VERSION) \ -X $(PKG)/build.GitCommit=$(COMMIT) \ @@ -18,12 +26,14 @@ LDFLAGS := -ldflags "-w -s \ default: build - -# -# Building -# - build: $(GOBUILD) $(LDFLAGS) +zip: + zip --junk-paths xud-launcher.zip $(OUTPUT) + +clean: + rm -f xud-launcher + rm -f xud-launcher.zip + .PHONY: build diff --git a/cmd/attach.go b/cmd/attach.go deleted file mode 100644 index dd6b4b4..0000000 --- a/cmd/attach.go +++ /dev/null @@ -1,24 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -func init() { - rootCmd.AddCommand(attachCmd) -} - -var attachCmd = &cobra.Command{ - Use: "attach", - Short: "Attach this launcher to xud-docker proxy(api) container", - Long: `The xud-docker proxy(api) container will delegate all container operations to an attached xud-launcher. So -we don't need to map any docker.sock file into proxy container and it will make the proxy robust across different -platforms. The proxy will communicate with the launcher through a WebSocket connection by reusing the HTTPS/HTTP port -exposed to the host`, - Run: func(cmd *cobra.Command, args []string) { - // TODO implement the attach command - fmt.Println("To be implemented!") - }, -} diff --git a/cmd/cleanup.go b/cmd/cleanup.go deleted file mode 100644 index 8f3f0fa..0000000 --- a/cmd/cleanup.go +++ /dev/null @@ -1,133 +0,0 @@ -package cmd - -import ( - "bufio" - "bytes" - "fmt" - "github.com/spf13/cobra" - "os" - "os/exec" - "strings" -) - -var cleanupCmd = &cobra.Command{ - Use: "cleanup", - Short: "Cleanup the XUD environment", - Run: func(cmd *cobra.Command, args []string) { - // get running containers - containers := getRunningContainers(network) - if len(containers) > 0 { - fmt.Println("Stopping containers...") - for _, c := range containers { - stopContainer(c) - } - } - - containers = getContainers(network) - if len(containers) > 0 { - fmt.Println("Removing containers...") - for _, c := range containers { - removeContainer(c) - } - } - - networks := getNetworks(network) - if len(containers) > 0 { - fmt.Println("Removing networks...") - for _, n := range networks { - removeNetwork(n) - } - } - - if _, err := os.Stat(networkDir); !os.IsNotExist(err) { - fmt.Printf("Do you want to remove all %s data (%s)? [y/N] ", network, networkDir) - var reply string - _, err := fmt.Scanln(&reply) - if err != nil { - logger.Fatal(err) - } - reply = strings.ToLower(reply) - if reply == "y" || reply == "yes" { - fmt.Println("Removing data...") - removeDir(networkDir) - } - } - }, -} - -func init() { - rootCmd.AddCommand(cleanupCmd) -} - -func getRunningContainers(network string) []string { - var result []string - filter := fmt.Sprintf("name=%s_", network) - out, err := exec.Command("docker", "ps", "--filter", filter, "--format", "{{.ID}}").Output() - if err != nil { - logger.Fatal(err) - } - s := bufio.NewScanner(bytes.NewReader(out)) - for s.Scan() { - result = append(result, s.Text()) - } - return result -} - -func getContainers(network string) []string { - var result []string - filter := fmt.Sprintf("name=%s_", network) - out, err := exec.Command("docker", "ps", "--filter", filter, "--format", "{{.ID}}", "-a").Output() - if err != nil { - logger.Fatal(err) - } - s := bufio.NewScanner(bytes.NewReader(out)) - for s.Scan() { - result = append(result, s.Text()) - } - return result -} - -func getNetworks(network string) []string { - var result []string - filter := fmt.Sprintf("name=%s_", network) - out, err := exec.Command("docker", "network", "ls", "--filter", filter, "--format", "{{.ID}}").Output() - if err != nil { - logger.Fatal(err) - } - s := bufio.NewScanner(bytes.NewReader(out)) - for s.Scan() { - result = append(result, s.Text()) - } - return result -} - -func stopContainer(id string) { - fmt.Println(id) - err := exec.Command("docker", "stop", id).Run() - if err != nil { - logger.Fatal(err) - } -} - -func removeContainer(id string) { - fmt.Println(id) - err := exec.Command("docker", "rm", id).Run() - if err != nil { - logger.Fatal(err) - } -} - -func removeNetwork(id string) { - fmt.Println(id) - err := exec.Command("docker", "network", "rm", id).Run() - if err != nil { - logger.Fatal(err) - } -} - -func removeDir(path string) { - err := os.RemoveAll(path) - if err != nil { - logger.Fatalf("Failed to remove %s: %s", path, err) - } -} diff --git a/cmd/compose.go b/cmd/compose.go deleted file mode 100644 index dded2b8..0000000 --- a/cmd/compose.go +++ /dev/null @@ -1,170 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" - "os" - "os/exec" -) - -func init() { - rootCmd.AddCommand(upCmd) - rootCmd.AddCommand(downCmd) - rootCmd.AddCommand(startCmd) - rootCmd.AddCommand(stopCmd) - rootCmd.AddCommand(restartCmd) - rootCmd.AddCommand(logsCmd) - rootCmd.AddCommand(execCmd) - rootCmd.AddCommand(pullCmd) -} - -var upCmd = &cobra.Command{ - Use: "up", - Short: "A docker-compose up wrapper", - Run: func(cmd *cobra.Command, args []string) { - err := os.Chdir(networkDir) - if err != nil { - logger.Fatal(err) - } - args = append([]string{"up"}, args...) - c := exec.Command("docker-compose", args...) - c.Stdout = os.Stdout - c.Stderr = os.Stderr - err = c.Run() - if err != nil { - logger.Fatal(err) - } - }, -} - -var downCmd = &cobra.Command{ - Use: "down", - Short: "A docker-compose down wrapper", - Run: func(cmd *cobra.Command, args []string) { - err := os.Chdir(networkDir) - if err != nil { - logger.Fatal(err) - } - args = append([]string{"down"}, args...) - c := exec.Command("docker-compose", args...) - c.Stdout = os.Stdout - c.Stderr = os.Stderr - err = c.Run() - if err != nil { - logger.Fatal(err) - } - }, -} - -var startCmd = &cobra.Command{ - Use: "start", - Short: "A docker-compose start wrapper", - Run: func(cmd *cobra.Command, args []string) { - err := os.Chdir(networkDir) - if err != nil { - logger.Fatal(err) - } - args = append([]string{"start"}, args...) - c := exec.Command("docker-compose", args...) - c.Stdout = os.Stdout - c.Stderr = os.Stderr - err = c.Run() - if err != nil { - logger.Fatal(err) - } - }, -} - -var stopCmd = &cobra.Command{ - Use: "stop", - Short: "A docker-compose stop wrapper", - Run: func(cmd *cobra.Command, args []string) { - err := os.Chdir(networkDir) - if err != nil { - logger.Fatal(err) - } - args = append([]string{"stop"}, args...) - c := exec.Command("docker-compose", args...) - c.Stdout = os.Stdout - c.Stderr = os.Stderr - err = c.Run() - if err != nil { - logger.Fatal(err) - } - }, -} - -var restartCmd = &cobra.Command{ - Use: "restart", - Short: "A docker-compose restart wrapper", - Run: func(cmd *cobra.Command, args []string) { - err := os.Chdir(networkDir) - if err != nil { - logger.Fatal(err) - } - args = append([]string{"restart"}, args...) - c := exec.Command("docker-compose", args...) - c.Stdout = os.Stdout - c.Stderr = os.Stderr - err = c.Run() - if err != nil { - logger.Fatal(err) - } - }, -} - -var logsCmd = &cobra.Command{ - Use: "logs", - Short: "A docker-compose logs wrapper", - Run: func(cmd *cobra.Command, args []string) { - err := os.Chdir(networkDir) - if err != nil { - logger.Fatal(err) - } - args = append([]string{"logs"}, args...) - c := exec.Command("docker-compose", args...) - c.Stdout = os.Stdout - c.Stderr = os.Stderr - err = c.Run() - if err != nil { - logger.Fatal(err) - } - }, -} - -var execCmd = &cobra.Command{ - Use: "exec", - Short: "A docker-compose exec wrapper", - Run: func(cmd *cobra.Command, args []string) { - err := os.Chdir(networkDir) - if err != nil { - logger.Fatal(err) - } - args = append([]string{"exec"}, args...) - c := exec.Command("docker-compose", args...) - c.Stdout = os.Stdout - c.Stderr = os.Stderr - err = c.Run() - if err != nil { - logger.Fatal(err) - } - }, -} - -var pullCmd = &cobra.Command{ - Use: "pull", - Short: "A docker-compose pull wrapper", - Run: func(cmd *cobra.Command, args []string) { - err := os.Chdir(networkDir) - if err != nil { - logger.Fatal(err) - } - args = append([]string{"pull"}, args...) - c := exec.Command("docker-compose", args...) - c.Stdout = os.Stdout - c.Stderr = os.Stderr - err = c.Run() - if err != nil { - logger.Fatal(err) - } - }, -} diff --git a/cmd/console.go b/cmd/console.go deleted file mode 100644 index 3abda95..0000000 --- a/cmd/console.go +++ /dev/null @@ -1,19 +0,0 @@ -package cmd - -import ( - "os/exec" - - "github.com/spf13/cobra" -) - -func init() { - rootCmd.AddCommand(consoleCmd) -} - -var consoleCmd = &cobra.Command{ - Use: "console", - Short: "Open your native console with some useful commands for xud-docker", - Run: func(cmd *cobra.Command, args []string) { - exec.Command("/bin/bash") - }, -} diff --git a/cmd/gen.go b/cmd/gen.go deleted file mode 100644 index 1fe2b5f..0000000 --- a/cmd/gen.go +++ /dev/null @@ -1,253 +0,0 @@ -package cmd - -import ( - "encoding/json" - "fmt" - "github.com/ExchangeUnion/xud-launcher/service" - "github.com/spf13/cobra" - "os" - "path/filepath" - "strings" - "time" -) - -var ( - config service.SharedConfig - services map[string]service.Service -) - -func init() { - // [Add capability to restrict flag values to a set of allowed values](https://github.com/spf13/pflag/issues/236) - - genCmd.PersistentFlags().StringVar(&config.ExternalIp, "external-ip", "", "Host machine external IP address") - - var names []string - - if network == "simnet" { - names = []string{ - "lndbtc", - "lndltc", - "connext", - "xud", - "arby", - "webui", - "proxy", - } - } else { - names = []string{ - "bitcoind", - "litecoind", - "geth", - "lndbtc", - "lndltc", - "connext", - "xud", - "arby", - "boltz", - "webui", - "proxy", - } - } - - services = make(map[string]service.Service) - - logger.Debugf("Configuring subcommand \"gen\" flags") - - for _, name := range names { - s := service.NewService(name) - err := s.ConfigureFlags(genCmd, network) - if err != nil { - logger.Fatal(err) - } - services[name] = s - } - - rootCmd.AddCommand(genCmd) -} - -func Export(services []service.Service) string { - var result = "" - - result += "version: \"2.4\"\n" - - result += "services:\n" - - for _, s := range services { - - result += fmt.Sprintf(" %s:\n", s.GetName()) - - result += fmt.Sprintf(" image: %s\n", s.GetImage()) - - if s.GetHostname() != "" { - result += fmt.Sprintf(" hostname: %s\n", s.GetName()) - } - - if len(s.GetCommand()) > 0 { - result += fmt.Sprintf(" command: >\n") - for _, arg := range s.GetCommand() { - result += fmt.Sprintf(" %s\n", arg) - } - } - - if len(s.GetEnvironment()) > 0 { - result += fmt.Sprintf(" environment:\n") - for key, value := range s.GetEnvironment() { - if strings.Contains(value, "\n") { - // multiline value - result += fmt.Sprintf(" - >\n") - result += fmt.Sprintf(" %s=\n", key) - for _, line := range strings.Split(value, "\n") { - result += fmt.Sprintf(" %s\n", line) - } - } else { - result += fmt.Sprintf(" - %s=%s\n", key, value) - } - - } - } - - if len(s.GetVolumes()) > 0 { - result += fmt.Sprintf(" volumes:\n") - for _, volume := range s.GetVolumes() { - result += fmt.Sprintf(" - %s\n", volume) - } - } - - if len(s.GetPorts()) > 0 { - result += fmt.Sprintf(" ports:\n") - for _, port := range s.GetPorts() { - result += fmt.Sprintf(" - %s\n", port) - } - } - - if s.GetName() == "xud" { - result += fmt.Sprintf(" entrypoint: [\"bash\", \"-c\", \"echo /root/backup > /root/.xud/.backup-dir-value && /entrypoint.sh\"]\n") - } - } - - return result -} - -func ExportCompose(services []service.Service) { - path := filepath.Join(networkDir, "docker-compose.yml") - f, err := os.Create(path) - if err != nil { - logger.Fatal(err) - } - defer f.Close() - - var targets []service.Service - - // filter enabled services - for _, s := range services { - if s.IsDisabled() { - continue - } - targets = append(targets, s) - } - - yml := Export(targets) - _, err = f.WriteString(yml) - if err != nil { - logger.Fatal(err) - } - fmt.Printf("Exported %s\n", path) -} - -type Service struct { - Name string `json:"name"` - Rpc interface{} `json:"rpc"` - Disabled bool `json:"disabled"` - Mode string `json:"mode"` -} - -type Config struct { - Timestamp string `json:"timestamp"` - Network string `json:"network"` - Services []interface{} `json:"services"` -} - -func Export2(services []service.Service) string { - var config Config - config.Timestamp = fmt.Sprintf("%d", time.Now().Unix()) - config.Network = network - for _, s := range services { - if s.GetName() == "proxy" { - continue - } - config.Services = append(config.Services, s.ToJson()) - } - data, err := json.MarshalIndent(config, "", " ") - if err != nil { - logger.Fatal(err) - } - return string(data) -} - -func ExportConfig(services []service.Service) { - path := filepath.Join(dataDir, "config.json") - f, err := os.Create(path) - if err != nil { - logger.Fatal(err) - } - defer f.Close() - - j := Export2(services) - _, err = f.WriteString(j) - if err != nil { - logger.Fatal(err) - } - fmt.Printf("Exported %s\n", path) -} - -var genCmd = &cobra.Command{ - Use: "gen", - Short: "Generate docker-compose.yml file from xud-docker configurations", - Run: func(cmd *cobra.Command, args []string) { - config.Network = network - config.HomeDir = homeDir - config.NetworkDir = networkDir - - var order []string - if network == "simnet" { - order = []string{ - "lndbtc", - "lndltc", - "connext", - "xud", - "arby", - "webui", - "proxy", - } - } else { - order = []string{ - "bitcoind", - "litecoind", - "geth", - "lndbtc", - "lndltc", - "connext", - "xud", - "arby", - "boltz", - "webui", - "proxy", - } - } - - var targets []service.Service - - // apply for all services - for _, name := range order { - s := services[name] - err := s.Apply(&config, services) - if err != nil { - logger.Fatalf("%s: %s", name, err) - } - targets = append(targets, s) - } - - ExportCompose(targets) - ExportConfig(targets) - }, -} diff --git a/cmd/info.go b/cmd/info.go deleted file mode 100644 index 8442f97..0000000 --- a/cmd/info.go +++ /dev/null @@ -1,35 +0,0 @@ -package cmd - -import ( - "fmt" - "github.com/ExchangeUnion/xud-launcher/build" - "strconv" - "time" - - "github.com/spf13/cobra" -) - -func init() { - rootCmd.AddCommand(infoCmd) -} - -var infoCmd = &cobra.Command{ - Use: "info", - Short: "Print the basic information of xud-launcher and your xud-docker setups", - Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("Launcher Version: %s\n", GetVersion()) - fmt.Printf("Built At: %s\n", GetBuildTime()) - fmt.Printf("XUD-Docker Version: 20.12.18-01\n") - fmt.Printf("Network: %s\n", network) - fmt.Printf("Network Directory: %s\n", networkDir) - }, -} - -func GetBuildTime() string { - i, err := strconv.ParseInt(build.Timestamp, 10, 64) - if err != nil { - panic(err) - } - tm := time.Unix(i, 0) - return fmt.Sprint(tm) -} diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 53d7f97..0000000 --- a/cmd/root.go +++ /dev/null @@ -1,179 +0,0 @@ -package cmd - -import ( - "errors" - "fmt" - "github.com/mattn/go-colorable" - "github.com/mitchellh/go-homedir" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "os" - "path/filepath" - "runtime" - "strings" -) - -var ( - logger = initLogger() - validNetworks = []string{"mainnet", "testnet", "simnet"} - network = initNetwork() - - homeDir string - networkDir string - dataDir string - logsDir string - - rootCmd = &cobra.Command{ - Use: "xud-launcher", - Short: fmt.Sprintf("XUD environment launcher (%s)", network), - } -) - -func getHomeDir() string { - homeDir, err := homedir.Dir() - if err != nil { - panic(err) - } - switch runtime.GOOS { - case "linux": - return filepath.Join(homeDir, ".xud-docker") - case "darwin": - return filepath.Join(homeDir, "Library", "Application Support", "XudDocker") - case "windows": - return filepath.Join(homeDir, "AppData", "Local", "XudDocker") - default: - panic(errors.New("unsupported platform: " + runtime.GOOS)) - } -} - -// Execute executes the root command. -func Execute() error { - return rootCmd.Execute() -} - -func ensureDir(path string) { - if _, err := os.Stat(path); os.IsNotExist(err) { - if err := os.Mkdir(path, os.ModeDir|0700); err != nil { - panic(err) - } - logger.Debugf("Created folder: " + path) - } -} - -func initNetwork() string { - n := os.Getenv("NETWORK") - n = strings.TrimSpace(n) - n = strings.ToLower(n) - if n == "" { - logger.Debug("Use network: mainnet") - return "mainnet" // default network - } - var valid = false - for _, vn := range validNetworks { - if n == vn { - valid = true - break - } - } - if !valid { - logger.Fatalf("Invalid network: %s", n) - } - logger.Debugf("Use network: %s", n) - return n -} - -func initLogger() *logrus.Entry { - lv := os.Getenv("LOG_LEVEL") - lv = strings.TrimSpace(lv) - lv = strings.ToLower(lv) - if lv == "" { - logrus.SetLevel(logrus.WarnLevel) - } else { - switch lv { - case "trace": - logrus.SetLevel(logrus.TraceLevel) - case "debug": - logrus.SetLevel(logrus.DebugLevel) - case "info": - logrus.SetLevel(logrus.InfoLevel) - case "warn": - logrus.SetLevel(logrus.WarnLevel) - case "error": - logrus.SetLevel(logrus.ErrorLevel) - case "fatal": - logrus.SetLevel(logrus.FatalLevel) - case "panic": - logrus.SetLevel(logrus.PanicLevel) - } - } - logrus.SetFormatter(&logrus.TextFormatter{ - FullTimestamp: true, - TimestampFormat: "2006-01-02 15:04:05.000", - ForceColors: true, - }) - if runtime.GOOS == "windows" { - logrus.SetOutput(colorable.NewColorableStdout()) - } - logger := logrus.NewEntry(logrus.StandardLogger()) - return logger -} - -func init() { - homeDir = getHomeDir() - ensureDir(homeDir) - - logger.Debugf("Configuring global flags") - - key := fmt.Sprintf("%s-dir", network) - - rootCmd.PersistentFlags().StringVar( - &networkDir, - key, - filepath.Join(homeDir, network), - fmt.Sprintf("%s environment folder", strings.Title(network)), - ) - - if err := viper.BindPFlag(key, rootCmd.PersistentFlags().Lookup(key)); err != nil { - logger.Fatal("Failed to bind Viper key %s: %s", key, err) - } - - cobra.OnInitialize(initConfig) -} - -func initConfig() { - generalConf := filepath.Join(homeDir, "xud-docker.conf") - logger.Debugf("Loading general config file: %s", generalConf) - - viper.SetConfigFile(generalConf) - viper.SetConfigType("toml") - viper.AutomaticEnv() - - err := viper.ReadInConfig() - if err != nil { - logger.Debugf("Failed to load general config: %s", err) - } - - ensureDir(networkDir) - - dataDir = filepath.Join(networkDir, "data") - ensureDir(dataDir) - logsDir = filepath.Join(networkDir, "logs") - ensureDir(logsDir) - - logger.Debugf("homeDir=%s", homeDir) - logger.Debugf("networkDir=%s", networkDir) - logger.Debugf("dataDir=%s", dataDir) - logger.Debugf("logsDir=%s", logsDir) - - networkConf := filepath.Join(networkDir, fmt.Sprintf("%s.conf", network)) - logger.Debugf("Loading network config file: %s", networkConf) - - viper.SetConfigFile(networkConf) - viper.SetConfigType("toml") - - err = viper.MergeInConfig() - if err != nil { - logger.Debugf("Failed to load network config: %s", err) - } -} diff --git a/cmd/setup.go b/cmd/setup.go deleted file mode 100644 index 84ae573..0000000 --- a/cmd/setup.go +++ /dev/null @@ -1,266 +0,0 @@ -package cmd - -import ( - "bytes" - "crypto/tls" - "encoding/json" - "errors" - "fmt" - "github.com/fatih/color" - "github.com/spf13/cobra" - "io/ioutil" - "net/http" - "os" - "os/exec" - "path/filepath" - "strings" - "sync" - "time" -) - -var ( - restore bool - backupDir string -) - -const ( - DefaultPassword = "OpenDEX!Rocks" -) - -func init() { - setupCmd.PersistentFlags().String("wallet-password", "", "XUD master wallet password") - setupCmd.PersistentFlags().StringVar(&backupDir, "backup-dir", "", "XUD backup location") - setupCmd.PersistentFlags().BoolVar(&restore, "restore", true, "Restore wallets") - - rootCmd.AddCommand(setupCmd) -} - -func runCommand(name string, args ...string) { - c := exec.Command(name, args...) - c.Stdout = os.Stdout - c.Stderr = os.Stderr - - color.Blue(c.String()) - err := c.Run() - if err != nil { - fmt.Printf("🐞 %s\n", err) - os.Exit(1) - } - fmt.Println() -} - -var setupCmd = &cobra.Command{ - Use: "setup", - Short: "Bring up your XUD environment in one command", - Run: func(cmd *cobra.Command, args []string) { - var err error - - logfile := filepath.Join(networkDir, "logs", fmt.Sprintf("%s.log", network)) - f, err := os.Create(logfile) - if err != nil { - logger.Fatalf("Failed to create %s: %s", logfile, err) - } - defer f.Close() - - launcher := os.Args[0] - - logger.Debugf("Generate files") - runCommand(launcher, "gen") - - logger.Debugf("Pulling images") - runCommand(launcher, "pull") - - logger.Debugf("Starting proxy") - runCommand(launcher, "up", "--", "-d", "proxy") - - logger.Debugf("Starting lndbtc, lndltc and connext") - runCommand(launcher, "up", "--", "-d", "lndbtc", "lndltc", "connext") - - // FIXME enable tls verification - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - - logger.Debugf("Waiting for LNDs to be synced") - _, err = f.WriteString("Waiting for XUD dependencies to be ready\n") - if err != nil { - logger.Fatalf("Failed to write to %s: %s", logfile, err) - } - - var lnds = map[string]string{ - "lndbtc": "0.00% (0/0)", - "lndltc": "0.00% (0/0)", - } - var lndsMutex = &sync.Mutex{} - waitLnds(func(service string, status string) { - lndsMutex.Lock() - defer lndsMutex.Unlock() - lnds[service] = status - _, err := f.WriteString(fmt.Sprintf(" [LightSync] lndbtc: %s | lndltc: %s\n", lnds["lndbtc"], lnds["lndltc"])) - if err != nil { - logger.Fatalf("Failed to write to %s: %s", logfile, err) - } - }) - - logger.Debugf("Starting xud") - runCommand(launcher, "up", "--", "-d", "xud") - - logger.Debugf("Ensuring wallets are created and unlocked") - _, err = f.WriteString("Setup wallets\n") - if err != nil { - logger.Fatalf("Failed to write to %s: %s", logfile, err) - } - ensureWallets() - - logger.Debugf("Starting boltz") - runCommand(launcher, "up", "--", "-d", "boltz") - - _, err = f.WriteString("Start shell\n") - if err != nil { - logger.Fatalf("Failed to write to %s: %s", logfile, err) - } - }, -} - -type StatusResponse struct { - Service string `json:"service"` - Status string `json:"status"` -} - -func getServiceStatus(name string) string { - resp, err := http.Get(fmt.Sprintf("https://localhost:8889/api/v1/status/%s", name)) - if err != nil { - logger.Fatal(err) - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - var r StatusResponse - err = json.Unmarshal(body, &r) - if err != nil { - logger.Fatal(err) - } - return r.Status -} - -func waitLnds(onChange func(service string, status string)) { - var wg sync.WaitGroup - wg.Add(2) - - go func() { - waitLnd("lndbtc", func(status string) { - onChange("lndbtc", status) - }) - wg.Done() - }() - - go func() { - waitLnd("lndltc", func(status string) { - onChange("lndltc", status) - }) - wg.Done() - }() - - wg.Wait() -} - -func waitLnd(name string, onChange func(status string)) { - for { - status := getServiceStatus(name) - logger.Debugf("%s: %s", name, status) - onChange(status) - if strings.Contains(status, "100.00%") { - break - } - if strings.Contains(status, "Synced 100%") { - break - } - if strings.Contains(status, "99.99%") { - break - } - if strings.Contains(status, "Ready") { - break - } - time.Sleep(1 * time.Second) - } -} - -func ensureWallets() { - var i = 0 - for ; i < 10; i++ { - status := getServiceStatus("xud") - logger.Debugf("xud: %s", status) - if strings.Contains(status, "Wallet missing") { - logger.Debug("Creating wallets") - err := create(DefaultPassword) - if err != nil { - logger.Fatalf("Failed to create wallets: %s", err) - } - break - } else if strings.Contains(status, "Wallet locked") { - logger.Debug("Unlocking wallets") - err := unlock(DefaultPassword) - if err != nil { - logger.Fatalf("Failed to unlock wallets: %s", err) - } - break - } else if strings.HasPrefix(status, "Error:") { - logger.Debug("Xud is not ready yet") - time.Sleep(3 * time.Second) - } else { - break - } - } - if i >= 10 { - logger.Fatal("It's too long to wait Xud to be ready. There must be something wrong.") - } -} - -func create(password string) error { - var payload = []byte(`{"password":"` + password + `"}`) - resp, err := http.Post("https://localhost:8889/api/v1/xud/create", "application/json", bytes.NewBuffer(payload)) - if err != nil { - logger.Fatalf("Failed to send HTTP request: %s", err) - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - logger.Fatalf("Failed to read HTTP response body: %s", err) - } - if resp.StatusCode == http.StatusOK { - fmt.Printf("%s\n", string(body)) - } else { - var payload Error - err = json.Unmarshal(body, &payload) - if err != nil { - logger.Fatalf("Failed to unmarshal JSON from HTTP response body: %s", err) - } - return errors.New(payload.Message) - } - return nil -} - -func unlock(password string) error { - var payload = []byte(`{"password":"` + password + `"}`) - resp, err := http.Post("https://localhost:8889/api/v1/xud/unlock", "application/json", bytes.NewBuffer(payload)) - if err != nil { - logger.Fatalf("Failed to send HTTP request: %s", err) - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - logger.Fatalf("Failed to read HTTP response body: %s", err) - } - if resp.StatusCode == http.StatusOK { - fmt.Printf("%s\n", string(body)) - } else { - var payload Error - err = json.Unmarshal(body, &payload) - if err != nil { - logger.Fatalf("Failed to unmarshal JSON from HTTP response body: %s", err) - } - return errors.New(payload.Message) - } - return nil -} - -type Error struct { - Message string `json:"message"` -} diff --git a/cmd/version.go b/cmd/version.go deleted file mode 100644 index 52de906..0000000 --- a/cmd/version.go +++ /dev/null @@ -1,24 +0,0 @@ -package cmd - -import ( - "fmt" - "github.com/ExchangeUnion/xud-launcher/build" - "github.com/spf13/cobra" -) - -func init() { - rootCmd.AddCommand(versionCmd) -} - -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Show the launcher version", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println(GetVersion()) - }, -} - -func GetVersion() string { - var version = fmt.Sprintf("%s-%s", build.Version, build.GitCommit[:7]) - return version -} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..d72e6fd --- /dev/null +++ b/config/config.go @@ -0,0 +1,31 @@ +package config + +import ( + "fmt" + "github.com/pelletier/go-toml" + "io/ioutil" +) + +type GitHub struct { + AccessToken string +} + +type Config struct { + GitHub GitHub + SimnetDir string + TestnetDir string + MainnetDir string +} + +func ParseConfig(configFile string) (*Config, error) { + config := Config{} + data, err := ioutil.ReadFile(configFile) + if err != nil { + return nil, fmt.Errorf("read file: %w", err) + } + err = toml.Unmarshal(data, &config) + if err != nil { + return nil, fmt.Errorf("unmarshal: %w", err) + } + return &config, nil +} diff --git a/core/github.go b/core/github.go new file mode 100644 index 0000000..c1aba98 --- /dev/null +++ b/core/github.go @@ -0,0 +1,271 @@ +package core + +import ( + "archive/zip" + "encoding/json" + "errors" + "fmt" + "github.com/sirupsen/logrus" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "regexp" + "runtime" +) + +var ( + ErrNotFound = errors.New("not found") + + ReleaseRef = regexp.MustCompile(`^\d{2}\.\d{2}\.\d{2}.*$`) +) + +type GitHub struct { + Client *http.Client + Logger *logrus.Entry + AccessToken string +} + +func NewGitHub(accessToken string) *GitHub { + return &GitHub{ + Client: http.DefaultClient, + Logger: logrus.NewEntry(logrus.StandardLogger()).WithField("name", "github"), + AccessToken: accessToken, + } +} + +func (t *GitHub) getResponseError(resp *http.Response) error { + var err error + if resp.StatusCode != http.StatusOK { + var result map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + return fmt.Errorf("decode: %w", err) + } + return errors.New(result["message"].(string)) + } + return nil +} + +func (t *GitHub) doGet(url string) ([]byte, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + req.Header.Add("Accept", "application/vnd.github.v3+json") + resp, err := t.Client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if err := t.getResponseError(resp); err != nil { + return nil, err + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return body, nil +} + +func (t *GitHub) GetHeadCommit(branch string) (string, error) { + url := fmt.Sprintf("https://api.github.com/repos/ExchangeUnion/xud-docker/commits/%s", branch) + body, err := t.doGet(url) + if err != nil { + return "", err + } + var result map[string]interface{} + err = json.Unmarshal(body, &result) + if err != nil { + return "", err + } + return result["sha"].(string), nil +} + +type Artifact struct { + Name string `json:"name"` + SizeInBytes uint `json:"size_in_bytes"` + ArchiveDownloadUrl string `json:"archive_download_url"` +} + +type ArtifactList struct { + TotalCount uint `json:"total_count"` + Artifacts []Artifact `json:"artifacts"` +} + +type WorkflowRun struct { + Id uint `json:"id"` + CreatedAt string `json:"created_at"` + HeadBranch string `json:"head_branch"` + HeadSha string `json:"head_sha"` +} + +type WorkflowRunList struct { + TotalCount uint `json:"total_count"` + WorkflowRuns []WorkflowRun `json:"workflow_runs"` +} + +func (t *GitHub) getDownloadUrl(runId uint) (string, error) { + url := fmt.Sprintf("https://api.github.com/repos/ExchangeUnion/xud-docker/actions/runs/%d/artifacts", runId) + body, err := t.doGet(url) + if err != nil { + return "", err + } + var result ArtifactList + err = json.Unmarshal(body, &result) + for _, artifact := range result.Artifacts { + name := fmt.Sprintf("%s-amd64", runtime.GOOS) + if name == artifact.Name { + return artifact.ArchiveDownloadUrl, nil + } + } + return "", ErrNotFound +} + +func (t *GitHub) getLastRunOfBranch(branch string, commit string) (*WorkflowRun, error) { + url := fmt.Sprintf("https://api.github.com/repos/ExchangeUnion/xud-docker/actions/workflows/launcher-build.yml/runs?branch=%s", branch) + body, err := t.doGet(url) + if err != nil { + return nil, err + } + var result WorkflowRunList + err = json.Unmarshal(body, &result) + if len(result.WorkflowRuns) == 0 { + return nil, ErrNotFound + } + run := &result.WorkflowRuns[0] + if run.HeadSha != commit { + return nil, ErrNotFound + } + return run, nil +} + +func (t *GitHub) DownloadLatestBinary(branch string, commit string) error { + var err error + var url string + + if ReleaseRef.Match([]byte(branch)) { + url = fmt.Sprintf("https://github.com/ExchangeUnion/xud-docker/releases/download/%s/launcher-%s-%s.zip", branch, runtime.GOOS, runtime.GOARCH) + } else { + run, err := t.getLastRunOfBranch(branch, commit) + if err != nil { + if errors.Is(err, ErrNotFound) { + return fmt.Errorf("no launcher build for commit %s (The branch \"%s\" does not have a binary launcher)", commit, branch) + } + return fmt.Errorf("get last run of branch: %w", err) + } + + url, err = t.getDownloadUrl(run.Id) + if err != nil { + return fmt.Errorf("get download url: %w", err) + } + t.Logger.Debugf("Download launcher.zip from %s", url) + } + + err = os.Mkdir(commit, 0755) + if err != nil { + return fmt.Errorf("mkdir: %w", err) + } + + err = os.Chdir(commit) + if err != nil { + return fmt.Errorf("change directory: %w", err) + } + + err = t.downloadFile(url, "launcher.zip") + if err != nil { + return fmt.Errorf("download: %w", err) + } + + err = t.unzip("launcher.zip") + if err != nil { + return fmt.Errorf("unzip: %w", err) + } + + return nil +} + +func (t *GitHub) unzip(file string) error { + var filenames []string + + r, err := zip.OpenReader(file) + if err != nil { + return fmt.Errorf("open reader: %w", err) + } + defer r.Close() + + for _, f := range r.File { + t.Logger.Debugf("Extracting %s", f.Name) + + fpath := f.Name + + filenames = append(filenames, fpath) + + if f.FileInfo().IsDir() { + // Make Folder + os.MkdirAll(fpath, os.ModePerm) + continue + } + + // Make File + if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { + return fmt.Errorf("mkdir all: %w", err) + } + + outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return fmt.Errorf("open file: %w", err) + } + + rc, err := f.Open() + if err != nil { + return fmt.Errorf("open: %w", err) + } + + _, err = io.Copy(outFile, rc) + + // Close the file without defer to close before next iteration of loop + _ = outFile.Close() + _ = rc.Close() + + if err != nil { + return fmt.Errorf("copy: %w", err) + } + } + return nil +} + +func (t *GitHub) downloadFile(url string, file string) error { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return fmt.Errorf("new request: %w", err) + } + req.Header.Add("Authorization", "token "+t.AccessToken) + resp, err := t.Client.Do(req) + if err != nil { + return fmt.Errorf("do request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("read all: %w", err) + } + return errors.New(string(body)) + } + + out, err := os.Create(file) + if err != nil { + return fmt.Errorf("create: %w", err) + } + defer out.Close() + + _, err = io.Copy(out, resp.Body) + if err != nil { + return fmt.Errorf("copy: %w", err) + } + + return nil +} diff --git a/core/launcher.go b/core/launcher.go new file mode 100644 index 0000000..c669479 --- /dev/null +++ b/core/launcher.go @@ -0,0 +1,137 @@ +package core + +import ( + "fmt" + "github.com/ExchangeUnion/xud-launcher/config" + "github.com/ExchangeUnion/xud-launcher/logging" + "github.com/sirupsen/logrus" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +type Launcher struct { + logger *logrus.Entry + + homeDir string + runtimeDir string + versionsDir string + + configFile string + + GitHub *GitHub +} + +func NewLauncher(homeDir string) (*Launcher, error) { + configFile := filepath.Join(homeDir, "xud-docker.conf") + cfg, err := config.ParseConfig(configFile) + if err != nil { + return nil, fmt.Errorf("parse config: %w", err) + } + + runtimeDir := filepath.Join(homeDir, "launcher") + versionsDir := filepath.Join(runtimeDir, "versions") + + logrus.StandardLogger().SetLevel(logrus.DebugLevel) + logrus.StandardLogger().SetFormatter(&logging.Formatter{}) + + r := Launcher{ + homeDir: homeDir, + runtimeDir: runtimeDir, + versionsDir: versionsDir, + logger: logrus.NewEntry(logrus.StandardLogger()).WithField("name", "core"), + configFile: configFile, + GitHub: NewGitHub(cfg.GitHub.AccessToken), + } + + if err := r.init(); err != nil { + return nil, err + } + + return &r, nil +} + +func (t *Launcher) init() error { + if _, err := os.Stat(t.runtimeDir); os.IsNotExist(err) { + if err := os.Mkdir(t.runtimeDir, 0755); err != nil { + return fmt.Errorf("mkdir: %w", err) + } + } + err := os.Chdir(t.runtimeDir) + if err != nil { + return fmt.Errorf("chdir: %w", err) + } + + return nil +} + +func (t *Launcher) Run(name string, args ...string) error { + t.logger.Debugf("[run] %s %s", name, strings.Join(args, " ")) + cmd := exec.Command(name, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func (t *Launcher) Start(branch string, network string, networkDir string, args ...string) error { + commit, err := t.GitHub.GetHeadCommit(branch) + if err != nil { + return fmt.Errorf("git head commit: %w", err) + } + + t.logger.Debugf("Start launcher with branch=%s(%s), network=%s, networkDir=%s", branch, commit, network, networkDir) + + if _, err := os.Stat(t.versionsDir); err != nil { + if err := os.Mkdir(t.versionsDir, 0755); err != nil { + return fmt.Errorf("mkdir: %w", err) + } + } + if err := os.Chdir(t.versionsDir); err != nil { + return fmt.Errorf("chdir: %w", err) + } + + if _, err := os.Stat(commit); os.IsNotExist(err) { + if err := t.GitHub.DownloadLatestBinary(branch, commit); err != nil { + return fmt.Errorf("download latest binary: %w", err) + } + } else { + if err := os.Chdir(commit); err != nil { + return fmt.Errorf("chdir: %w", err) + } + } + + if err := os.Setenv("NETWORK", network); err != nil { + return fmt.Errorf("setenv: %w", err) + } + + if err := os.Setenv("NETWORK_DIR", networkDir); err != nil { + return fmt.Errorf("setenv: %w", err) + } + + var launcher string + if runtime.GOOS == "windows" { + launcher = ".\\launcher.exe" + + } else { + launcher = "./launcher" + + // check if binary launcher is executable + info, _ := os.Stat(launcher) + mode := info.Mode() + if mode&0100 == 0 { + err := os.Chmod(launcher, 0755) + if err != nil { + return fmt.Errorf("chmod: %w", err) + } + } + } + + if err := t.Run(launcher, args[1:]...); err != nil { + return fmt.Errorf("run: %w", err) + } + + return nil +} diff --git a/go.mod b/go.mod index 1d7b2ed..7f70c39 100644 --- a/go.mod +++ b/go.mod @@ -5,20 +5,23 @@ go 1.15 require ( github.com/fatih/color v1.10.0 github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/gofrs/flock v0.8.0 // indirect github.com/iancoleman/strcase v0.1.2 - github.com/magiconair/properties v1.8.4 // indirect + github.com/magiconair/properties v1.8.4 github.com/mattn/go-colorable v0.1.8 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.3.3 // indirect - github.com/pelletier/go-toml v1.8.1 // indirect + github.com/pelletier/go-toml v1.8.1 github.com/sirupsen/logrus v1.7.0 github.com/spf13/afero v1.4.1 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cobra v1.1.1 github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/viper v1.7.1 - golang.org/x/sys v0.0.0-20201126144705-a4b67b81d3d2 // indirect + github.com/stretchr/testify v1.6.1 + golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // indirect golang.org/x/text v0.3.4 // indirect gopkg.in/ini.v1 v1.62.0 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect ) diff --git a/go.sum b/go.sum index 69cd3a6..9c4b483 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= +github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -208,6 +210,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -286,6 +290,8 @@ golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPj golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126144705-a4b67b81d3d2 h1:WFCmm2Hi9I2gYf1kv7LQ8ajKA5x9heC2v9xuUKwvf68= golang.org/x/sys v0.0.0-20201126144705-a4b67b81d3d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo= +golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -352,6 +358,11 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/logging/formatter.go b/logging/formatter.go new file mode 100644 index 0000000..b906682 --- /dev/null +++ b/logging/formatter.go @@ -0,0 +1,39 @@ +package logging + +import ( + "bytes" + "fmt" + "github.com/sirupsen/logrus" + "strings" +) + +type Formatter struct { +} + +func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) { + data := make(logrus.Fields) + for k, v := range entry.Data { + data[k] = v + } + + var b *bytes.Buffer + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} + } + + name := data["name"] + if name == nil { + name = "" + } + + b.WriteString(fmt.Sprintf("%s [%-5s] %-24s: %s\n", + entry.Time.Format("2006-01-02 15:04:05.000"), + strings.ToUpper(entry.Level.String()), + name, + entry.Message, + )) + + return b.Bytes(), nil +} diff --git a/main.go b/main.go index e450c94..bde98d2 100644 --- a/main.go +++ b/main.go @@ -1,13 +1,65 @@ package main import ( - "github.com/ExchangeUnion/xud-launcher/cmd" - "log" + "fmt" + "github.com/ExchangeUnion/xud-launcher/core" + "github.com/mitchellh/go-homedir" + "os" + "path/filepath" + "runtime" ) func main() { - err := cmd.Execute() + homeDir, err := GetHomeDir() if err != nil { - log.Fatal(err) + fmt.Println(err) + os.Exit(1) } + + r, err := core.NewLauncher(homeDir) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + network := GetNetwork() + networkDir := filepath.Join(homeDir, network) + branch := GetBranch() + + err = r.Start(branch, network, networkDir, os.Args...) + if err != nil { + fmt.Printf("ERROR: %s\n", err) + os.Exit(1) + } +} + +func GetHomeDir() (string, error) { + homeDir, err := homedir.Dir() + if err != nil { + panic(err) + } + switch runtime.GOOS { + case "linux": + return filepath.Join(homeDir, ".xud-docker"), nil + case "darwin": + return filepath.Join(homeDir, "Library", "Application Support", "XudDocker"), nil + case "windows": + return filepath.Join(homeDir, "AppData", "Local", "XudDocker"), nil + default: + return "", fmt.Errorf("unsupported platform: %s", runtime.GOOS) + } +} + +func GetNetwork() string { + if value, ok := os.LookupEnv("NETWORK"); ok { + return value + } + return "mainnet" +} + +func GetBranch() string { + if value, ok := os.LookupEnv("BRANCH"); ok { + return value + } + return "master" } diff --git a/service/arby.go b/service/arby.go deleted file mode 100644 index db82c1c..0000000 --- a/service/arby.go +++ /dev/null @@ -1,124 +0,0 @@ -package service - -import ( - "errors" - "fmt" - "github.com/spf13/cobra" -) - -type ArbyConfig struct { - LiveCex bool `usage:"Live CEX (deprecated)"` - TestMode bool `usage:"Whether to issue real orders on the centralized exchange"` - BaseAsset string `usage:"Base asset"` - QuoteAsset string `usage:"Quote asset"` - CexBaseAsset string `usage:"Centralized exchange base asset"` - CexQuoteAsset string `usage:"Centralized exchange quote asset"` - TestCentralizedBaseassetBalance string `usage:"Test centralized base asset balance"` - TestCentralizedQuoteassetBalance string `usage:"Test centralized quote asset balance"` - Cex string `usage:"Centralized Exchange"` - CexApiKey string `usage:"CEX API key"` - CexApiSecret string `usage:"CEX API secret"` - Margin string `usage:"Trade margin"` -} - -type Arby struct { - Base - - Config ArbyConfig -} - -func newArby(name string) Arby { - return Arby{ - Base: newBase(name), - } -} - -func (t *Arby) ConfigureFlags(cmd *cobra.Command, network string) error { - if err := t.Base.ConfigureFlags(cmd, network, &BaseConfig{ - Disabled: true, - ExposePorts: []string{}, - Dir: fmt.Sprintf("./data/%s", t.Name), - Image: images[network][t.Name], - }); err != nil { - return err - } - - if err := ReflectFlags(t.Name, &t.Config, &ArbyConfig{ - LiveCex: true, - TestMode: true, - BaseAsset: "", - QuoteAsset: "", - CexBaseAsset: "", - CexQuoteAsset: "", - TestCentralizedBaseassetBalance: "", - TestCentralizedQuoteassetBalance: "", - Cex: "binance", - CexApiKey: "123", - CexApiSecret: "abc", - Margin: "0.04", - }, cmd); err != nil { - return err - } - - if err := cmd.PersistentFlags().MarkDeprecated("arby.live-cex", "Please use --arby.test-mode instead"); err != nil { - return err - } - - return nil -} - -func (t *Arby) GetConfig() interface{} { - return t.Config -} - -func (t *Arby) Apply(config *SharedConfig, services map[string]Service) error { - ReflectFillConfig(t.Name, &t.Config) - - network := config.Network - - // validation - if network != "simnet" && network != "testnet" && network != "mainnet" { - return errors.New("invalid network: " + network) - } - - // base apply - err := t.Base.Apply("/root/.arby", config.Network) - if err != nil { - return err - } - - // arby apply - var rpcPort string - - t.Environment["NETWORK"] = network - - if network == "simnet" { - rpcPort = "28886" - } else if network == "testnet" { - rpcPort = "18886" - } else if network == "mainnet" { - rpcPort = "8886" - } else { - return errors.New("invalid network: " + network) - } - - t.Environment["NODE_ENV"] = "production" - t.Environment["LOG_LEVEL"] = "trace" - t.Environment["DATA_DIR"] = "/root/.arby" - t.Environment["OPENDEX_CERT_PATH"] = "/root/.xud/tls.cert" - t.Environment["OPENDEX_RPC_HOST"] = "xud" - t.Environment["BASEASSET"] = t.Config.BaseAsset - t.Environment["QUOTEASSET"] = t.Config.QuoteAsset - t.Environment["CEX_BASEASSET"] = t.Config.CexBaseAsset - t.Environment["CEX_QUOTEASSET"] = t.Config.CexQuoteAsset - t.Environment["OPENDEX_RPC_PORT"] = rpcPort - t.Environment["CEX"] = t.Config.Cex - t.Environment["CEX_API_SECRET"] = t.Config.CexApiSecret - t.Environment["CEX_API_KEY"] = t.Config.CexApiKey - t.Environment["TEST_MODE"] = fmt.Sprint(t.Config.TestMode) - t.Environment["MARGIN"] = t.Config.Margin - t.Environment["TEST_CENTRALIZED_EXCHANGE_BASEASSET_BALANCE"] = t.Config.TestCentralizedBaseassetBalance - t.Environment["TEST_CENTRALIZED_EXCHANGE_QUOTEASSET_BALANCE"] = t.Config.TestCentralizedQuoteassetBalance - - return nil -} diff --git a/service/base.go b/service/base.go deleted file mode 100644 index 53319aa..0000000 --- a/service/base.go +++ /dev/null @@ -1,256 +0,0 @@ -package service - -import ( - "errors" - "fmt" - "github.com/iancoleman/strcase" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "reflect" -) - -var ( - logger = logrus.New() -) - -const ( - PROXY_DATA_DIR = "/root/network/data" -) - -func init() { - logger.SetLevel(logrus.DebugLevel) -} - -type BaseConfig struct { - Disabled bool `usage:"Enable/Disable service"` - ExposePorts []string `usage:"Expose service ports to your host machine"` - Dir string `usage:"Specify the main data directory of service"` - Image string `usage:"Specify the image of service"` -} - -type SharedConfig struct { - Network string - HomeDir string - NetworkDir string - ExternalIp string - Dev bool - UseLocalImages string -} - -type Base struct { - Name string - Image string - Environment map[string]string - Command []string - Ports []string - Volumes []string - Hostname string - - Disabled bool - Network string - - Config BaseConfig -} - -func newBase(name string) Base { - return Base{ - Name: name, - Image: "", - Environment: make(map[string]string), - Command: []string{}, - Ports: []string{}, - Volumes: []string{}, - Hostname: "", - } -} - -func (t *Base) ConfigureFlags(cmd *cobra.Command, network string, defaultValues *BaseConfig) error { - if err := ReflectFlags(t.Name, &t.Config, defaultValues, cmd); err != nil { - return err - } - return nil -} - -func (t *Base) Apply(dir string, network string) error { - - ReflectFillConfig(t.Name, &t.Config) - - for _, port := range t.Config.ExposePorts { - t.Ports = append(t.Ports, port) - } - - t.Volumes = append(t.Volumes, fmt.Sprintf("%s:%s", t.Config.Dir, dir)) - - t.Image = t.Config.Image - - t.Disabled = t.Config.Disabled - - t.Network = network - - t.Environment["NETWORK"] = network - - return nil -} - -func (t *Base) GetName() string { - return t.Name -} - -func (t *Base) GetImage() string { - return t.Image -} - -func (t *Base) GetCommand() []string { - return t.Command -} - -func (t *Base) GetEnvironment() map[string]string { - return t.Environment -} - -func (t *Base) GetVolumes() []string { - return t.Volumes -} - -func (t *Base) GetPorts() []string { - return t.Ports -} - -func (t *Base) GetHostname() string { - return t.Hostname -} - -func (t *Base) IsDisabled() bool { - return t.Disabled -} - -func (t *Base) ToJson() map[string]interface{} { - var result = make(map[string]interface{}) - result["name"] = t.Name - result["disabled"] = t.Disabled - result["rpc"] = make(map[string]interface{}) - return result -} - -func NewService(name string) Service { - switch name { - case "bitcoind": - s := newBitcoind("bitcoind") - return &s - case "litecoind": - s := newLitecoind("litecoind") - return &s - case "geth": - s := newGeth("geth") - return &s - case "lndbtc": - s := newLnd("lndbtc", "bitcoin") - return &s - case "lndltc": - s := newLnd("lndltc", "litecoin") - return &s - case "connext": - s := newConnext("connext") - return &s - case "xud": - s := newXud("xud") - return &s - case "arby": - s := newArby("arby") - return &s - case "boltz": - s := newBoltz("boltz") - return &s - case "webui": - s := newWebui("webui") - return &s - case "proxy": - s := newProxy("proxy") - return &s - } - - return nil -} - -func getDefaultValue(dv reflect.Value, fieldName string) interface{} { - f := dv.FieldByName(fieldName) - return f.Interface() -} - -func ReflectFlags(name string, config interface{}, defaultValues interface{}, cmd *cobra.Command) error { - v := reflect.ValueOf(config).Elem() - t := v.Type() - dv := reflect.ValueOf(defaultValues).Elem() - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - - fn := field.Name - usage := field.Tag.Get("usage") - ft := field.Type - - key := fmt.Sprintf("%s.%s", name, strcase.ToKebab(fn)) - - //p := v.FieldByName(fn).Addr().Interface() - - value := getDefaultValue(dv, fn) - - switch ft.Kind() { - case reflect.String: - //cmd.PersistentFlags().StringVar(p.(*string), key, value.(string), usage) - cmd.PersistentFlags().String(key, value.(string), usage) - case reflect.Bool: - //cmd.PersistentFlags().BoolVar(p.(*bool), key, value.(bool), usage) - cmd.PersistentFlags().Bool(key, value.(bool), usage) - case reflect.Uint16: - //cmd.PersistentFlags().Uint16Var(p.(*uint16), key, value.(uint16), usage) - cmd.PersistentFlags().Uint16(key, value.(uint16), usage) - case reflect.Slice: - // FIXME differentiate slice item type - //cmd.PersistentFlags().StringSliceVar(p.(*[]string), key, value.([]string), usage) - cmd.PersistentFlags().StringSlice(key, value.([]string), usage) - default: - return errors.New("unsupported config struct field type: " + ft.Kind().String()) - } - if err := viper.BindPFlag(key, cmd.PersistentFlags().Lookup(key)); err != nil { - return err - } - } - return nil -} - -func ReflectFillConfig(name string, config interface{}) { - v := reflect.ValueOf(config).Elem() - t := v.Type() - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - fn := field.Name - ft := field.Type - key := fmt.Sprintf("%s.%s", name, strcase.ToKebab(fn)) - //flag := cmd.PersistentFlags().Lookup(key) - p := v.FieldByName(fn).Addr().Interface() - - switch ft.Kind() { - case reflect.String: - //if ! flag.Changed { - // *p.(*string) = viper.GetString(key) - //} - *p.(*string) = viper.GetString(key) - case reflect.Bool: - //if ! flag.Changed { - // *p.(*bool) = viper.GetBool(key) - //} - *p.(*bool) = viper.GetBool(key) - case reflect.Uint16: - //if ! flag.Changed { - // *p.(*uint16) = uint16(viper.GetUint(key)) - //} - *p.(*uint16) = uint16(viper.GetUint(key)) - case reflect.Slice: - //if ! flag.Changed { - // *p.(*[]string) = viper.GetStringSlice(key) - //} - *p.(*[]string) = viper.GetStringSlice(key) - } - } -} diff --git a/service/bitcoind.go b/service/bitcoind.go deleted file mode 100644 index 46f6c0a..0000000 --- a/service/bitcoind.go +++ /dev/null @@ -1,123 +0,0 @@ -package service - -import ( - "errors" - "fmt" - "github.com/spf13/cobra" -) - -type BitcoindConfig struct { - Mode string `usage:"Bitcoind service mode"` - Rpchost string `usage:"External bitcoind RPC hostname"` - Rpcport uint16 `usage:"External bitcoind RPC port"` - Rpcuser string `usage:"External bitcoind RPC username"` - Rpcpass string `usage:"External bitcoind RPC password"` - Zmqpubrawblock string `usage:"External bitcoind ZeroMQ raw blocks publication address"` - Zmqpubrawtx string `usage:"External bitcoind ZeroMQ raw transactions publication address"` -} - -type Bitcoind struct { - Base - - Config BitcoindConfig -} - -func newBitcoind(name string) Bitcoind { - return Bitcoind{ - Base: newBase(name), - } -} - -func (t *Bitcoind) ConfigureFlags(cmd *cobra.Command, network string) error { - if err := t.Base.ConfigureFlags(cmd, network, &BaseConfig{ - Disabled: true, - ExposePorts: []string{}, - Dir: fmt.Sprintf("./data/%s", t.Name), - Image: images[network][t.Name], - }); err != nil { - return err - } - - if err := ReflectFlags(t.Name, &t.Config, &BitcoindConfig{ - Mode: "light", - Rpchost: "", - Rpcport: 0, - Rpcuser: "", - Rpcpass: "", - Zmqpubrawblock: "", - Zmqpubrawtx: "", - }, cmd); err != nil { - return err - } - - return nil -} - -func (t *Bitcoind) GetConfig() interface{} { - return t.Config -} - -func (t *Bitcoind) Apply(config *SharedConfig, services map[string]Service) error { - ReflectFillConfig(t.Name, &t.Config) - - network := config.Network - - // validation - if network != "testnet" && network != "mainnet" { - return errors.New("invalid network: " + network) - } - - // base apply - - err := t.Base.Apply("/root/.bitcoind", config.Network) - if err != nil { - return err - } - - // bitcoind apply - t.Environment["NETWORK"] = network - - t.Command = append(t.Command, - "-server", - "-rpcuser=xu", - "-rpcpassword=xu", - "-disablewallet", - "-txindex", - "-zmqpubrawblock=tcp://0.0.0.0:28332", - "-zmqpubrawtx=tcp://0.0.0.0:28333", - "-logips", - "-rpcallowip=::/0", - "-rpcbind=0.0.0.0", - ) - - if network == "testnet" { - t.Command = append(t.Command, "-rpcport=18332", "-testnet") - } else { // mainnet - t.Command = append(t.Command, "-rpcport=8332") - } - - if t.Config.Mode != "native" || network == "simnet" { - t.Disabled = true - } - - return nil -} - -func (t *Bitcoind) ToJson() map[string]interface{} { - result := t.Base.ToJson() - result["mode"] = t.Config.Mode - - rpc := make(map[string]interface{}) - result["rpc"] = rpc - rpc["type"] = "JSON-RPC" - rpc["host"] = "bitcoind" - if t.Network == "testnet" { - rpc["port"] = 18332 - } else { - rpc["port"] = 8332 - } - rpc["username"] = "xu" - rpc["password"] = "xu" - - return result -} diff --git a/service/boltz.go b/service/boltz.go deleted file mode 100644 index 21f1332..0000000 --- a/service/boltz.go +++ /dev/null @@ -1,94 +0,0 @@ -package service - -import ( - "errors" - "fmt" - "github.com/spf13/cobra" -) - -type BoltzConfig struct { - // add more boltz specified attributes here -} - -type Boltz struct { - Base - - Config BoltzConfig -} - -func newBoltz(name string) Boltz { - return Boltz{ - Base: newBase(name), - } -} - -func (t *Boltz) ConfigureFlags(cmd *cobra.Command, network string) error { - if err := t.Base.ConfigureFlags(cmd, network, &BaseConfig{ - Disabled: network == "simnet", - ExposePorts: []string{}, - Dir: fmt.Sprintf("./data/%s", t.Name), - Image: images[network][t.Name], - }); err != nil { - return err - } - - // configure boltz specified flags here - - return nil -} - -func (t *Boltz) GetConfig() interface{} { - return t.Config -} - -func (t *Boltz) Apply(config *SharedConfig, services map[string]Service) error { - ReflectFillConfig(t.Name, &t.Config) - - network := config.Network - - // validation - if network != "simnet" && network != "testnet" && network != "mainnet" { - return errors.New("invalid network: " + network) - } - - // base apply - err := t.Base.Apply("/root/.boltz", config.Network) - if err != nil { - return err - } - - // boltz apply - - t.Volumes = append(t.Volumes, - "./data/lndbtc:/root/.lndbtc", - "./data/lndltc:/root/.lndltc", - ) - - return nil -} - -func (t *Boltz) ToJson() map[string]interface{} { - result := t.Base.ToJson() - - rpc := make(map[string]interface{}) - result["rpc"] = rpc - - bitcoin := make(map[string]interface{}) - bitcoin["type"] = "gRPC" - bitcoin["host"] = "boltz" - bitcoin["port"] = 9002 - bitcoin["tlsCert"] = fmt.Sprintf("%s/%s/bitcoin/tls.cert", PROXY_DATA_DIR, t.GetName()) - bitcoin["macaroon"] = fmt.Sprintf("%s/%s/bitcoin/admin.macaroon", PROXY_DATA_DIR, t.GetName()) - - litecoin := make(map[string]interface{}) - litecoin["type"] = "gRPC" - litecoin["host"] = "boltz" - litecoin["port"] = 9102 - litecoin["tlsCert"] = fmt.Sprintf("%s/%s/litecoin/tls.cert", PROXY_DATA_DIR, t.GetName()) - litecoin["macaroon"] = fmt.Sprintf("%s/%s/litecoin/admin.macaroon", PROXY_DATA_DIR, t.GetName()) - - rpc["bitcoin"] = bitcoin - rpc["litecoin"] = litecoin - - return result -} diff --git a/service/config.go b/service/config.go deleted file mode 100644 index 81814be..0000000 --- a/service/config.go +++ /dev/null @@ -1,54 +0,0 @@ -package service - -var ( - images = map[string]map[string]string{ - "simnet": { - "lndbtc": "exchangeunion/lndbtc-simnet:latest", - "lndltc": "exchangeunion/lndltc-simnet:latest", - "connext": "connextproject/vector_node:837bafa1", - "arby": "exchangeunion/arby:latest", - "webui": "exchangeunion/webui:latest", - "proxy": "exchangeunion/proxy:latest", - "xud": "exchangeunion/xud:latest", - }, - "testnet": { - "bitcoind": "exchangeunion/bitcoind:latest", - "litecoind": "exchangeunion/litecoind:latest", - "geth": "exchangeunion/geth:latest", - "lndbtc": "exchangeunion/lndbtc:latest", - "lndltc": "exchangeunion/lndltc:latest", - "connext": "exchangeunion/connext:latest", - "arby": "exchangeunion/arby:latest", - "boltz": "exchangeunion/boltz:latest", - "webui": "exchangeunion/webui:latest", - "proxy": "exchangeunion/proxy:latest", - "xud": "exchangeunion/xud:latest", - }, - "mainnet": { - "bitcoind": "exchangeunion/bitcoind:0.20.1", - "litecoind": "exchangeunion/litecoind:0.18.1", - "geth": "exchangeunion/geth:1.9.24", - "lndbtc": "exchangeunion/lndbtc:0.11.1-beta", - "lndltc": "exchangeunion/lndltc:0.11.0-beta.rc1", - "connext": "exchangeunion/connext:1.3.6", - "arby": "exchangeunion/arby:1.3.0", - "boltz": "exchangeunion/boltz:1.2.0", - "webui": "exchangeunion/webui:1.0.0", - "proxy": "exchangeunion/proxy:1.2.0", - "xud": "exchangeunion/xud:1.2.4", - }, - } - - ethProviders = map[string][]string{ - "testnet": { - "http://eth.kilrau.com:52041", - "http://michael1011.at:8546", - "http://gethxudxv2k4pv5t5a5lswq2hcv3icmj3uwg7m2n2vuykiyv77legiad.onion:8546", - }, - "mainnet": { - "http://eth.kilrau.com:41007", - "http://michael1011.at:8545", - "http://gethxudxv2k4pv5t5a5lswq2hcv3icmj3uwg7m2n2vuykiyv77legiad.onion:8545", - }, - } -) diff --git a/service/connext.go b/service/connext.go deleted file mode 100644 index f08d99f..0000000 --- a/service/connext.go +++ /dev/null @@ -1,172 +0,0 @@ -package service - -import ( - "bytes" - "errors" - "fmt" - "github.com/spf13/cobra" - "io/ioutil" - "net/http" - "strings" -) - -type ConnextConfig struct { - // add more connext specified attributes here -} - -type Connext struct { - Base - - Config ConnextConfig -} - -func newConnext(name string) Connext { - return Connext{ - Base: newBase(name), - } -} - -func (t *Connext) ConfigureFlags(cmd *cobra.Command, network string) error { - if err := t.Base.ConfigureFlags(cmd, network, &BaseConfig{ - Disabled: false, - ExposePorts: []string{}, - Dir: fmt.Sprintf("./data/%s", t.Name), - Image: images[network][t.Name], - }); err != nil { - return err - } - - // configure connext specified flags here - - return nil -} - -func (t *Connext) GetConfig() interface{} { - return t.Config -} - -func (t *Connext) Apply(config *SharedConfig, services map[string]Service) error { - ReflectFillConfig(t.Name, &t.Config) - - network := config.Network - - // validation - if network != "simnet" && network != "testnet" && network != "mainnet" { - return errors.New("invalid network: " + network) - } - - // base apply - err := t.Base.Apply("/app/connext-store", config.Network) - if err != nil { - return err - } - - t.Environment["NETWORK"] = network - - // connext apply - if strings.Contains(t.Image, "vector_node") { - t.Environment["VECTOR_CONFIG"] = `{ - "adminToken": "ddrWR8TK8UMTyR", - "chainAddresses": { - "1337": { - "channelFactoryAddress": "0x2eC39861B9Be41c20675a1b727983E3F3151C576", - "channelMastercopyAddress": "0x7AcAcA3BC812Bcc0185Fa63faF7fE06504D7Fa70", - "transferRegistryAddress": "0xB2b8A1d98bdD5e7A94B3798A13A94dEFFB1Fe709", - "TestToken": "" - } - }, - "chainProviders": { - "1337": "http://35.234.110.95:8545" - }, - "domainName": "", - "logLevel": "debug", - "messagingUrl": "https://messaging.connext.network", - "production": true, - "mnemonic": "crazy angry east hood fiber awake leg knife entire excite output scheme" -}` - t.Environment["VECTOR_SQLITE_FILE"] = "/database/store.db" - t.Environment["VECTOR_PROD"] = "true" - } else { - t.Environment["LEGACY_MODE"] = "true" - switch network { - case "simnet": - t.Environment["CONNEXT_ETH_PROVIDER_URL"] = "http://connext.simnet.exchangeunion.com:8545" - t.Environment["CONNEXT_NODE_URL"] = "https://connext.simnet.exchangeunion.com" - case "testnet": - t.Environment["CONNEXT_NODE_URL"] = "https://connext.testnet.exchangeunion.com" - case "mainnet": - t.Environment["CONNEXT_NODE_URL"] = "https://connext.boltz.exchange" - } - - gethConfig := services["geth"].GetConfig().(GethConfig) - - mode := gethConfig.Mode - switch mode { - case "external": - rpcHost := gethConfig.Rpchost - rpcPort := gethConfig.Rpcport - t.Environment["CONNEXT_ETH_PROVIDER_URL"] = fmt.Sprintf("http://%s:%d", rpcHost, rpcPort) - case "infura": - projId := gethConfig.InfuraProjectId - switch network { - case "mainnet": - t.Environment["CONNEXT_ETH_PROVIDER_URL"] = fmt.Sprintf("https://mainnet.infura.io/v3/%s", projId) - case "testnet": - t.Environment["CONNEXT_ETH_PROVIDER_URL"] = fmt.Sprintf("https://rinkeby.infura.io/v3/%s", projId) - case "simnet": - return errors.New("no Infura Ethereum provider for simnet") - } - case "light": - t.Environment["CONNEXT_ETH_PROVIDER_URL"] = selectFastestProvider(ethProviders[network]) - case "native": - t.Environment["CONNEXT_ETH_PROVIDER_URL"] = "http://geth:8545" - } - } - - return nil -} - -func checkProvider(url string) error { - var payload = []byte(`{"jsonrpc":"2.0","method":"net_version","params":[],"id":1}`) - req, err := http.NewRequest("POST", url, bytes.NewReader(payload)) - if err != nil { - return err - } - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return err - } - defer func() { - err := resp.Body.Close() - if err != nil { - logger.Fatal(err) - } - }() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - fmt.Println(string(body)) - - return nil -} - -func selectFastestProvider(providers []string) string { - return providers[0] -} - -func (t *Connext) ToJson() map[string]interface{} { - result := t.Base.ToJson() - - rpc := make(map[string]interface{}) - result["rpc"] = rpc - rpc["type"] = "HTTP" - rpc["host"] = "connext" - rpc["port"] = 5040 - - return result -} diff --git a/service/geth.go b/service/geth.go deleted file mode 100644 index 4088966..0000000 --- a/service/geth.go +++ /dev/null @@ -1,109 +0,0 @@ -package service - -import ( - "errors" - "fmt" - "github.com/spf13/cobra" -) - -type GethConfig struct { - Mode string `usage:"Geth service mode"` - Rpchost string `usage:"External geth RPC hostname"` - Rpcport uint16 `usage:"External geth RPC port"` - InfuraProjectId string `usage:"Infura geth provider project ID"` - InfuraProjectSecret string `usage:"Infura geth provider project secret"` - Cache string `usage:"Geth cache size"` - AncientChaindataDir string `usage:"Specify the container's volume mapping ancient chaindata directory. Can be located on a slower HDD."` -} - -type Geth struct { - Base - - Config GethConfig -} - -func newGeth(name string) Geth { - return Geth{ - Base: newBase(name), - } -} - -func (t *Geth) ConfigureFlags(cmd *cobra.Command, network string) error { - if err := t.Base.ConfigureFlags(cmd, network, &BaseConfig{ - Disabled: true, - ExposePorts: []string{}, - Dir: fmt.Sprintf("./data/%s", t.Name), - Image: images[network][t.Name], - }); err != nil { - return err - } - - if err := ReflectFlags(t.Name, &t.Config, &GethConfig{ - Mode: "light", - Rpchost: "", - Rpcport: 0, - InfuraProjectId: "", - InfuraProjectSecret: "", - Cache: "", - AncientChaindataDir: "", - }, cmd); err != nil { - return err - } - - return nil -} - -func (t *Geth) GetConfig() interface{} { - return t.Config -} - -func (t *Geth) Apply(config *SharedConfig, services map[string]Service) error { - ReflectFillConfig(t.Name, &t.Config) - - network := config.Network - - // validation - if network != "testnet" && network != "mainnet" { - return errors.New("invalid network: " + network) - } - - // base apply - err := t.Base.Apply("/root/.ethereum", config.Network) - if err != nil { - return err - } - - // geth apply - t.Environment["NETWORK"] = network - - if t.Config.AncientChaindataDir != "" { - volume := fmt.Sprintf("%s:/root/.ethereum-ancient-chaindata", t.Config.AncientChaindataDir) - t.Volumes = append(t.Volumes, volume) - t.Environment["CUSTOM_ANCIENT_CHAINDATA"] = "true" - } - - if t.Config.Cache != "" { - t.Command = append(t.Command, fmt.Sprintf("--cache %s", t.Config.Cache)) - } - - // TODO select ethProvider in light mode - - if t.Config.Mode != "native" || network == "simnet" { - t.Disabled = true - } - - return nil -} - -func (t *Geth) ToJson() map[string]interface{} { - result := t.Base.ToJson() - result["mode"] = t.Config.Mode - - rpc := make(map[string]interface{}) - result["rpc"] = rpc - rpc["type"] = "JSON-RPC" - rpc["host"] = "geth" - rpc["port"] = 8545 - - return result -} diff --git a/service/interface.go b/service/interface.go deleted file mode 100644 index 3d1975a..0000000 --- a/service/interface.go +++ /dev/null @@ -1,20 +0,0 @@ -package service - -import "github.com/spf13/cobra" - -type Service interface { - ConfigureFlags(cmd *cobra.Command, network string) error - GetConfig() interface{} - GetName() string - Apply(config *SharedConfig, services map[string]Service) error - - GetImage() string - GetCommand() []string - GetEnvironment() map[string]string - GetVolumes() []string - GetPorts() []string - GetHostname() string - IsDisabled() bool - - ToJson() map[string]interface{} -} diff --git a/service/litecoind.go b/service/litecoind.go deleted file mode 100644 index 1e5ea6e..0000000 --- a/service/litecoind.go +++ /dev/null @@ -1,103 +0,0 @@ -package service - -import ( - "errors" - "fmt" - "github.com/spf13/cobra" -) - -type LitecoindConfig struct { - Mode string `usage:"Litecoind service mode"` - Rpchost string `usage:"External litecoind RPC hostname"` - Rpcport uint16 `usage:"External litecoind RPC port"` - Rpcuser string `usage:"External litecoind RPC username"` - Rpcpass string `usage:"External litecoind RPC password"` - Zmqpubrawblock string `usage:"External litecoind ZeroMQ raw blocks publication address"` - Zmqpubrawtx string `usage:"External litecoind ZeroMQ raw transactions publication address"` -} - -type Litecoind struct { - Base - - Config LitecoindConfig -} - -func newLitecoind(name string) Litecoind { - return Litecoind{ - Base: newBase(name), - } -} - -func (t *Litecoind) ConfigureFlags(cmd *cobra.Command, network string) error { - if err := t.Base.ConfigureFlags(cmd, network, &BaseConfig{ - Disabled: true, - ExposePorts: []string{}, - Dir: fmt.Sprintf("./data/%s", t.Name), - Image: images[network][t.Name], - }); err != nil { - return err - } - - if err := ReflectFlags(t.Name, &t.Config, &LitecoindConfig{ - Mode: "light", - Rpchost: "", - Rpcport: 0, - Rpcuser: "", - Rpcpass: "", - Zmqpubrawblock: "", - Zmqpubrawtx: "", - }, cmd); err != nil { - return err - } - - return nil -} - -func (t *Litecoind) GetConfig() interface{} { - return t.Config -} - -func (t *Litecoind) Apply(config *SharedConfig, services map[string]Service) error { - ReflectFillConfig(t.Name, &t.Config) - - network := config.Network - - // validation - if network != "testnet" && network != "mainnet" { - return errors.New("invalid network: " + network) - } - - // base apply - err := t.Base.Apply("/root/.litecoind", config.Network) - if err != nil { - return err - } - - // litecoind apply - t.Environment["NETWORK"] = network - - if t.Config.Mode != "native" || network == "simnet" { - t.Disabled = true - } - - return nil -} - -func (t *Litecoind) ToJson() map[string]interface{} { - result := t.Base.ToJson() - result["mode"] = t.Config.Mode - - rpc := make(map[string]interface{}) - result["rpc"] = rpc - rpc["type"] = "JSON-RPC" - rpc["host"] = "litecoind" - if t.Network == "testnet" { - rpc["port"] = 19332 - } else { - rpc["port"] = 9332 - } - rpc["username"] = "xu" - rpc["password"] = "xu" - - return result -} diff --git a/service/lnd.go b/service/lnd.go deleted file mode 100644 index 6a1e52f..0000000 --- a/service/lnd.go +++ /dev/null @@ -1,186 +0,0 @@ -package service - -import ( - "errors" - "fmt" - "github.com/spf13/cobra" -) - -// TODO support variable replacement in usage tag -type LndConfig struct { - Mode string `usage:"Lnd service mode"` - PreserveConfig string `usage:"Preserve lnd.conf file during updates"` -} - -type Lnd struct { - Base - - Config LndConfig - Chain string -} - -func newLnd(name string, chain string) Lnd { - return Lnd{ - Base: newBase(name), - Chain: chain, - } -} - -func (t *Lnd) ConfigureFlags(cmd *cobra.Command, network string) error { - if err := t.Base.ConfigureFlags(cmd, network, &BaseConfig{ - Disabled: false, - ExposePorts: []string{}, - Dir: fmt.Sprintf("./data/%s", t.Name), - Image: images[network][t.Name], - }); err != nil { - return err - } - - if err := ReflectFlags(t.Name, &t.Config, &LndConfig{ - Mode: "native", - PreserveConfig: "false", - }, cmd); err != nil { - return err - } - - return nil -} - -func (t *Lnd) GetConfig() interface{} { - return t.Config -} - -func (t *Lnd) Apply(config *SharedConfig, services map[string]Service) error { - ReflectFillConfig(t.Name, &t.Config) - - network := config.Network - - // validation - if network != "simnet" && network != "testnet" && network != "mainnet" { - return errors.New("invalid network: " + network) - } - t.Network = network - - // base apply - err := t.Base.Apply("/root/.lnd", config.Network) - if err != nil { - return err - } - - // lnd apply - t.Environment["NETWORK"] = network - t.Environment["CHAIN"] = t.Chain - - if t.Config.PreserveConfig == "true" { - t.Environment["PRESERVE_CONFIG"] = "true" - } else { - t.Environment["PRESERVE_CONFIG"] = "false" - } - - if config.ExternalIp != "" { - t.Environment["EXTERNAL_IP"] = config.ExternalIp - } - - if network == "testnet" || network == "mainnet" { - var mode string - var rpchost string - var rpcport uint16 - var rpcuser string - var rpcpass string - var zmqpubrawblock string - var zmqpubrawtx string - - if t.Chain == "bitcoin" { - backend := services["bitcoind"].GetConfig().(BitcoindConfig) - mode = backend.Mode - rpchost = backend.Rpchost - rpcport = backend.Rpcport - rpcuser = backend.Rpcuser - rpcpass = backend.Rpcpass - zmqpubrawblock = backend.Zmqpubrawblock - zmqpubrawtx = backend.Zmqpubrawtx - } else { - backend := services["litecoind"].GetConfig().(LitecoindConfig) - mode = backend.Mode - rpchost = backend.Rpchost - rpcport = backend.Rpcport - rpcuser = backend.Rpcuser - rpcpass = backend.Rpcpass - zmqpubrawblock = backend.Zmqpubrawblock - zmqpubrawtx = backend.Zmqpubrawtx - } - - if mode == "neutrino" || mode == "light" { - t.Environment["NEUTRINO"] = "True" - } else if mode == "external" { - t.Environment["RPCHOST"] = rpchost - t.Environment["RPCPORT"] = fmt.Sprint(rpcport) - t.Environment["RPCUSER"] = rpcuser - t.Environment["RPCPASS"] = rpcpass - t.Environment["ZMQPUBRAWBLOCK"] = zmqpubrawblock - t.Environment["ZMQPUBRAWTX"] = zmqpubrawtx - } - } - - if network == "simnet" { - if t.Chain == "bitcoin" { - t.Command = append(t.Command, - "--debuglevel=debug", - "--nobootstrap", - "--minbackoff=30s", - "--maxbackoff=24h", - "--bitcoin.active", - "--bitcoin.simnet", - "--bitcoin.node=neutrino", - "--bitcoin.defaultchanconfs=6", - "--routing.assumechanvalid", - "--neutrino.connect=btcd.simnet.exchangeunion.com:38555", - "--chan-enable-timeout=0m10s", - "--max-cltv-expiry=5000", - ) - } else { // litecoin - t.Command = append(t.Command, - "--debuglevel=debug", - "--nobootstrap", - "--minbackoff=30s", - "--maxbackoff=24h", - "--litecoin.active", - "--litecoin.simnet", - "--litecoin.node=neutrino", - "--litecoin.defaultchanconfs=6", - "--routing.assumechanvalid", - "--neutrino.connect=btcd.simnet.exchangeunion.com:39555", - "--chan-enable-timeout=0m10s", - "--max-cltv-expiry=20000", - ) - } - } - - t.Hostname = t.Name - - return nil -} - -func (t *Lnd) ToJson() map[string]interface{} { - result := t.Base.ToJson() - - result["mode"] = t.Config.Mode - - rpc := make(map[string]interface{}) - result["rpc"] = rpc - rpc["type"] = "gRPC" - - var name string - switch t.Chain { - case "bitcoin": - name = "lndbtc" - case "litecoin": - name = "lndltc" - } - rpc["host"] = name - rpc["port"] = 10009 - rpc["tlsCert"] = fmt.Sprintf("%s/%s/tls.cert", PROXY_DATA_DIR, name) - rpc["macaroon"] = fmt.Sprintf("%s/%s/data/chain/%s/%s/readonly.macaroon", PROXY_DATA_DIR, name, t.Chain, t.Network) - - return result -} diff --git a/service/proxy.go b/service/proxy.go deleted file mode 100644 index 9eef94c..0000000 --- a/service/proxy.go +++ /dev/null @@ -1,85 +0,0 @@ -package service - -import ( - "errors" - "fmt" - "github.com/spf13/cobra" - "runtime" -) - -type ProxyConfig struct { - // add more proxy specified attributes here -} - -type Proxy struct { - Base - - Config ProxyConfig -} - -func newProxy(name string) Proxy { - return Proxy{ - Base: newBase(name), - } -} - -func (t *Proxy) ConfigureFlags(cmd *cobra.Command, network string) error { - if err := t.Base.ConfigureFlags(cmd, network, &BaseConfig{ - Disabled: false, - ExposePorts: []string{}, - Dir: fmt.Sprintf("./data/%s", t.Name), - Image: images[network][t.Name], - }); err != nil { - return err - } - - // configure proxy specified flags here - - return nil -} - -func (t *Proxy) GetConfig() interface{} { - return t.Config -} - -func (t *Proxy) Apply(config *SharedConfig, services map[string]Service) error { - ReflectFillConfig(t.Name, &t.Config) - - network := config.Network - - // validation - if network != "simnet" && network != "testnet" && network != "mainnet" { - return errors.New("invalid network: " + network) - } - - // base apply - err := t.Base.Apply("/root/.proxy", config.Network) - if err != nil { - return err - } - - var dockerSock string - - if runtime.GOOS == "windows" { - dockerSock = "//var/run/docker.sock" - } else { - dockerSock = "/var/run/docker.sock" - } - - // proxy apply - t.Volumes = append(t.Volumes, - fmt.Sprintf("%s:/var/run/docker.sock", dockerSock), - fmt.Sprintf("%s:/root/network:ro", config.NetworkDir), - ) - - switch network { - case "simnet": - t.Ports = append(t.Ports, "127.0.0.1:28889:8080") - case "testnet": - t.Ports = append(t.Ports, "127.0.0.1:18889:8080") - case "mainnet": - t.Ports = append(t.Ports, "127.0.0.1:8889:8080") - } - - return nil -} diff --git a/service/webui.go b/service/webui.go deleted file mode 100644 index 34a4e17..0000000 --- a/service/webui.go +++ /dev/null @@ -1,64 +0,0 @@ -package service - -import ( - "errors" - "fmt" - "github.com/spf13/cobra" -) - -type WebuiConfig struct { - // add more webui specified attributes here -} - -type Webui struct { - Base - - Config WebuiConfig -} - -func newWebui(name string) Webui { - return Webui{ - Base: newBase(name), - } -} - -func (t *Webui) ConfigureFlags(cmd *cobra.Command, network string) error { - if err := t.Base.ConfigureFlags(cmd, network, &BaseConfig{ - Disabled: true, - ExposePorts: []string{}, - Dir: fmt.Sprintf("./data/%s", t.Name), - Image: images[network][t.Name], - }); err != nil { - return err - } - - // configure webui specified flags here - - return nil -} - -func (t *Webui) GetConfig() interface{} { - return t.Config -} - -func (t *Webui) Apply(config *SharedConfig, services map[string]Service) error { - ReflectFillConfig(t.Name, &t.Config) - - network := config.Network - - // validation - if network != "simnet" && network != "testnet" && network != "mainnet" { - return errors.New("invalid network: " + network) - } - - // base apply - err := t.Base.Apply("/root/.webui", config.Network) - if err != nil { - return err - } - - // webui apply - t.Disabled = true - - return nil -} diff --git a/service/xud.go b/service/xud.go deleted file mode 100644 index b9784eb..0000000 --- a/service/xud.go +++ /dev/null @@ -1,113 +0,0 @@ -package service - -import ( - "errors" - "fmt" - "github.com/spf13/cobra" -) - -type XudConfig struct { - PreserveConfig string `usage:"Preserve xud xud.conf file during updates"` -} - -type Xud struct { - Base - - Config XudConfig - - PreserveConfig bool -} - -func newXud(name string) Xud { - return Xud{ - Base: newBase(name), - } -} - -func (t *Xud) ConfigureFlags(cmd *cobra.Command, network string) error { - if err := t.Base.ConfigureFlags(cmd, network, &BaseConfig{ - Disabled: false, - ExposePorts: []string{}, - Dir: fmt.Sprintf("./data/%s", t.Name), - Image: images[network][t.Name], - }); err != nil { - return err - } - - if err := ReflectFlags(t.Name, &t.Config, &XudConfig{ - PreserveConfig: "", - }, cmd); err != nil { - return err - } - - return nil -} - -func (t *Xud) GetConfig() interface{} { - return t.Config -} - -func (t *Xud) Apply(config *SharedConfig, services map[string]Service) error { - ReflectFillConfig(t.Name, &t.Config) - - network := config.Network - - // validation - if network != "simnet" && network != "testnet" && network != "mainnet" { - return errors.New("invalid network: " + network) - } - - // base apply - err := t.Base.Apply("/root/.xud", config.Network) - if err != nil { - return err - } - - // xud apply - t.Environment["NETWORK"] = network - t.Environment["NODE_ENV"] = "production" - - if t.Config.PreserveConfig == "true" { - t.Environment["PRESERVE_CONFIG"] = "true" - } else { - t.Environment["PRESERVE_CONFIG"] = "false" - } - - t.Volumes = append(t.Volumes, - "./data/lndbtc:/root/.lndbtc", - "./data/lndltc:/root/.lndltc", - //"/:/mnt/hostfs", - "./backup:/root/backup", - ) - - switch network { - case "simnet": - t.Ports = append(t.Ports, "28885") - case "testnet": - t.Ports = append(t.Ports, "18885") - case "mainnet": - t.Ports = append(t.Ports, "8885") - } - - return nil -} - -func (t *Xud) ToJson() map[string]interface{} { - result := t.Base.ToJson() - - rpc := make(map[string]interface{}) - result["rpc"] = rpc - rpc["type"] = "gRPC" - rpc["host"] = "xud" - switch t.Network { - case "simnet": - rpc["port"] = 28886 - case "testnet": - rpc["port"] = 18886 - case "mainnet": - rpc["port"] = 8886 - } - rpc["tlsCert"] = fmt.Sprintf("%s/xud/tls.cert", PROXY_DATA_DIR) - - return result -}