github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/cmd/commands/service/command.go (about)

     1  /*
     2   * Copyright (C) 2017 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package service
    19  
    20  import (
    21  	"fmt"
    22  	"os"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/mysteriumnetwork/terms/terms-go"
    27  
    28  	"github.com/pkg/errors"
    29  	"github.com/rs/zerolog/log"
    30  	"github.com/urfave/cli/v2"
    31  
    32  	"github.com/mysteriumnetwork/node/cmd"
    33  	"github.com/mysteriumnetwork/node/cmd/commands/cli/clio"
    34  	"github.com/mysteriumnetwork/node/config"
    35  	"github.com/mysteriumnetwork/node/config/urfavecli/clicontext"
    36  	"github.com/mysteriumnetwork/node/core/node"
    37  	"github.com/mysteriumnetwork/node/metadata"
    38  	"github.com/mysteriumnetwork/node/services"
    39  	"github.com/mysteriumnetwork/node/tequilapi/client"
    40  	"github.com/mysteriumnetwork/node/tequilapi/contract"
    41  )
    42  
    43  // NewCommand function creates service command
    44  func NewCommand(licenseCommandName string) *cli.Command {
    45  	var di cmd.Dependencies
    46  	command := &cli.Command{
    47  		Name:      "service",
    48  		Usage:     "Starts and publishes services on Mysterium Network",
    49  		ArgsUsage: "comma separated list of services to start",
    50  		Before:    clicontext.LoadUserConfigQuietly,
    51  		Action: func(ctx *cli.Context) error {
    52  			quit := make(chan error)
    53  			config.ParseFlagsServiceStart(ctx)
    54  			config.ParseFlagsServiceOpenvpn(ctx)
    55  			config.ParseFlagsServiceWireguard(ctx)
    56  			config.ParseFlagsServiceNoop(ctx)
    57  			config.ParseFlagsNode(ctx)
    58  
    59  			if err := config.ValidateWireguardMTUFlag(); err != nil {
    60  				log.Error().Msg(err.Error())
    61  				return err
    62  			}
    63  
    64  			if err := hasAcceptedTOS(ctx); err != nil {
    65  				clio.PrintTOSError(err)
    66  				os.Exit(2)
    67  			}
    68  
    69  			nodeOptions := node.GetOptions()
    70  			nodeOptions.Discovery.FetchEnabled = false
    71  			if err := di.Bootstrap(*nodeOptions); err != nil {
    72  				return err
    73  			}
    74  			go func() { quit <- di.Node.Wait() }()
    75  
    76  			cmd.RegisterSignalCallback(func() { quit <- nil })
    77  
    78  			cmdService := &serviceCommand{
    79  				tequilapi:    client.NewClient(nodeOptions.TequilapiAddress, nodeOptions.TequilapiPort),
    80  				errorChannel: quit,
    81  			}
    82  			go func() {
    83  				quit <- cmdService.Run(ctx)
    84  			}()
    85  
    86  			return describeQuit(<-quit)
    87  		},
    88  		After: func(ctx *cli.Context) error {
    89  			return di.Shutdown()
    90  		},
    91  	}
    92  
    93  	config.RegisterFlagsServiceStart(&command.Flags)
    94  	config.RegisterFlagsServiceOpenvpn(&command.Flags)
    95  	config.RegisterFlagsServiceWireguard(&command.Flags)
    96  	config.RegisterFlagsServiceNoop(&command.Flags)
    97  
    98  	return command
    99  }
   100  
   101  func describeQuit(err error) error {
   102  	if err == nil {
   103  		log.Info().Msg("Stopping application")
   104  	} else {
   105  		log.Error().Err(err).Stack().Msg("Terminating application due to error")
   106  	}
   107  	return err
   108  }
   109  
   110  // serviceCommand represent entrypoint for service command with top level components
   111  type serviceCommand struct {
   112  	tequilapi    *client.Client
   113  	errorChannel chan error
   114  }
   115  
   116  // Run runs a command
   117  func (sc *serviceCommand) Run(ctx *cli.Context) (err error) {
   118  	serviceTypes := make([]string, 0)
   119  
   120  	activeServices := config.Current.GetString(config.FlagActiveServices.Name)
   121  	if len(activeServices) != 0 {
   122  		serviceTypes = strings.Split(activeServices, ",")
   123  	}
   124  
   125  	sc.tryRememberTOS(ctx, sc.errorChannel)
   126  	providerID := sc.unlockIdentity(
   127  		ctx.String(config.FlagIdentity.Name),
   128  		ctx.String(config.FlagIdentityPassphrase.Name),
   129  	)
   130  	log.Info().Msgf("Unlocked identity: %v", providerID)
   131  
   132  	if config.Current.GetString(config.FlagNodeVersion.Name) == "" {
   133  
   134  		// on first version update: enable dvpn service if wireguard service is enabled
   135  		mapServices := make(map[string]bool, 0)
   136  		for _, serviceType := range serviceTypes {
   137  			mapServices[serviceType] = true
   138  		}
   139  
   140  		if mapServices["wireguard"] && !mapServices["dvpn"] {
   141  			serviceTypes = append(serviceTypes, "dvpn")
   142  		}
   143  	}
   144  	// save the version
   145  	config.Current.SetUser(config.FlagNodeVersion.Name, metadata.BuildNumber)
   146  	config.Current.SaveUserConfig()
   147  
   148  	for _, serviceType := range serviceTypes {
   149  		serviceOpts, err := services.GetStartOptions(serviceType)
   150  		if err != nil {
   151  			return err
   152  		}
   153  		startRequest := contract.ServiceStartRequest{
   154  			ProviderID:     providerID,
   155  			Type:           serviceType,
   156  			AccessPolicies: &contract.ServiceAccessPolicies{IDs: serviceOpts.AccessPolicyList},
   157  			Options:        serviceOpts,
   158  		}
   159  
   160  		go sc.runService(startRequest)
   161  	}
   162  
   163  	return <-sc.errorChannel
   164  }
   165  
   166  func (sc *serviceCommand) unlockIdentity(id, passphrase string) string {
   167  	const retryRate = 10 * time.Second
   168  	for {
   169  		id, err := sc.tequilapi.CurrentIdentity(id, passphrase)
   170  		if err == nil {
   171  			return id.Address
   172  		}
   173  		log.Warn().Err(err).Msg("Failed to get current identity")
   174  		log.Warn().Msgf("retrying in %vs...", retryRate.Seconds())
   175  		time.Sleep(retryRate)
   176  	}
   177  }
   178  
   179  func (sc *serviceCommand) tryRememberTOS(ctx *cli.Context, errCh chan error) {
   180  	if !ctx.Bool(config.FlagAgreedTermsConditions.Name) {
   181  		return
   182  	}
   183  
   184  	doUpdate := func() {
   185  		t := true
   186  		for i := 0; i < 5; i++ {
   187  			if err := sc.tequilapi.UpdateTerms(contract.TermsRequest{
   188  				AgreedProvider: &t,
   189  				AgreedConsumer: &t,
   190  				AgreedVersion:  terms.TermsVersion,
   191  			}); err == nil {
   192  				return
   193  			}
   194  			time.Sleep(time.Second * 2)
   195  		}
   196  	}
   197  
   198  	go func() {
   199  		select {
   200  		case <-errCh:
   201  			return
   202  		default:
   203  			doUpdate()
   204  		}
   205  	}()
   206  }
   207  
   208  func (sc *serviceCommand) runService(request contract.ServiceStartRequest) {
   209  	_, err := sc.tequilapi.ServiceStart(request)
   210  	if err != nil {
   211  		sc.errorChannel <- errors.Wrapf(err, "failed to run service %s", request.Type)
   212  	}
   213  }
   214  
   215  func hasAcceptedTOS(ctx *cli.Context) error {
   216  	if ctx.Bool(config.FlagAgreedTermsConditions.Name) {
   217  		return nil
   218  	}
   219  
   220  	agreed := config.Current.GetBool(contract.TermsProviderAgreed)
   221  	if !agreed {
   222  		return errors.New("You must agree with provider terms of use in order to use this command")
   223  	}
   224  
   225  	version := config.Current.GetString(contract.TermsVersion)
   226  	if version != terms.TermsVersion {
   227  		return fmt.Errorf("you've agreed to terms of use version %s, but version %s is required", version, terms.TermsVersion)
   228  	}
   229  
   230  	return nil
   231  }