From dc8d5829ee271bcf69d6679b60bb1d1d80a7c385 Mon Sep 17 00:00:00 2001 From: Mohammed <79150699+mrpalide@users.noreply.github.com> Date: Sun, 10 Dec 2023 11:11:23 +0000 Subject: [PATCH] improve `skywire-cli proxy` command (#1680) * improve output of skywire-cli proxy status command * add -addr value to generated config for skysocks-client * improve skywire-cli proxy stop command with add two flag --all and --name * fix no flag choosen issue * add condition on choosen at least one flag for stop command * implement proxy start command | implement rpc for custom setting arguments * fix rpc query on customsetting * fix random app port on adding new app * add ctrl+c signal to command --- cmd/skywire-cli/commands/config/gen.go | 1 + cmd/skywire-cli/commands/proxy/proxy.go | 138 ++++++++++++++++++++---- cmd/skywire-cli/commands/proxy/root.go | 4 + cmd/skywire-cli/commands/vpn/vvpn.go | 11 ++ pkg/visor/api.go | 24 ++++- pkg/visor/rpc.go | 27 ++++- pkg/visor/rpc_client.go | 23 +++- pkg/visor/visorconfig/config.go | 1 + pkg/visor/visorconfig/v1.go | 36 +++++++ 9 files changed, 229 insertions(+), 36 deletions(-) diff --git a/cmd/skywire-cli/commands/config/gen.go b/cmd/skywire-cli/commands/config/gen.go index 0635419b11..60da07fb2a 100644 --- a/cmd/skywire-cli/commands/config/gen.go +++ b/cmd/skywire-cli/commands/config/gen.go @@ -907,6 +907,7 @@ var genConfigCmd = &cobra.Command{ Binary: visorconfig.SkysocksClientName, AutoStart: false, Port: routing.Port(visorconfig.SkysocksClientPort), + Args: []string{"-addr", visorconfig.SkysocksClientAddr}, }, { Name: visorconfig.VPNServerName, diff --git a/cmd/skywire-cli/commands/proxy/proxy.go b/cmd/skywire-cli/commands/proxy/proxy.go index 83d5866260..2c4ef5e821 100644 --- a/cmd/skywire-cli/commands/proxy/proxy.go +++ b/cmd/skywire-cli/commands/proxy/proxy.go @@ -3,6 +3,7 @@ package skysocksc import ( "bytes" + "context" "encoding/json" "fmt" "math/rand" @@ -12,14 +13,17 @@ import ( "text/tabwriter" "time" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/skycoin/skywire-utilities/pkg/buildinfo" + "github.com/skycoin/skywire-utilities/pkg/cmdutil" "github.com/skycoin/skywire-utilities/pkg/skyenv" clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc" "github.com/skycoin/skywire/cmd/skywire-cli/internal" "github.com/skycoin/skywire/pkg/app/appserver" + "github.com/skycoin/skywire/pkg/routing" "github.com/skycoin/skywire/pkg/servicedisc" ) @@ -36,6 +40,10 @@ func init() { version = "" } startCmd.Flags().StringVarP(&pk, "pk", "k", "", "server public key") + startCmd.Flags().StringVarP(&addr, "addr", "a", "", "address of proxy for use") + startCmd.Flags().StringVarP(&clientName, "name", "n", "", "name of skysocks client") + stopCmd.Flags().BoolVar(&allClients, "all", false, "stop all skysocks client") + stopCmd.Flags().StringVar(&clientName, "name", "", "specific skysocks client that want stop") listCmd.Flags().StringVarP(&sdURL, "url", "a", "", "service discovery url default:\n"+skyenv.ServiceDiscAddr) listCmd.Flags().BoolVarP(&directQuery, "direct", "b", false, "query service discovery directly") listCmd.Flags().StringVarP(&pk, "pk", "k", "", "check "+serviceType+" service discovery for public key") @@ -50,25 +58,81 @@ var startCmd = &cobra.Command{ Use: "start", Short: "start the " + serviceType + " client", Run: func(cmd *cobra.Command, args []string) { - //check that a valid public key is provided - err := pubkey.Set(pk) + + rpcClient, err := clirpc.Client(cmd.Flags()) if err != nil { - if len(args) > 0 { - err := pubkey.Set(args[0]) + internal.PrintFatalError(cmd.Flags(), fmt.Errorf("unable to create RPC client: %w", err)) + } + + if clientName != "" && pk != "" && addr != "" { + // add new app with -srv and -addr args, and if app was there just change -srv and -addr args and run it + err := pubkey.Set(pk) + if err != nil { + if len(args) > 0 { + err := pubkey.Set(args[0]) + if err != nil { + internal.PrintFatalError(cmd.Flags(), err) + } + } else { + internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Invalid or missing public key")) + } + } + + arguments := map[string]string{} + arguments["srv"] = pubkey.String() + arguments["addr"] = addr + + _, err = rpcClient.App(clientName) + if err == nil { + err = rpcClient.DoCustomSetting(clientName, arguments) if err != nil { - internal.PrintFatalError(cmd.Flags(), err) + internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Error occurs during set args to custom skysocks client")) } } else { - internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Invalid or missing public key")) + err = rpcClient.AddApp(clientName, "skysocks-client") + if err != nil { + internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Error during add new app")) + } + err = rpcClient.DoCustomSetting(clientName, arguments) + if err != nil { + internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Error occurs during set args to custom skysocks client")) + } } + internal.Catch(cmd.Flags(), rpcClient.StartApp(clientName)) + internal.PrintOutput(cmd.Flags(), nil, "Starting.") + } else if clientName != "" && pk == "" && addr == "" { + internal.Catch(cmd.Flags(), rpcClient.StartApp(clientName)) + internal.PrintOutput(cmd.Flags(), nil, "Starting.") + } else if pk != "" && clientName == "" && addr == "" { + err := pubkey.Set(pk) + if err != nil { + if len(args) > 0 { + err := pubkey.Set(args[0]) + if err != nil { + internal.PrintFatalError(cmd.Flags(), err) + } + } else { + internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Invalid or missing public key")) + } + } + internal.Catch(cmd.Flags(), rpcClient.StartSkysocksClient(pubkey.String())) + internal.PrintOutput(cmd.Flags(), nil, "Starting.") + clientName = "skysocks-client" + // change defaul skysocks-proxy app -srv arg and run it + } else { + // error + return } - rpcClient, err := clirpc.Client(cmd.Flags()) - if err != nil { - internal.PrintFatalError(cmd.Flags(), fmt.Errorf("unable to create RPC client: %w", err)) - } - //TODO: implement operational timeout - internal.Catch(cmd.Flags(), rpcClient.StartSkysocksClient(pubkey.String())) - internal.PrintOutput(cmd.Flags(), nil, "Starting.") + + ctx, cancel := cmdutil.SignalContext(context.Background(), &logrus.Logger{}) + defer cancel() + go func() { + <-ctx.Done() + cancel() + rpcClient.StopApp(clientName) //nolint + os.Exit(1) + }() + startProcess := true for startProcess { time.Sleep(time.Second * 1) @@ -107,8 +171,19 @@ var stopCmd = &cobra.Command{ if err != nil { internal.PrintFatalError(cmd.Flags(), fmt.Errorf("unable to create RPC client: %w", err)) } - internal.Catch(cmd.Flags(), rpcClient.StopSkysocksClient()) - internal.PrintOutput(cmd.Flags(), "OK", fmt.Sprintln("OK")) + if allClients && clientName != "" { + internal.PrintFatalError(cmd.Flags(), fmt.Errorf("cannot use both --all and --name flag in together")) + } + if !allClients && clientName == "" { + internal.PrintFatalError(cmd.Flags(), fmt.Errorf("you should use one of flags, --all or --name")) + } + if allClients { + internal.Catch(cmd.Flags(), rpcClient.StopSkysocksClients()) + internal.PrintOutput(cmd.Flags(), "all skysocks client stopped", fmt.Sprintln("all skysocks clients stopped")) + return + } + internal.Catch(cmd.Flags(), rpcClient.StopApp(clientName)) + internal.PrintOutput(cmd.Flags(), fmt.Sprintf("skysocks client %s stopped", clientName), fmt.Sprintf("skysocks client %s stopped\n", clientName)) }, } @@ -128,12 +203,16 @@ var statusCmd = &cobra.Command{ w := tabwriter.NewWriter(&b, 0, 0, 5, ' ', tabwriter.TabIndent) internal.Catch(cmd.Flags(), err) type appState struct { - Status string `json:"status"` + Name string `json:"name"` + Status string `json:"status"` + AutoStart bool `json:"autostart"` + Args []string `json:"args"` + AppPort routing.Port `json:"app_port"` } - var jsonAppStatus appState + var jsonAppStatus []appState + fmt.Fprintf(w, "---- All Proxy List -----------------------------------------------------\n\n") for _, state := range states { - if state.Name == stateName { - + if state.AppConfig.Binary == binaryName { status := "stopped" if state.Status == appserver.AppStatusRunning { status = "running" @@ -141,13 +220,28 @@ var statusCmd = &cobra.Command{ if state.Status == appserver.AppStatusErrored { status = "errored" } - jsonAppStatus = appState{ - Status: status, + jsonAppStatus = append(jsonAppStatus, appState{ + Name: state.Name, + Status: status, + AutoStart: state.AutoStart, + Args: state.Args, + AppPort: state.Port, + }) + var tmpAddr string + var tmpSrv string + for idx, arg := range state.Args { + if arg == "-srv" { + tmpSrv = state.Args[idx+1] + } + if arg == "-addr" { + tmpAddr = "127.0.0.1" + state.Args[idx+1] + } } - _, err = fmt.Fprintf(w, "%s\n", status) + _, err = fmt.Fprintf(w, "Name: %s\nStatus: %s\nServer: %s\nAddress: %s\nAppPort: %d\nAutoStart: %t\n\n", state.Name, status, tmpSrv, tmpAddr, state.Port, state.AutoStart) internal.Catch(cmd.Flags(), err) } } + fmt.Fprintf(w, "-------------------------------------------------------------------------\n") internal.Catch(cmd.Flags(), w.Flush()) internal.PrintOutput(cmd.Flags(), jsonAppStatus, b.String()) }, diff --git a/cmd/skywire-cli/commands/proxy/root.go b/cmd/skywire-cli/commands/proxy/root.go index 42a69590b6..d6d5a95a29 100644 --- a/cmd/skywire-cli/commands/proxy/root.go +++ b/cmd/skywire-cli/commands/proxy/root.go @@ -9,6 +9,7 @@ import ( ) var ( + binaryName = "skysocks-client" stateName = "skysocks-client" serviceType = servicedisc.ServiceTypeProxy servicePort = ":44" @@ -22,6 +23,9 @@ var ( sdURL string directQuery bool servers []servicedisc.Service + allClients bool + clientName string + addr string ) // RootCmd contains commands that interact with the skywire-visor diff --git a/cmd/skywire-cli/commands/vpn/vvpn.go b/cmd/skywire-cli/commands/vpn/vvpn.go index 3d0150ee8d..4ab463523e 100644 --- a/cmd/skywire-cli/commands/vpn/vvpn.go +++ b/cmd/skywire-cli/commands/vpn/vvpn.go @@ -3,6 +3,7 @@ package clivpn import ( "bytes" + "context" "encoding/json" "fmt" "math/rand" @@ -12,10 +13,12 @@ import ( "text/tabwriter" "time" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/skycoin/skywire-utilities/pkg/buildinfo" + "github.com/skycoin/skywire-utilities/pkg/cmdutil" "github.com/skycoin/skywire-utilities/pkg/skyenv" clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc" "github.com/skycoin/skywire/cmd/skywire-cli/internal" @@ -80,6 +83,14 @@ var startCmd = &cobra.Command{ } internal.Catch(cmd.Flags(), rpcClient.StartVPNClient(pubkey)) internal.PrintOutput(cmd.Flags(), nil, "Starting.") + ctx, cancel := cmdutil.SignalContext(context.Background(), &logrus.Logger{}) + defer cancel() + go func() { + <-ctx.Done() + cancel() + rpcClient.StopVPNClient("vpn-client") //nolint + os.Exit(1) + }() startProcess := true for startProcess { time.Sleep(time.Second * 1) diff --git a/pkg/visor/api.go b/pkg/visor/api.go index 790d27329d..bba1c8413c 100644 --- a/pkg/visor/api.go +++ b/pkg/visor/api.go @@ -62,6 +62,7 @@ type API interface { App(appName string) (*appserver.AppState, error) Apps() ([]*appserver.AppState, error) StartApp(appName string) error + AddApp(appName, binaryName string) error RegisterApp(procConf appcommon.ProcConfig) (appcommon.ProcKey, error) DeregisterApp(procKey appcommon.ProcKey) error StopApp(appName string) error @@ -89,7 +90,7 @@ type API interface { //skysocks-client controls StartSkysocksClient(pk string) error - StopSkysocksClient() error + StopSkysocksClients() error ProxyServers(version, country string) ([]servicedisc.Service, error) //transports @@ -462,6 +463,15 @@ func (v *Visor) StartApp(appName string) error { return ErrProcNotAvailable } +// AddApp implement API. +func (v *Visor) AddApp(appName, binaryName string) error { + // check process manager and app launcher availability + if v.appL == nil { + return ErrAppLauncherNotAvailable + } + return v.conf.AddAppConfig(v.appL, appName, binaryName) +} + // RegisterApp implements API. func (v *Visor) RegisterApp(procConf appcommon.ProcConfig) (appcommon.ProcKey, error) { // check process manager and app launcher availability @@ -618,15 +628,19 @@ func (v *Visor) StartSkysocksClient(serverKey string) error { return errors.New("no skysocks-client app configuration found") } -// StopSkysocksClient implements API. -func (v *Visor) StopSkysocksClient() error { +// StopSkysocksClients implements API. +func (v *Visor) StopSkysocksClients() error { // check process manager and app launcher availability if v.appL == nil { return ErrAppLauncherNotAvailable } if v.procM != nil { - _, err := v.appL.StopApp(visorconfig.SkysocksClientName) //nolint:errcheck - return err + for _, app := range v.conf.Launcher.Apps { + if app.Binary == visorconfig.SkysocksClientName { + v.appL.StopApp(app.Name) //nolint + } + } + return nil } return ErrProcNotAvailable } diff --git a/pkg/visor/rpc.go b/pkg/visor/rpc.go index 6cde0f716b..924ee69d8c 100644 --- a/pkg/visor/rpc.go +++ b/pkg/visor/rpc.go @@ -248,6 +248,25 @@ func (r *RPC) StartApp(name *string, _ *struct{}) (err error) { return r.visor.StartApp(*name) } +// SetAppAddIn is input for SetAppAdd. +type SetAppAddIn struct { + AppName string + BinaryName string +} + +// AddApp add app to config +func (r *RPC) AddApp(in *SetAppAddIn, _ *struct{}) (err error) { + defer rpcutil.LogCall(r.log, "AddApp", in)(nil, &err) + + return r.visor.AddApp(in.AppName, in.BinaryName) +} + +// DoCustomSetting set custom setting to apps arguments +func (r *RPC) DoCustomSetting(in *SetAppMapIn, _ *struct{}) (err error) { + defer rpcutil.LogCall(r.log, "DoCustomSetting", in)(nil, &err) + return r.visor.DoCustomSetting(in.AppName, in.Val) +} + // RegisterApp registers a App with provided proc config. func (r *RPC) RegisterApp(procConf *appcommon.ProcConfig, reply *appcommon.ProcKey) (err error) { defer rpcutil.LogCall(r.log, "RegisterApp", procConf)(reply, &err) @@ -298,11 +317,11 @@ func (r *RPC) StartSkysocksClient(pk string, _ *struct{}) (err error) { return r.visor.StartSkysocksClient(pk) } -// StopSkysocksClient stops SkysocksClient App -func (r *RPC) StopSkysocksClient(_ *struct{}, _ *struct{}) (err error) { - defer rpcutil.LogCall(r.log, "StopSkysocksClient", nil)(nil, &err) +// StopSkysocksClients stops all SkysocksClient Apps +func (r *RPC) StopSkysocksClients(_ *struct{}, _ *struct{}) (err error) { + defer rpcutil.LogCall(r.log, "StopSkysocksClients", nil)(nil, &err) - return r.visor.StopSkysocksClient() + return r.visor.StopSkysocksClients() } // RestartApp restarts App with provided name. diff --git a/pkg/visor/rpc_client.go b/pkg/visor/rpc_client.go index a6aff2d71b..67051985af 100644 --- a/pkg/visor/rpc_client.go +++ b/pkg/visor/rpc_client.go @@ -161,6 +161,14 @@ func (rc *rpcClient) StartApp(appName string) error { return rc.Call("StartApp", &appName, &struct{}{}) } +// AddApp calls AddApp. +func (rc *rpcClient) AddApp(appName, binaryName string) error { + return rc.Call("AddApp", &SetAppAddIn{ + AppName: appName, + BinaryName: binaryName, + }, &struct{}{}) +} + // RegisterApp calls RegisterApp. func (rc *rpcClient) RegisterApp(procConf appcommon.ProcConfig) (appcommon.ProcKey, error) { var procKey appcommon.ProcKey @@ -200,9 +208,9 @@ func (rc *rpcClient) StartSkysocksClient(pk string) error { return rc.Call("StartSkysocksClient", pk, &struct{}{}) } -// StopSkysocksClient calls StopSkysocksClient. -func (rc *rpcClient) StopSkysocksClient() error { - return rc.Call("StopSkysocksClient", &struct{}{}, &struct{}{}) +// StopSkysocksCliens calls StopSkysocksCliens. +func (rc *rpcClient) StopSkysocksClients() error { + return rc.Call("StopSkysocksClients", &struct{}{}, &struct{}{}) } // SetAppDetailedStatus sets app's detailed state. @@ -834,6 +842,11 @@ func (*mockRPCClient) StartApp(string) error { return nil } +// AddApp implement API. +func (*mockRPCClient) AddApp(string, string) error { + return nil +} + // RegisterApp implements API. func (*mockRPCClient) RegisterApp(appcommon.ProcConfig) (appcommon.ProcKey, error) { return appcommon.ProcKey{}, nil @@ -869,8 +882,8 @@ func (*mockRPCClient) StartSkysocksClient(string) error { return nil } -// StopSkysocksClient implements API. -func (*mockRPCClient) StopSkysocksClient() error { +// StopSkysocksClients implements API. +func (*mockRPCClient) StopSkysocksClients() error { return nil } diff --git a/pkg/visor/visorconfig/config.go b/pkg/visor/visorconfig/config.go index 4c5e7619a0..3cd40c8869 100644 --- a/pkg/visor/visorconfig/config.go +++ b/pkg/visor/visorconfig/config.go @@ -257,6 +257,7 @@ func makeDefaultLauncherAppsConfig(dnsServer string) []appserver.AppConfig { Binary: SkysocksClientName, AutoStart: false, Port: routing.Port(skyenv.SkysocksClientPort), + Args: []string{"-addr", SkysocksClientAddr}, }, { Name: VPNServerName, diff --git a/pkg/visor/visorconfig/v1.go b/pkg/visor/visorconfig/v1.go index fbc7da63af..48a26f2611 100644 --- a/pkg/visor/visorconfig/v1.go +++ b/pkg/visor/visorconfig/v1.go @@ -3,6 +3,7 @@ package visorconfig import ( "fmt" + "math/rand" "strings" "sync" @@ -10,6 +11,7 @@ import ( "github.com/skycoin/skywire/pkg/app/appserver" "github.com/skycoin/skywire/pkg/app/launcher" "github.com/skycoin/skywire/pkg/dmsgc" + "github.com/skycoin/skywire/pkg/routing" "github.com/skycoin/skywire/pkg/transport" "github.com/skycoin/skywire/pkg/transport/network" ) @@ -237,6 +239,40 @@ func (v1 *V1) UpdatePublicAutoconnect(pAc bool) error { return v1.flush(v1) } +// AddAppConfig add new config to apps if name was not same +func (v1 *V1) AddAppConfig(launch *launcher.AppLauncher, appName, binaryName string) error { + v1.mu.Lock() + defer v1.mu.Unlock() + + conf := v1.Launcher + busyPorts := map[routing.Port]bool{} + for _, app := range conf.Apps { + busyPorts[app.Port] = true + if app.Name == appName { + return fmt.Errorf("the app exist") + } + } + var randomNumber int + for { + min := 10 + max := 99 + randomNumber = rand.Intn(max-min+1) + min //nolint + if _, ok := busyPorts[routing.Port(randomNumber)]; !ok { + break + } + } + + conf.Apps = append(conf.Apps, appserver.AppConfig{Name: appName, Binary: binaryName, Port: routing.Port(randomNumber)}) + + launch.ResetConfig(launcher.AppLauncherConfig{ + VisorPK: v1.PK, + Apps: conf.Apps, + ServerAddr: conf.ServerAddr, + DisplayNodeIP: conf.DisplayNodeIP, + }) + return v1.flush(v1) +} + // updateStringArg updates the cli non-boolean flag of the specified app config and also within the // It removes argName from app args if value is an empty string. // The updated config gets flushed to file if there are any changes.