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 }