From 3da1c0d4603ff1647dd8afdf062b90cb2f774c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denny=20K=C3=B6nigsmann?= Date: Mon, 21 Dec 2020 10:18:57 +0100 Subject: [PATCH 01/12] Initial interactive mode --- cli.go | 4 +- cmd/root.go | 141 +++++++++++++++++++++- go.mod | 1 + go.sum | 19 +++ internal/pkg/closehandler/closehandler.go | 3 +- 5 files changed, 162 insertions(+), 6 deletions(-) diff --git a/cli.go b/cli.go index e4584d2..01906c3 100644 --- a/cli.go +++ b/cli.go @@ -5,6 +5,7 @@ package main import ( "github.com/loophole/cli/cmd" "github.com/loophole/cli/config" + "github.com/loophole/cli/internal/pkg/closehandler" ) var ( @@ -18,5 +19,6 @@ func main() { config.Config.CommitHash = commit config.Config.ClientMode = mode - cmd.Execute() + c := closehandler.SetupCloseHandler("https://forms.gle/K9ga7FZB3deaffnV7") + cmd.Execute(c) } diff --git a/cmd/root.go b/cmd/root.go index a99feff..6212cb4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,10 +6,14 @@ import ( "fmt" stdlog "log" "os" + "strconv" + "strings" "time" + "github.com/AlecAivazis/survey/v2" "github.com/loophole/cli/config" "github.com/loophole/cli/internal/pkg/cache" + "github.com/loophole/cli/internal/pkg/token" "github.com/mattn/go-colorable" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -17,15 +21,141 @@ import ( "github.com/spf13/cobra" ) +var signalChan chan os.Signal + +var b bool + var rootCmd = &cobra.Command{ Use: "loophole", Short: "Loophole - End to end TLS encrypted TCP communication between you and your clients", Long: "Loophole - End to end TLS encrypted TCP communication between you and your clients", Run: func(cmd *cobra.Command, args []string) { - cmd.Help() + if !b { + b = true + interactivePrompt() + } }, } +func interactivePrompt() { + cmd := httpCmd.Root() //find a better way to access rootCMD + + if !token.IsTokenSaved() { + cmd.SetArgs([]string{"account", "login"}) + cmd.Execute() + } + + initq := &survey.Select{ + Message: "Welcome to loophole. What do you want to do?", + Options: []string{"Expose an HTTP Port", "Expose a local path", "Expose a local path with WebDAV", "Logout"}, + } + var portPrompt = []*survey.Question{ + { + Name: "port", + Prompt: &survey.Input{Message: "Please enter the http port you want to expose: "}, + Validate: survey.Required, + Transform: survey.Title, + }, + } + var pathPrompt = []*survey.Question{ + { + Name: "path", + Prompt: &survey.Input{Message: "Please enter the path you want to expose: "}, + Validate: survey.Required, + Transform: survey.Title, + }, + } + logoutPrompt := &survey.Select{ + Message: "Are you sure you want to logout?", + Options: []string{"No", "Yes, I'm sure"}, + } + var res string + var exposePort int + var exposePath string + + err := survey.AskOne(initq, &res) + if err != nil { + signalChan <- nil + } + if res == "Expose an HTTP Port" { + err = survey.Ask(portPrompt, &exposePort) + if err != nil { + signalChan <- nil + } + hostname := askHostname() + if hostname != "" { + cmd.SetArgs([]string{"http", strconv.Itoa(exposePort), "--hostname", strings.ToLower(hostname)}) + } else { + cmd.SetArgs([]string{"http", strconv.Itoa(exposePort)}) + } + cmd.Execute() + } else if res == "Expose a local path" { + err = survey.Ask(pathPrompt, &exposePath) + if err != nil { + signalChan <- nil + } + hostname := askHostname() + if hostname != "" { + cmd.SetArgs([]string{"path", exposePath, "--hostname", strings.ToLower(hostname)}) + } else { + cmd.SetArgs([]string{"path", exposePath}) + } + cmd.Execute() + } else if res == "Expose a local path with WebDAV" { + err = survey.Ask(pathPrompt, &exposePath) + if err != nil { + signalChan <- nil + } + hostname := askHostname() + if hostname != "" { + cmd.SetArgs([]string{"webdav", exposePath, "--hostname", strings.ToLower(hostname)}) + } else { + cmd.SetArgs([]string{"webdav", exposePath}) + } + cmd.Execute() + } else if res == "Logout" { + err := survey.AskOne(logoutPrompt, &res) + if err != nil { + signalChan <- nil + } + if res == "Yes, I'm sure" { + cmd.SetArgs([]string{"account", "logout"}) + cmd.Execute() + } + } +} + +func askHostname() string { + res := "" + prompt := &survey.Select{ + Message: "Do you want to use a custom hostname?", + Options: []string{"Yes", "No"}, + } + var hostnamePrompt = []*survey.Question{ + { + Name: "hostname", + Prompt: &survey.Input{Message: "Please enter the hostname you want to use: "}, + Validate: survey.Required, + Transform: survey.Title, + }, + } + err := survey.AskOne(prompt, &res) + if err != nil { + signalChan <- nil + } + if res == "Yes" { + err = survey.Ask(hostnamePrompt, &res) + time.Sleep(1 * time.Second) + if err != nil { + os.Exit(0) + return err.Error() + } + } else { + return "" + } + return res +} + func init() { cobra.OnInitialize(initLogger) @@ -54,10 +184,13 @@ func initLogger() { } // Execute runs command parsing chain -func Execute() { +func Execute(c chan os.Signal) { rootCmd.Version = fmt.Sprintf("%s (%s)", config.Config.Version, config.Config.CommitHash) - if err := rootCmd.Execute(); err != nil { - os.Exit(1) + signalChan = c + if !b { + if err := rootCmd.Execute(); err != nil { + signalChan <- nil + } } } diff --git a/go.mod b/go.mod index b93ac14..cce6e2a 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/loophole/cli go 1.20 require ( + github.com/AlecAivazis/survey/v2 v2.2.4 github.com/abbot/go-http-auth v0.4.0 github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0 github.com/blang/semver/v4 v4.0.0 diff --git a/go.sum b/go.sum index 75f1ca3..ae994a2 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,10 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/AlecAivazis/survey/v2 v2.2.4 h1:OAh6g17JmXsjVVHTnfQFEi6K+YZX6mrC+pT8IPkUlpk= +github.com/AlecAivazis/survey/v2 v2.2.4/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= +github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0= github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM= @@ -35,6 +40,9 @@ 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/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/gobuffalo/here v0.6.2 h1:ZtCqC7F9ou3moLbYfHM1Tj+gwHGgWhjyRjVjsir9BE0= +github.com/gobuffalo/here v0.6.2/go.mod h1:D75Sq0p2BVHdgQu3vCRsXbg85rx943V19urJpqAVWjI= 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= @@ -51,10 +59,14 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= +github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -63,6 +75,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= +github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs= @@ -75,6 +89,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdp/qrterminal v1.0.1 h1:07+fzVDlPuBlXS8tB0ktTAyf+Lp1j2+2zK3fBOL5b7c= github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -122,6 +138,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -139,6 +156,7 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= @@ -178,6 +196,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/pkg/closehandler/closehandler.go b/internal/pkg/closehandler/closehandler.go index 8e2ae29..6b784c6 100644 --- a/internal/pkg/closehandler/closehandler.go +++ b/internal/pkg/closehandler/closehandler.go @@ -14,7 +14,7 @@ var successfulConnectionOccured bool = false var terminalState *term.State = &term.State{} // SetupCloseHandler ensures that CTRL+C inputs are properly processed, restoring the terminal state from not displaying entered characters where necessary -func SetupCloseHandler(feedbackFormURL string) { +func SetupCloseHandler(feedbackFormURL string) chan os.Signal { var terminalState *term.State c := make(chan os.Signal) signal.Notify(c, os.Interrupt, syscall.SIGTERM) @@ -35,4 +35,5 @@ func SetupCloseHandler(feedbackFormURL string) { communication.ApplicationStop() os.Exit(0) }() + return c } From f5cfc6f15a0c127d49a77d718cb36d9dbc67e3d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denny=20K=C3=B6nigsmann?= Date: Tue, 29 Dec 2020 08:50:08 +0100 Subject: [PATCH 02/12] Display the used arguments for interactive mode --- cmd/root.go | 25 +++++++++++++++++------ internal/pkg/closehandler/closehandler.go | 16 +++++++++++++++ internal/pkg/communication/logger.go | 2 +- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 6212cb4..d1fa089 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,6 +13,7 @@ import ( "github.com/AlecAivazis/survey/v2" "github.com/loophole/cli/config" "github.com/loophole/cli/internal/pkg/cache" + "github.com/loophole/cli/internal/pkg/closehandler" "github.com/loophole/cli/internal/pkg/token" "github.com/mattn/go-colorable" "github.com/rs/zerolog" @@ -84,9 +85,13 @@ func interactivePrompt() { } hostname := askHostname() if hostname != "" { - cmd.SetArgs([]string{"http", strconv.Itoa(exposePort), "--hostname", strings.ToLower(hostname)}) + arguments := []string{"http", strconv.Itoa(exposePort), "--hostname", strings.ToLower(hostname)} + closehandler.SaveArguments(arguments) + cmd.SetArgs(arguments) } else { - cmd.SetArgs([]string{"http", strconv.Itoa(exposePort)}) + arguments := []string{"http", strconv.Itoa(exposePort)} + closehandler.SaveArguments(arguments) + cmd.SetArgs(arguments) } cmd.Execute() } else if res == "Expose a local path" { @@ -96,9 +101,13 @@ func interactivePrompt() { } hostname := askHostname() if hostname != "" { - cmd.SetArgs([]string{"path", exposePath, "--hostname", strings.ToLower(hostname)}) + arguments := []string{"path", exposePath, "--hostname", strings.ToLower(hostname)} + closehandler.SaveArguments(arguments) + cmd.SetArgs(arguments) } else { - cmd.SetArgs([]string{"path", exposePath}) + arguments := []string{"path", exposePath} + closehandler.SaveArguments(arguments) + cmd.SetArgs(arguments) } cmd.Execute() } else if res == "Expose a local path with WebDAV" { @@ -108,9 +117,13 @@ func interactivePrompt() { } hostname := askHostname() if hostname != "" { - cmd.SetArgs([]string{"webdav", exposePath, "--hostname", strings.ToLower(hostname)}) + arguments := []string{"webdav", exposePath, "--hostname", strings.ToLower(hostname)} + closehandler.SaveArguments(arguments) + cmd.SetArgs(arguments) } else { - cmd.SetArgs([]string{"webdav", exposePath}) + arguments := []string{"webdav", exposePath} + closehandler.SaveArguments(arguments) + cmd.SetArgs(arguments) } cmd.Execute() } else if res == "Logout" { diff --git a/internal/pkg/closehandler/closehandler.go b/internal/pkg/closehandler/closehandler.go index 6b784c6..7e890ea 100644 --- a/internal/pkg/closehandler/closehandler.go +++ b/internal/pkg/closehandler/closehandler.go @@ -1,8 +1,10 @@ package closehandler import ( + "fmt" "os" "os/signal" + "strings" "syscall" "github.com/loophole/cli/internal/pkg/communication" @@ -12,6 +14,7 @@ import ( var successfulConnectionOccured bool = false var terminalState *term.State = &term.State{} +var interactiveArgs string = "" // SetupCloseHandler ensures that CTRL+C inputs are properly processed, restoring the terminal state from not displaying entered characters where necessary func SetupCloseHandler(feedbackFormURL string) chan os.Signal { @@ -32,8 +35,21 @@ func SetupCloseHandler(feedbackFormURL string) chan os.Signal { if terminalState != nil { term.Restore(int(os.Stdin.Fd()), terminalState) } + if interactiveArgs != "" { + communication.Info(fmt.Sprintf("Next time, add the following to loophole to start a tunnel with the same settings: %s", interactiveArgs)) + } communication.ApplicationStop() os.Exit(0) }() return c } + +//SuccessfulConnectionOccured sets the corresponding boolean to true, enabling the display of the feedback form URL after closing the CLI +func SuccessfulConnectionOccured() { + successfulConnectionOccured = true +} + +//SaveArguments generated by interactive mode to display after closing to teach the user how to get the same results easier next time +func SaveArguments(args []string) { + interactiveArgs = strings.Join(args, " ") +} diff --git a/internal/pkg/communication/logger.go b/internal/pkg/communication/logger.go index fa52893..f55561a 100644 --- a/internal/pkg/communication/logger.go +++ b/internal/pkg/communication/logger.go @@ -99,7 +99,7 @@ func (l *stdoutLogger) ApplicationStop() { l.messageMutex.Lock() defer l.messageMutex.Unlock() l.divider() - fmt.Fprint(l.colorableOutput, "Goodbye") + fmt.Fprint(l.colorableOutput, "Goodbye\n") fmt.Fprintln(l.colorableOutput, aurora.Cyan(fmt.Sprintf("Thank you for using Loophole. Please give us your feedback via %s and help us improve our services.", config.Config.FeedbackFormURL))) } From e757135d936867426b1811713a5073c3d1e7bfc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denny=20K=C3=B6nigsmann?= Date: Tue, 29 Dec 2020 08:59:12 +0100 Subject: [PATCH 03/12] Remove unnecessary Transform property from prompts --- cmd/root.go | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index d1fa089..50fef8d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,7 +7,6 @@ import ( stdlog "log" "os" "strconv" - "strings" "time" "github.com/AlecAivazis/survey/v2" @@ -52,18 +51,16 @@ func interactivePrompt() { } var portPrompt = []*survey.Question{ { - Name: "port", - Prompt: &survey.Input{Message: "Please enter the http port you want to expose: "}, - Validate: survey.Required, - Transform: survey.Title, + Name: "port", + Prompt: &survey.Input{Message: "Please enter the http port you want to expose: "}, + Validate: survey.Required, }, } var pathPrompt = []*survey.Question{ { - Name: "path", - Prompt: &survey.Input{Message: "Please enter the path you want to expose: "}, - Validate: survey.Required, - Transform: survey.Title, + Name: "path", + Prompt: &survey.Input{Message: "Please enter the path you want to expose: "}, + Validate: survey.Required, }, } logoutPrompt := &survey.Select{ @@ -85,7 +82,7 @@ func interactivePrompt() { } hostname := askHostname() if hostname != "" { - arguments := []string{"http", strconv.Itoa(exposePort), "--hostname", strings.ToLower(hostname)} + arguments := []string{"http", strconv.Itoa(exposePort), "--hostname", hostname} closehandler.SaveArguments(arguments) cmd.SetArgs(arguments) } else { @@ -101,7 +98,7 @@ func interactivePrompt() { } hostname := askHostname() if hostname != "" { - arguments := []string{"path", exposePath, "--hostname", strings.ToLower(hostname)} + arguments := []string{"path", exposePath, "--hostname", hostname} closehandler.SaveArguments(arguments) cmd.SetArgs(arguments) } else { @@ -117,7 +114,7 @@ func interactivePrompt() { } hostname := askHostname() if hostname != "" { - arguments := []string{"webdav", exposePath, "--hostname", strings.ToLower(hostname)} + arguments := []string{"webdav", exposePath, "--hostname", hostname} closehandler.SaveArguments(arguments) cmd.SetArgs(arguments) } else { @@ -146,10 +143,9 @@ func askHostname() string { } var hostnamePrompt = []*survey.Question{ { - Name: "hostname", - Prompt: &survey.Input{Message: "Please enter the hostname you want to use: "}, - Validate: survey.Required, - Transform: survey.Title, + Name: "hostname", + Prompt: &survey.Input{Message: "Please enter the hostname you want to use: "}, + Validate: survey.Required, }, } err := survey.AskOne(prompt, &res) From 3f7ceb62b9cc02453cd99c14c0d8a3fbb7d154a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denny=20K=C3=B6nigsmann?= Date: Tue, 29 Dec 2020 09:29:53 +0100 Subject: [PATCH 04/12] Refactor and Add Basic Auth prompt --- cmd/root.go | 81 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 50fef8d..12f4e7b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -70,6 +70,7 @@ func interactivePrompt() { var res string var exposePort int var exposePath string + var arguments []string err := survey.AskOne(initq, &res) if err != nil { @@ -80,49 +81,19 @@ func interactivePrompt() { if err != nil { signalChan <- nil } - hostname := askHostname() - if hostname != "" { - arguments := []string{"http", strconv.Itoa(exposePort), "--hostname", hostname} - closehandler.SaveArguments(arguments) - cmd.SetArgs(arguments) - } else { - arguments := []string{"http", strconv.Itoa(exposePort)} - closehandler.SaveArguments(arguments) - cmd.SetArgs(arguments) - } - cmd.Execute() + arguments = []string{"http", strconv.Itoa(exposePort)} } else if res == "Expose a local path" { err = survey.Ask(pathPrompt, &exposePath) if err != nil { signalChan <- nil } - hostname := askHostname() - if hostname != "" { - arguments := []string{"path", exposePath, "--hostname", hostname} - closehandler.SaveArguments(arguments) - cmd.SetArgs(arguments) - } else { - arguments := []string{"path", exposePath} - closehandler.SaveArguments(arguments) - cmd.SetArgs(arguments) - } - cmd.Execute() + arguments = []string{"path", exposePath} } else if res == "Expose a local path with WebDAV" { err = survey.Ask(pathPrompt, &exposePath) if err != nil { signalChan <- nil } - hostname := askHostname() - if hostname != "" { - arguments := []string{"webdav", exposePath, "--hostname", hostname} - closehandler.SaveArguments(arguments) - cmd.SetArgs(arguments) - } else { - arguments := []string{"webdav", exposePath} - closehandler.SaveArguments(arguments) - cmd.SetArgs(arguments) - } - cmd.Execute() + arguments = []string{"webdav", exposePath} } else if res == "Logout" { err := survey.AskOne(logoutPrompt, &res) if err != nil { @@ -132,7 +103,50 @@ func interactivePrompt() { cmd.SetArgs([]string{"account", "logout"}) cmd.Execute() } + os.Exit(0) //if Execute() should fail, don't ask for hostname etc. but instead exit + } + + hostname := askHostname() + if hostname != "" { + arguments = append(arguments, "--hostname", hostname) + } + basicAuth := askBasicAuth() + if basicAuth != "" { + arguments = append(arguments, "-u", basicAuth) } + closehandler.SaveArguments(arguments) + cmd.SetArgs(arguments) + cmd.Execute() +} + +func askBasicAuth() string { + res := "" + prompt := &survey.Select{ + Message: "Do you want to secure your tunnel using a username and password?", + Options: []string{"Yes", "No"}, + } + var hostnamePrompt = []*survey.Question{ + { + Name: "username", + Prompt: &survey.Input{Message: "Please enter the username you want to use: "}, + Validate: survey.Required, + }, + } + err := survey.AskOne(prompt, &res) + if err != nil { + signalChan <- nil + } + if res == "Yes" { + err = survey.Ask(hostnamePrompt, &res) + if err != nil { + os.Exit(0) + return err.Error() + } + } else { + return "" + } + return res + } func askHostname() string { @@ -154,7 +168,6 @@ func askHostname() string { } if res == "Yes" { err = survey.Ask(hostnamePrompt, &res) - time.Sleep(1 * time.Second) if err != nil { os.Exit(0) return err.Error() From 96e177589d78074d680128fe45cfea1de64b7e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denny=20K=C3=B6nigsmann?= Date: Tue, 29 Dec 2020 11:19:02 +0100 Subject: [PATCH 05/12] Add validation and small fixes --- cmd/root.go | 68 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 12f4e7b..3a46a7a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,11 +3,14 @@ package cmd import ( + "errors" "fmt" stdlog "log" "os" + "regexp" "strconv" "time" + "unicode" "github.com/AlecAivazis/survey/v2" "github.com/loophole/cli/config" @@ -51,16 +54,43 @@ func interactivePrompt() { } var portPrompt = []*survey.Question{ { - Name: "port", - Prompt: &survey.Input{Message: "Please enter the http port you want to expose: "}, - Validate: survey.Required, + Name: "port", + Prompt: &survey.Input{Message: "Please enter the http port you want to expose: "}, + Validate: func(val interface{}) error { + if port, ok := val.(string); !ok { + return errors.New("enter a valid string") + } else { //else is necessary here to keep access to port + n, err := strconv.Atoi(port) + if err != nil { + return errors.New("port must be between 0-65535") + } + if (n < 0) || (n > 65535) { + return errors.New("port must be between 0-65535") + } + } + + return nil + }, }, } var pathPrompt = []*survey.Question{ { - Name: "path", - Prompt: &survey.Input{Message: "Please enter the path you want to expose: "}, - Validate: survey.Required, + Name: "path", + Prompt: &survey.Input{Message: "Please enter the path you want to expose: "}, + Validate: func(val interface{}) error { + if path, ok := val.(string); !ok { + return errors.New("enter an existing path without any quotation marks") + } else { //else is necessary here to keep access to path + _, err := os.Stat(path) + if err == nil { + return nil + } + return errors.New("enter an existing path without any quotation marks") + } + }, + Transform: survey.TransformString(func(ans string) string { + return fmt.Sprintf("'%s'", ans) + }), }, } logoutPrompt := &survey.Select{ @@ -123,13 +153,12 @@ func askBasicAuth() string { res := "" prompt := &survey.Select{ Message: "Do you want to secure your tunnel using a username and password?", - Options: []string{"Yes", "No"}, + Options: []string{"No", "Yes"}, } - var hostnamePrompt = []*survey.Question{ + var usernamePrompt = []*survey.Question{ { - Name: "username", - Prompt: &survey.Input{Message: "Please enter the username you want to use: "}, - Validate: survey.Required, + Name: "username", + Prompt: &survey.Input{Message: "Please enter the username you want to use: "}, //not asking for a password since it's already implemented in virtual-serve }, } err := survey.AskOne(prompt, &res) @@ -137,7 +166,7 @@ func askBasicAuth() string { signalChan <- nil } if res == "Yes" { - err = survey.Ask(hostnamePrompt, &res) + err = survey.Ask(usernamePrompt, &res) if err != nil { os.Exit(0) return err.Error() @@ -153,13 +182,20 @@ func askHostname() string { res := "" prompt := &survey.Select{ Message: "Do you want to use a custom hostname?", - Options: []string{"Yes", "No"}, + Options: []string{"No", "Yes"}, } var hostnamePrompt = []*survey.Question{ { - Name: "hostname", - Prompt: &survey.Input{Message: "Please enter the hostname you want to use: "}, - Validate: survey.Required, + Name: "hostname", + Prompt: &survey.Input{Message: "Please enter the hostname you want to use: "}, + Validate: func(val interface{}) error { + var validChars = regexp.MustCompile(`^[a-z0-9]+$`).MatchString + if hostname, ok := val.(string); !ok || len(hostname) > 31 || len(hostname) < 6 || !validChars(hostname) || !unicode.IsLetter(rune(hostname[0])) { + return errors.New("hostname must be between 6-31 characters, may only contain lowercase letters and numbers and must start with a letter") + } + + return nil + }, }, } err := survey.AskOne(prompt, &res) From 3afc622d0377cb3574db416648efb30aab6c8d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denny=20K=C3=B6nigsmann?= Date: Tue, 29 Dec 2020 11:54:04 +0100 Subject: [PATCH 06/12] Save last args for interactive mode --- cmd/root.go | 32 ++++++++++++++++++++--- internal/pkg/closehandler/closehandler.go | 4 +++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 3a46a7a..f4ea1e1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,10 +5,12 @@ package cmd import ( "errors" "fmt" + "io/ioutil" stdlog "log" "os" "regexp" "strconv" + "strings" "time" "unicode" @@ -26,15 +28,15 @@ import ( var signalChan chan os.Signal -var b bool +var alreadyRunning bool var rootCmd = &cobra.Command{ Use: "loophole", Short: "Loophole - End to end TLS encrypted TCP communication between you and your clients", Long: "Loophole - End to end TLS encrypted TCP communication between you and your clients", Run: func(cmd *cobra.Command, args []string) { - if !b { - b = true + if !alreadyRunning { + alreadyRunning = true interactivePrompt() } }, @@ -48,6 +50,17 @@ func interactivePrompt() { cmd.Execute() } + argPath := cache.GetLocalStorageFile("lastArgs", "logs") + var lastArgs string = "" + if _, err := os.Stat(argPath); err == nil { + argBytes, _ := ioutil.ReadFile(argPath) + lastArgs = string(argBytes) + fmt.Println(string(lastArgs)) + } + argsq := &survey.Select{ + Message: "Your last settings were: " + lastArgs + " , would you like to reuse them?", + Options: []string{"Yes", "No"}, + } initq := &survey.Select{ Message: "Welcome to loophole. What do you want to do?", Options: []string{"Expose an HTTP Port", "Expose a local path", "Expose a local path with WebDAV", "Logout"}, @@ -102,6 +115,17 @@ func interactivePrompt() { var exposePath string var arguments []string + if lastArgs != "" { + err := survey.AskOne(argsq, &res) + if err != nil { + signalChan <- nil + } + if res == "Yes" { + cmd.SetArgs(strings.Split(lastArgs, " ")) //needs validation + cmd.Execute() + os.Exit(0) + } + } err := survey.AskOne(initq, &res) if err != nil { signalChan <- nil @@ -246,7 +270,7 @@ func Execute(c chan os.Signal) { rootCmd.Version = fmt.Sprintf("%s (%s)", config.Config.Version, config.Config.CommitHash) signalChan = c - if !b { + if !alreadyRunning { if err := rootCmd.Execute(); err != nil { signalChan <- nil } diff --git a/internal/pkg/closehandler/closehandler.go b/internal/pkg/closehandler/closehandler.go index 7e890ea..b4aa8e3 100644 --- a/internal/pkg/closehandler/closehandler.go +++ b/internal/pkg/closehandler/closehandler.go @@ -2,11 +2,13 @@ package closehandler import ( "fmt" + "io/ioutil" "os" "os/signal" "strings" "syscall" + "github.com/loophole/cli/internal/pkg/cache" "github.com/loophole/cli/internal/pkg/communication" "github.com/loophole/cli/internal/pkg/inpututil" "golang.org/x/term" @@ -37,6 +39,8 @@ func SetupCloseHandler(feedbackFormURL string) chan os.Signal { } if interactiveArgs != "" { communication.Info(fmt.Sprintf("Next time, add the following to loophole to start a tunnel with the same settings: %s", interactiveArgs)) + argFile := cache.GetLocalStorageFile("lastArgs", "logs") + ioutil.WriteFile(argFile, []byte(interactiveArgs), 0644) } communication.ApplicationStop() os.Exit(0) From fc0e000eda48f4b52d8d8d0d82a86c7ad756d140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denny=20K=C3=B6nigsmann?= Date: Mon, 4 Jan 2021 10:16:30 +0100 Subject: [PATCH 07/12] Add Review suggestions --- cmd/root.go | 183 +++++++++++++++++++++++++++------------------------- 1 file changed, 94 insertions(+), 89 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index f4ea1e1..208ae18 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -18,7 +18,7 @@ import ( "github.com/loophole/cli/config" "github.com/loophole/cli/internal/pkg/cache" "github.com/loophole/cli/internal/pkg/closehandler" - "github.com/loophole/cli/internal/pkg/token" + "github.com/loophole/cli/internal/pkg/communication" "github.com/mattn/go-colorable" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -30,6 +30,10 @@ var signalChan chan os.Signal var alreadyRunning bool +const http = "Expose an HTTP Port" +const path = "Expose a local path" +const webDAV = "Expose a local path with WebDAV" + var rootCmd = &cobra.Command{ Use: "loophole", Short: "Loophole - End to end TLS encrypted TCP communication between you and your clients", @@ -42,36 +46,14 @@ var rootCmd = &cobra.Command{ }, } -func interactivePrompt() { - cmd := httpCmd.Root() //find a better way to access rootCMD - - if !token.IsTokenSaved() { - cmd.SetArgs([]string{"account", "login"}) - cmd.Execute() - } - - argPath := cache.GetLocalStorageFile("lastArgs", "logs") - var lastArgs string = "" - if _, err := os.Stat(argPath); err == nil { - argBytes, _ := ioutil.ReadFile(argPath) - lastArgs = string(argBytes) - fmt.Println(string(lastArgs)) - } - argsq := &survey.Select{ - Message: "Your last settings were: " + lastArgs + " , would you like to reuse them?", - Options: []string{"Yes", "No"}, - } - initq := &survey.Select{ - Message: "Welcome to loophole. What do you want to do?", - Options: []string{"Expose an HTTP Port", "Expose a local path", "Expose a local path with WebDAV", "Logout"}, - } - var portPrompt = []*survey.Question{ +func getPortPrompt() []*survey.Question { + return []*survey.Question{ { Name: "port", Prompt: &survey.Input{Message: "Please enter the http port you want to expose: "}, Validate: func(val interface{}) error { if port, ok := val.(string); !ok { - return errors.New("enter a valid string") + return errors.New("port must be between 0-65535") } else { //else is necessary here to keep access to port n, err := strconv.Atoi(port) if err != nil { @@ -86,7 +68,10 @@ func interactivePrompt() { }, }, } - var pathPrompt = []*survey.Question{ +} + +func getPathPrompt() []*survey.Question { + return []*survey.Question{ { Name: "path", Prompt: &survey.Input{Message: "Please enter the path you want to expose: "}, @@ -106,71 +91,20 @@ func interactivePrompt() { }), }, } - logoutPrompt := &survey.Select{ - Message: "Are you sure you want to logout?", - Options: []string{"No", "Yes, I'm sure"}, - } - var res string - var exposePort int - var exposePath string - var arguments []string +} - if lastArgs != "" { - err := survey.AskOne(argsq, &res) - if err != nil { - signalChan <- nil - } - if res == "Yes" { - cmd.SetArgs(strings.Split(lastArgs, " ")) //needs validation - cmd.Execute() - os.Exit(0) - } - } - err := survey.AskOne(initq, &res) - if err != nil { - signalChan <- nil - } - if res == "Expose an HTTP Port" { - err = survey.Ask(portPrompt, &exposePort) - if err != nil { - signalChan <- nil - } - arguments = []string{"http", strconv.Itoa(exposePort)} - } else if res == "Expose a local path" { - err = survey.Ask(pathPrompt, &exposePath) - if err != nil { - signalChan <- nil - } - arguments = []string{"path", exposePath} - } else if res == "Expose a local path with WebDAV" { - err = survey.Ask(pathPrompt, &exposePath) - if err != nil { - signalChan <- nil - } - arguments = []string{"webdav", exposePath} - } else if res == "Logout" { - err := survey.AskOne(logoutPrompt, &res) - if err != nil { - signalChan <- nil - } - if res == "Yes, I'm sure" { - cmd.SetArgs([]string{"account", "logout"}) - cmd.Execute() - } - os.Exit(0) //if Execute() should fail, don't ask for hostname etc. but instead exit +func getLastArgsPrompt(lastArgs string) *survey.Select { + return &survey.Select{ + Message: "Your last settings were: '" + lastArgs + "', would you like to reuse them?", + Options: []string{"Yes", "No"}, } +} - hostname := askHostname() - if hostname != "" { - arguments = append(arguments, "--hostname", hostname) - } - basicAuth := askBasicAuth() - if basicAuth != "" { - arguments = append(arguments, "-u", basicAuth) +func getInitialPrompt() *survey.Select { + return &survey.Select{ + Message: "Welcome to loophole. What do you want to do?", + Options: []string{http, path, webDAV}, } - closehandler.SaveArguments(arguments) - cmd.SetArgs(arguments) - cmd.Execute() } func askBasicAuth() string { @@ -199,7 +133,6 @@ func askBasicAuth() string { return "" } return res - } func askHostname() string { @@ -238,6 +171,78 @@ func askHostname() string { return res } +func interactivePrompt() { + argPath := cache.GetLocalStorageFile("lastArgs", "logs") + var lastArgs string = "" + if _, err := os.Stat(argPath); err == nil { + argBytes, err := ioutil.ReadFile(argPath) + if err != nil { + communication.Fatal("Error reading last used arguments:" + err.Error()) + } + lastArgs = string(argBytes) + } + var lastArgsPrompt = getLastArgsPrompt(lastArgs) + var initialPrompt = getInitialPrompt() + var portPrompt = getPortPrompt() + var pathPrompt = getPathPrompt() + + var res string + var exposePort int + var exposePath string + var arguments []string + + cmd := httpCmd.Root() //TODO: find a better way to access rootCMD + + if lastArgs != "" { + err := survey.AskOne(lastArgsPrompt, &res) + if err != nil { + signalChan <- nil + } + if res == "Yes" { + cmd.SetArgs(strings.Split(lastArgs, " ")) //needs validation + cmd.Execute() + os.Exit(1) + } + } + err := survey.AskOne(initialPrompt, &res) + + if err != nil { + signalChan <- nil + } + switch res { + case http: + err = survey.Ask(portPrompt, &exposePort) + if err != nil { + signalChan <- nil + } + arguments = []string{"http", strconv.Itoa(exposePort)} + case path: + err = survey.Ask(pathPrompt, &exposePath) + if err != nil { + signalChan <- nil + } + arguments = []string{"path", exposePath} + case webDAV: + err = survey.Ask(pathPrompt, &exposePath) + if err != nil { + signalChan <- nil + } + arguments = []string{"webdav", exposePath} + } + + hostname := askHostname() + if hostname != "" { + arguments = append(arguments, "--hostname", hostname) + } + basicAuth := askBasicAuth() + if basicAuth != "" { + arguments = append(arguments, "-u", basicAuth) + } + closehandler.SaveArguments(arguments) + cmd.SetArgs(arguments) + cmd.Execute() +} + func init() { cobra.OnInitialize(initLogger) From c8e9f6368fb4756a4bac5164646ff48eb7859826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denny=20K=C3=B6nigsmann?= Date: Tue, 12 Jan 2021 08:55:22 +0100 Subject: [PATCH 08/12] Add more review suggestions, add -c for hostname --- cmd/root.go | 57 +++++++++++++---------- cmd/virtual-serve.go | 3 +- internal/pkg/closehandler/closehandler.go | 3 ++ 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 208ae18..267530b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -30,9 +30,16 @@ var signalChan chan os.Signal var alreadyRunning bool -const http = "Expose an HTTP Port" -const path = "Expose a local path" -const webDAV = "Expose a local path with WebDAV" +//Possible answers for prompts and error messages +const ( + AnswerTunnelTypeHTTP string = "Expose an HTTP Port" + AnswerTunnelTypePath string = "Expose a local path" + AnswerTunnelTypeWebDAV string = "Expose a local path with WebDAV" + AnswerYes string = "Yes" + AnswerNo string = "No" + PortRangeErrorMsg string = "port must be between 0-65535" + PathValidityErrorMsg string = "enter an existing path without any quotation marks" +) var rootCmd = &cobra.Command{ Use: "loophole", @@ -53,14 +60,14 @@ func getPortPrompt() []*survey.Question { Prompt: &survey.Input{Message: "Please enter the http port you want to expose: "}, Validate: func(val interface{}) error { if port, ok := val.(string); !ok { - return errors.New("port must be between 0-65535") + return errors.New(PortRangeErrorMsg) } else { //else is necessary here to keep access to port n, err := strconv.Atoi(port) if err != nil { - return errors.New("port must be between 0-65535") + return errors.New(PortRangeErrorMsg) } if (n < 0) || (n > 65535) { - return errors.New("port must be between 0-65535") + return errors.New(PortRangeErrorMsg) } } @@ -77,17 +84,17 @@ func getPathPrompt() []*survey.Question { Prompt: &survey.Input{Message: "Please enter the path you want to expose: "}, Validate: func(val interface{}) error { if path, ok := val.(string); !ok { - return errors.New("enter an existing path without any quotation marks") + return errors.New(PathValidityErrorMsg) } else { //else is necessary here to keep access to path _, err := os.Stat(path) if err == nil { return nil } - return errors.New("enter an existing path without any quotation marks") + return errors.New(PathValidityErrorMsg) } }, Transform: survey.TransformString(func(ans string) string { - return fmt.Sprintf("'%s'", ans) + return fmt.Sprintf("'%s'", ans) //adding quotation marks to the given answer to prevent issues with spaces in paths }), }, } @@ -95,15 +102,15 @@ func getPathPrompt() []*survey.Question { func getLastArgsPrompt(lastArgs string) *survey.Select { return &survey.Select{ - Message: "Your last settings were: '" + lastArgs + "', would you like to reuse them?", - Options: []string{"Yes", "No"}, + Message: fmt.Sprintf("Your last settings were: '%s', would you like to reuse them?", lastArgs), + Options: []string{AnswerYes, AnswerNo}, } } func getInitialPrompt() *survey.Select { return &survey.Select{ Message: "Welcome to loophole. What do you want to do?", - Options: []string{http, path, webDAV}, + Options: []string{AnswerTunnelTypeHTTP, AnswerTunnelTypePath, AnswerTunnelTypeWebDAV}, } } @@ -111,7 +118,7 @@ func askBasicAuth() string { res := "" prompt := &survey.Select{ Message: "Do you want to secure your tunnel using a username and password?", - Options: []string{"No", "Yes"}, + Options: []string{AnswerNo, AnswerYes}, } var usernamePrompt = []*survey.Question{ { @@ -123,7 +130,7 @@ func askBasicAuth() string { if err != nil { signalChan <- nil } - if res == "Yes" { + if res == AnswerYes { err = survey.Ask(usernamePrompt, &res) if err != nil { os.Exit(0) @@ -139,16 +146,16 @@ func askHostname() string { res := "" prompt := &survey.Select{ Message: "Do you want to use a custom hostname?", - Options: []string{"No", "Yes"}, + Options: []string{AnswerNo, AnswerYes}, } var hostnamePrompt = []*survey.Question{ { Name: "hostname", Prompt: &survey.Input{Message: "Please enter the hostname you want to use: "}, Validate: func(val interface{}) error { - var validChars = regexp.MustCompile(`^[a-z0-9]+$`).MatchString - if hostname, ok := val.(string); !ok || len(hostname) > 31 || len(hostname) < 6 || !validChars(hostname) || !unicode.IsLetter(rune(hostname[0])) { - return errors.New("hostname must be between 6-31 characters, may only contain lowercase letters and numbers and must start with a letter") + var validChars = regexp.MustCompile(`^[a-z][a-z0-9]{0,30}$`).MatchString + if hostname, ok := val.(string); !ok || len(hostname) > 31 || !validChars(hostname) || !unicode.IsLetter(rune(hostname[0])) { + return errors.New("hostname must be up to 31 characters, may only contain lowercase letters and numbers and must start with a letter") } return nil @@ -159,7 +166,7 @@ func askHostname() string { if err != nil { signalChan <- nil } - if res == "Yes" { + if res == AnswerYes { err = survey.Ask(hostnamePrompt, &res) if err != nil { os.Exit(0) @@ -198,7 +205,7 @@ func interactivePrompt() { if err != nil { signalChan <- nil } - if res == "Yes" { + if res == AnswerYes { cmd.SetArgs(strings.Split(lastArgs, " ")) //needs validation cmd.Execute() os.Exit(1) @@ -210,19 +217,19 @@ func interactivePrompt() { signalChan <- nil } switch res { - case http: + case AnswerTunnelTypeHTTP: err = survey.Ask(portPrompt, &exposePort) if err != nil { signalChan <- nil } arguments = []string{"http", strconv.Itoa(exposePort)} - case path: + case AnswerTunnelTypePath: err = survey.Ask(pathPrompt, &exposePath) if err != nil { signalChan <- nil } arguments = []string{"path", exposePath} - case webDAV: + case AnswerTunnelTypeWebDAV: err = survey.Ask(pathPrompt, &exposePath) if err != nil { signalChan <- nil @@ -236,10 +243,10 @@ func interactivePrompt() { } basicAuth := askBasicAuth() if basicAuth != "" { - arguments = append(arguments, "-u", basicAuth) + arguments = append(arguments, "--basic-auth-username", basicAuth) } - closehandler.SaveArguments(arguments) cmd.SetArgs(arguments) + closehandler.SaveArguments(arguments) cmd.Execute() } diff --git a/cmd/virtual-serve.go b/cmd/virtual-serve.go index 2307478..954dee1 100644 --- a/cmd/virtual-serve.go +++ b/cmd/virtual-serve.go @@ -33,8 +33,7 @@ func initServeCommand(serveCmd *cobra.Command) { serveCmd.PersistentFlags().StringVarP(&remoteEndpointSpecs.IdentityFile, "identity-file", "i", fmt.Sprintf("%s/id_rsa", sshDir), "private key path") serveCmd.MarkFlagFilename("identity-file") - - serveCmd.PersistentFlags().StringVar(&remoteEndpointSpecs.SiteID, "hostname", "", "custom hostname you want to run service on") + serveCmd.PersistentFlags().StringVarP(&remoteEndpointSpecs.SiteID, "hostname", "c", "", "custom hostname you want to run service on") serveCmd.PersistentFlags().BoolVar(&config.Config.Display.QR, "qr", false, "use if you want a QR version of your url to be shown") serveCmd.PersistentFlags().StringVarP(&remoteEndpointSpecs.BasicAuthUsername, basicAuthUsernameFlagName, "u", "", "Basic authentication username to protect site with") diff --git a/internal/pkg/closehandler/closehandler.go b/internal/pkg/closehandler/closehandler.go index b4aa8e3..3f432fa 100644 --- a/internal/pkg/closehandler/closehandler.go +++ b/internal/pkg/closehandler/closehandler.go @@ -39,6 +39,9 @@ func SetupCloseHandler(feedbackFormURL string) chan os.Signal { } if interactiveArgs != "" { communication.Info(fmt.Sprintf("Next time, add the following to loophole to start a tunnel with the same settings: %s", interactiveArgs)) + if strings.Contains(interactiveArgs, "--basic-auth-username") { + communication.Info("If you want to provide the password as well instead of typing it into the terminal, also add --basic-auth-password ") + } argFile := cache.GetLocalStorageFile("lastArgs", "logs") ioutil.WriteFile(argFile, []byte(interactiveArgs), 0644) } From 823ab4f0dede61bbe57dcaf43b4a62b860d5c9dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denny=20K=C3=B6nigsmann?= Date: Tue, 12 Jan 2021 08:59:31 +0100 Subject: [PATCH 09/12] Fix tunnels with paths not working --- cmd/root.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 267530b..4e37c17 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -93,9 +93,6 @@ func getPathPrompt() []*survey.Question { return errors.New(PathValidityErrorMsg) } }, - Transform: survey.TransformString(func(ans string) string { - return fmt.Sprintf("'%s'", ans) //adding quotation marks to the given answer to prevent issues with spaces in paths - }), }, } } @@ -246,7 +243,23 @@ func interactivePrompt() { arguments = append(arguments, "--basic-auth-username", basicAuth) } cmd.SetArgs(arguments) - closehandler.SaveArguments(arguments) + + var argumentsWithQuotes []string + //setting the path argument in code doesn't work when it contains quotation marks, + //but they do need to be there when entered as a standalone command in a command line if the path contains spaces + //so, we give a copy of the arguments to the closehandler, with the path in quotation marks, where necessary + if strings.Contains(exposePath, " ") { + for i := 0; i < len(arguments); i++ { + if arguments[i] == exposePath { + argumentsWithQuotes = append(argumentsWithQuotes, fmt.Sprintf("'%s'", exposePath)) + } else { + argumentsWithQuotes = append(argumentsWithQuotes, arguments[i]) + } + } + closehandler.SaveArguments(argumentsWithQuotes) + } else { + closehandler.SaveArguments(arguments) + } cmd.Execute() } From e02a7270b1aca068472f9a5879a1b182e0d92690 Mon Sep 17 00:00:00 2001 From: Denny Date: Wed, 20 Oct 2021 13:51:50 +0200 Subject: [PATCH 10/12] Move interactivity things to prompts package --- cmd/root.go | 233 +------------------------------ internal/pkg/prompts/prompts.go | 237 ++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 231 deletions(-) create mode 100644 internal/pkg/prompts/prompts.go diff --git a/cmd/root.go b/cmd/root.go index 4e37c17..ac4165c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,22 +3,14 @@ package cmd import ( - "errors" "fmt" - "io/ioutil" stdlog "log" "os" - "regexp" - "strconv" - "strings" "time" - "unicode" - "github.com/AlecAivazis/survey/v2" "github.com/loophole/cli/config" "github.com/loophole/cli/internal/pkg/cache" - "github.com/loophole/cli/internal/pkg/closehandler" - "github.com/loophole/cli/internal/pkg/communication" + "github.com/loophole/cli/internal/pkg/prompts" "github.com/mattn/go-colorable" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -30,17 +22,6 @@ var signalChan chan os.Signal var alreadyRunning bool -//Possible answers for prompts and error messages -const ( - AnswerTunnelTypeHTTP string = "Expose an HTTP Port" - AnswerTunnelTypePath string = "Expose a local path" - AnswerTunnelTypeWebDAV string = "Expose a local path with WebDAV" - AnswerYes string = "Yes" - AnswerNo string = "No" - PortRangeErrorMsg string = "port must be between 0-65535" - PathValidityErrorMsg string = "enter an existing path without any quotation marks" -) - var rootCmd = &cobra.Command{ Use: "loophole", Short: "Loophole - End to end TLS encrypted TCP communication between you and your clients", @@ -48,221 +29,11 @@ var rootCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { if !alreadyRunning { alreadyRunning = true - interactivePrompt() + prompts.StartInteractivePrompt(httpCmd.Root(), signalChan) } }, } -func getPortPrompt() []*survey.Question { - return []*survey.Question{ - { - Name: "port", - Prompt: &survey.Input{Message: "Please enter the http port you want to expose: "}, - Validate: func(val interface{}) error { - if port, ok := val.(string); !ok { - return errors.New(PortRangeErrorMsg) - } else { //else is necessary here to keep access to port - n, err := strconv.Atoi(port) - if err != nil { - return errors.New(PortRangeErrorMsg) - } - if (n < 0) || (n > 65535) { - return errors.New(PortRangeErrorMsg) - } - } - - return nil - }, - }, - } -} - -func getPathPrompt() []*survey.Question { - return []*survey.Question{ - { - Name: "path", - Prompt: &survey.Input{Message: "Please enter the path you want to expose: "}, - Validate: func(val interface{}) error { - if path, ok := val.(string); !ok { - return errors.New(PathValidityErrorMsg) - } else { //else is necessary here to keep access to path - _, err := os.Stat(path) - if err == nil { - return nil - } - return errors.New(PathValidityErrorMsg) - } - }, - }, - } -} - -func getLastArgsPrompt(lastArgs string) *survey.Select { - return &survey.Select{ - Message: fmt.Sprintf("Your last settings were: '%s', would you like to reuse them?", lastArgs), - Options: []string{AnswerYes, AnswerNo}, - } -} - -func getInitialPrompt() *survey.Select { - return &survey.Select{ - Message: "Welcome to loophole. What do you want to do?", - Options: []string{AnswerTunnelTypeHTTP, AnswerTunnelTypePath, AnswerTunnelTypeWebDAV}, - } -} - -func askBasicAuth() string { - res := "" - prompt := &survey.Select{ - Message: "Do you want to secure your tunnel using a username and password?", - Options: []string{AnswerNo, AnswerYes}, - } - var usernamePrompt = []*survey.Question{ - { - Name: "username", - Prompt: &survey.Input{Message: "Please enter the username you want to use: "}, //not asking for a password since it's already implemented in virtual-serve - }, - } - err := survey.AskOne(prompt, &res) - if err != nil { - signalChan <- nil - } - if res == AnswerYes { - err = survey.Ask(usernamePrompt, &res) - if err != nil { - os.Exit(0) - return err.Error() - } - } else { - return "" - } - return res -} - -func askHostname() string { - res := "" - prompt := &survey.Select{ - Message: "Do you want to use a custom hostname?", - Options: []string{AnswerNo, AnswerYes}, - } - var hostnamePrompt = []*survey.Question{ - { - Name: "hostname", - Prompt: &survey.Input{Message: "Please enter the hostname you want to use: "}, - Validate: func(val interface{}) error { - var validChars = regexp.MustCompile(`^[a-z][a-z0-9]{0,30}$`).MatchString - if hostname, ok := val.(string); !ok || len(hostname) > 31 || !validChars(hostname) || !unicode.IsLetter(rune(hostname[0])) { - return errors.New("hostname must be up to 31 characters, may only contain lowercase letters and numbers and must start with a letter") - } - - return nil - }, - }, - } - err := survey.AskOne(prompt, &res) - if err != nil { - signalChan <- nil - } - if res == AnswerYes { - err = survey.Ask(hostnamePrompt, &res) - if err != nil { - os.Exit(0) - return err.Error() - } - } else { - return "" - } - return res -} - -func interactivePrompt() { - argPath := cache.GetLocalStorageFile("lastArgs", "logs") - var lastArgs string = "" - if _, err := os.Stat(argPath); err == nil { - argBytes, err := ioutil.ReadFile(argPath) - if err != nil { - communication.Fatal("Error reading last used arguments:" + err.Error()) - } - lastArgs = string(argBytes) - } - var lastArgsPrompt = getLastArgsPrompt(lastArgs) - var initialPrompt = getInitialPrompt() - var portPrompt = getPortPrompt() - var pathPrompt = getPathPrompt() - - var res string - var exposePort int - var exposePath string - var arguments []string - - cmd := httpCmd.Root() //TODO: find a better way to access rootCMD - - if lastArgs != "" { - err := survey.AskOne(lastArgsPrompt, &res) - if err != nil { - signalChan <- nil - } - if res == AnswerYes { - cmd.SetArgs(strings.Split(lastArgs, " ")) //needs validation - cmd.Execute() - os.Exit(1) - } - } - err := survey.AskOne(initialPrompt, &res) - - if err != nil { - signalChan <- nil - } - switch res { - case AnswerTunnelTypeHTTP: - err = survey.Ask(portPrompt, &exposePort) - if err != nil { - signalChan <- nil - } - arguments = []string{"http", strconv.Itoa(exposePort)} - case AnswerTunnelTypePath: - err = survey.Ask(pathPrompt, &exposePath) - if err != nil { - signalChan <- nil - } - arguments = []string{"path", exposePath} - case AnswerTunnelTypeWebDAV: - err = survey.Ask(pathPrompt, &exposePath) - if err != nil { - signalChan <- nil - } - arguments = []string{"webdav", exposePath} - } - - hostname := askHostname() - if hostname != "" { - arguments = append(arguments, "--hostname", hostname) - } - basicAuth := askBasicAuth() - if basicAuth != "" { - arguments = append(arguments, "--basic-auth-username", basicAuth) - } - cmd.SetArgs(arguments) - - var argumentsWithQuotes []string - //setting the path argument in code doesn't work when it contains quotation marks, - //but they do need to be there when entered as a standalone command in a command line if the path contains spaces - //so, we give a copy of the arguments to the closehandler, with the path in quotation marks, where necessary - if strings.Contains(exposePath, " ") { - for i := 0; i < len(arguments); i++ { - if arguments[i] == exposePath { - argumentsWithQuotes = append(argumentsWithQuotes, fmt.Sprintf("'%s'", exposePath)) - } else { - argumentsWithQuotes = append(argumentsWithQuotes, arguments[i]) - } - } - closehandler.SaveArguments(argumentsWithQuotes) - } else { - closehandler.SaveArguments(arguments) - } - cmd.Execute() -} - func init() { cobra.OnInitialize(initLogger) diff --git a/internal/pkg/prompts/prompts.go b/internal/pkg/prompts/prompts.go new file mode 100644 index 0000000..fa40477 --- /dev/null +++ b/internal/pkg/prompts/prompts.go @@ -0,0 +1,237 @@ +package prompts + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "regexp" + "strconv" + "strings" + "unicode" + + "github.com/AlecAivazis/survey/v2" + "github.com/loophole/cli/internal/pkg/cache" + "github.com/loophole/cli/internal/pkg/closehandler" + "github.com/loophole/cli/internal/pkg/communication" + "github.com/spf13/cobra" +) + +//Possible answers for prompts and error messages +const ( + AnswerTunnelTypeHTTP string = "Expose an HTTP Port" + AnswerTunnelTypePath string = "Expose a local path" + AnswerTunnelTypeWebDAV string = "Expose a local path with WebDAV" + AnswerYes string = "Yes" + AnswerNo string = "No" + PortRangeErrorMsg string = "port must be between 0-65535" + PathValidityErrorMsg string = "enter an existing path without any quotation marks" +) + +func getPortPrompt() []*survey.Question { + return []*survey.Question{ + { + Name: "port", + Prompt: &survey.Input{Message: "Please enter the http port you want to expose: "}, + Validate: func(val interface{}) error { + if port, ok := val.(string); !ok { + return errors.New(PortRangeErrorMsg) + } else { //else is necessary here to keep access to port + n, err := strconv.Atoi(port) + if err != nil { + return errors.New(PortRangeErrorMsg) + } + if (n < 0) || (n > 65535) { + return errors.New(PortRangeErrorMsg) + } + } + + return nil + }, + }, + } +} + +func getPathPrompt() []*survey.Question { + return []*survey.Question{ + { + Name: "path", + Prompt: &survey.Input{Message: "Please enter the path you want to expose: "}, + Validate: func(val interface{}) error { + if path, ok := val.(string); !ok { + return errors.New(PathValidityErrorMsg) + } else { //else is necessary here to keep access to path + _, err := os.Stat(path) + if err == nil { + return nil + } + return errors.New(PathValidityErrorMsg) + } + }, + }, + } +} + +func getLastArgsPrompt(lastArgs string) *survey.Select { + return &survey.Select{ + Message: fmt.Sprintf("Your last settings were: '%s', would you like to reuse them?", lastArgs), + Options: []string{AnswerYes, AnswerNo}, + } +} + +func getInitialPrompt() *survey.Select { + return &survey.Select{ + Message: "Welcome to loophole. What do you want to do?", + Options: []string{AnswerTunnelTypeHTTP, AnswerTunnelTypePath, AnswerTunnelTypeWebDAV}, + } +} + +func askBasicAuth(signalChan chan os.Signal) string { + res := "" + prompt := &survey.Select{ + Message: "Do you want to secure your tunnel using a username and password?", + Options: []string{AnswerNo, AnswerYes}, + } + var usernamePrompt = []*survey.Question{ + { + Name: "username", + Prompt: &survey.Input{Message: "Please enter the username you want to use: "}, //not asking for a password since it's already implemented in virtual-serve + }, + } + err := survey.AskOne(prompt, &res) + if err != nil { + signalChan <- nil + } + if res == AnswerYes { + err = survey.Ask(usernamePrompt, &res) + if err != nil { + os.Exit(0) + return err.Error() + } + } else { + return "" + } + return res +} + +func askHostname(signalChan chan os.Signal) string { + res := "" + prompt := &survey.Select{ + Message: "Do you want to use a custom hostname?", + Options: []string{AnswerNo, AnswerYes}, + } + var hostnamePrompt = []*survey.Question{ + { + Name: "hostname", + Prompt: &survey.Input{Message: "Please enter the hostname you want to use: "}, + Validate: func(val interface{}) error { + var validChars = regexp.MustCompile(`^[a-z][a-z0-9]{0,30}$`).MatchString + if hostname, ok := val.(string); !ok || len(hostname) > 31 || !validChars(hostname) || !unicode.IsLetter(rune(hostname[0])) { + return errors.New("hostname must be up to 31 characters, may only contain lowercase letters and numbers and must start with a letter") + } + + return nil + }, + }, + } + err := survey.AskOne(prompt, &res) + if err != nil { + signalChan <- nil + } + if res == AnswerYes { + err = survey.Ask(hostnamePrompt, &res) + if err != nil { + os.Exit(0) + return err.Error() + } + } else { + return "" + } + return res +} + +func StartInteractivePrompt(cmd *cobra.Command, signalChan chan os.Signal) { + argPath := cache.GetLocalStorageFile("lastArgs", "logs") + var lastArgs string = "" + if _, err := os.Stat(argPath); err == nil { + argBytes, err := ioutil.ReadFile(argPath) + if err != nil { + communication.Fatal("Error reading last used arguments:" + err.Error()) + } + lastArgs = string(argBytes) + } + var lastArgsPrompt = getLastArgsPrompt(lastArgs) + var initialPrompt = getInitialPrompt() + var portPrompt = getPortPrompt() + var pathPrompt = getPathPrompt() + + var res string + var exposePort int + var exposePath string + var arguments []string + + if lastArgs != "" { + err := survey.AskOne(lastArgsPrompt, &res) + if err != nil { + signalChan <- nil + } + if res == AnswerYes { + cmd.SetArgs(strings.Split(lastArgs, " ")) //needs validation + cmd.Execute() + os.Exit(1) + } + } + err := survey.AskOne(initialPrompt, &res) + + if err != nil { + signalChan <- nil + } + switch res { + case AnswerTunnelTypeHTTP: + err = survey.Ask(portPrompt, &exposePort) + if err != nil { + signalChan <- nil + } + arguments = []string{"http", strconv.Itoa(exposePort)} + case AnswerTunnelTypePath: + err = survey.Ask(pathPrompt, &exposePath) + if err != nil { + signalChan <- nil + } + arguments = []string{"path", exposePath} + case AnswerTunnelTypeWebDAV: + err = survey.Ask(pathPrompt, &exposePath) + if err != nil { + signalChan <- nil + } + arguments = []string{"webdav", exposePath} + } + + hostname := askHostname(signalChan) + if hostname != "" { + arguments = append(arguments, "--hostname", hostname) + } + basicAuth := askBasicAuth(signalChan) + if basicAuth != "" { + arguments = append(arguments, "--basic-auth-username", basicAuth) + } + cmd.SetArgs(arguments) + + var argumentsWithQuotes []string + //setting the path argument in code doesn't work when it contains quotation marks, + //but they do need to be there when entered as a standalone command in a command line if the path contains spaces + //so, we give a copy of the arguments to the closehandler, with the path in quotation marks, where necessary + if strings.Contains(exposePath, " ") { + for i := 0; i < len(arguments); i++ { + if arguments[i] == exposePath { + argumentsWithQuotes = append(argumentsWithQuotes, fmt.Sprintf("'%s'", exposePath)) + } else { + argumentsWithQuotes = append(argumentsWithQuotes, arguments[i]) + } + } + closehandler.SaveArguments(argumentsWithQuotes) + } else { + closehandler.SaveArguments(arguments) + } + cmd.Execute() +} From 4aec5ff16ea95610f63bed3d202d21c03104e3e3 Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 24 Jun 2022 12:28:18 +0200 Subject: [PATCH 11/12] Tidy up go.mod --- go.mod | 2 ++ go.sum | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cce6e2a..ce64ce0 100644 --- a/go.mod +++ b/go.mod @@ -29,8 +29,10 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/fatih/color v1.7.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kr/pretty v0.2.0 // indirect github.com/mattn/go-isatty v0.0.8 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect golang.org/x/image v0.5.0 // indirect diff --git a/go.sum b/go.sum index ae994a2..c6b79b2 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,7 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -40,9 +41,6 @@ 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/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= -github.com/gobuffalo/here v0.6.2 h1:ZtCqC7F9ou3moLbYfHM1Tj+gwHGgWhjyRjVjsir9BE0= -github.com/gobuffalo/here v0.6.2/go.mod h1:D75Sq0p2BVHdgQu3vCRsXbg85rx943V19urJpqAVWjI= 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= @@ -140,6 +138,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= From ea26b49300307a02d7ec285da2a6a320f03a8632 Mon Sep 17 00:00:00 2001 From: Denny Date: Thu, 13 Apr 2023 09:41:35 +0200 Subject: [PATCH 12/12] Change exit code to 1 for errors --- internal/pkg/prompts/prompts.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/prompts/prompts.go b/internal/pkg/prompts/prompts.go index fa40477..de612b5 100644 --- a/internal/pkg/prompts/prompts.go +++ b/internal/pkg/prompts/prompts.go @@ -105,7 +105,7 @@ func askBasicAuth(signalChan chan os.Signal) string { if res == AnswerYes { err = survey.Ask(usernamePrompt, &res) if err != nil { - os.Exit(0) + os.Exit(1) return err.Error() } } else { @@ -141,7 +141,7 @@ func askHostname(signalChan chan os.Signal) string { if res == AnswerYes { err = survey.Ask(hostnamePrompt, &res) if err != nil { - os.Exit(0) + os.Exit(1) return err.Error() } } else {