From d05829c123e68b9c74b836be1f72392e670fa59f Mon Sep 17 00:00:00 2001 From: Jeroen Wijenbergh Date: Sat, 15 Feb 2025 22:17:32 +0100 Subject: CLI: Move to cmd/eduvpn-cli --- Makefile | 2 +- cmd/cli/main.go | 182 --------------------------------------------- cmd/eduvpn-cli/main.go | 182 +++++++++++++++++++++++++++++++++++++++++++++ docs/md/building-client.md | 12 +-- 4 files changed, 185 insertions(+), 193 deletions(-) delete mode 100644 cmd/cli/main.go create mode 100644 cmd/eduvpn-cli/main.go diff --git a/Makefile b/Makefile index a64b72b..350f0bc 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ lint: golangci-lint run -E stylecheck,revive,gocritic ./... cli: - go build -o eduvpn-common-cli ./cmd/cli + go build ./cmd/eduvpn-cli docs: mkdocs build -f docs/mkdocs.yml diff --git a/cmd/cli/main.go b/cmd/cli/main.go deleted file mode 100644 index 263f55c..0000000 --- a/cmd/cli/main.go +++ /dev/null @@ -1,182 +0,0 @@ -// Package main implements an example CLI client -package main - -import ( - "context" - "flag" - "fmt" - "os" - "reflect" - "strings" - - "codeberg.org/eduVPN/eduvpn-common/client" - "codeberg.org/eduVPN/eduvpn-common/internal/version" - "codeberg.org/eduVPN/eduvpn-common/types/cookie" - srvtypes "codeberg.org/eduVPN/eduvpn-common/types/server" - "codeberg.org/eduVPN/eduvpn-common/util" - - "github.com/pkg/browser" -) - -// Open a browser with xdg-open. -func openBrowser(data interface{}) { - str, ok := data.(string) - if !ok { - return - } - fmt.Printf("OAuth: Authorization URL: %s\n", str) - fmt.Println("Opening browser...") - go func() { - err := browser.OpenURL(str) - if err != nil { - fmt.Fprintln(os.Stderr, "failed to open browser with error:", err) - fmt.Println("Please open your browser manually") - } - }() -} - -// Ask for a profile in the command line. -func sendProfile(state *client.Client, data interface{}) { - fmt.Printf("Multiple VPN profiles found. Please select a profile by entering e.g. 1") - d, ok := data.(*srvtypes.RequiredAskTransition) - if !ok { - fmt.Fprintf(os.Stderr, "\ninvalid data type: %v\n", reflect.TypeOf(data)) - return - } - sps, ok := d.Data.(*srvtypes.Profiles) - if !ok { - fmt.Fprintf(os.Stderr, "\ninvalid data type for profiles: %v\n", reflect.TypeOf(d.Data)) - return - } - - ps := "" - var options []string - i := 0 - for k, v := range sps.Map { - ps += fmt.Sprintf("\n%d - %s", i+1, util.GetLanguageMatched(v.DisplayName, "en")) - options = append(options, k) - i++ - } - - // Show the profiles - fmt.Println(ps) - - var idx int - if _, err := fmt.Scanf("%d", &idx); err != nil || idx <= 0 || - idx > len(sps.Map) { - fmt.Fprintln(os.Stderr, "invalid profile chosen, please retry") - sendProfile(state, data) - return - } - - p := options[idx-1] - fmt.Println("Sending profile ID", p) - if err := d.C.Send(p); err != nil { - fmt.Fprintln(os.Stderr, "failed setting profile with error", err) - } -} - -// The callback function -// If OAuth is started we open the browser with the Auth URL -// If we ask for a profile, we send the profile using command line input -// Note that this has an additional argument, the vpn state which was wrapped into this callback function below. -func stateCallback(state *client.Client, _ client.FSMStateID, newState client.FSMStateID, data interface{}) { - if newState == client.StateOAuthStarted { - openBrowser(data) - } - - if newState == client.StateAskProfile { - sendProfile(state, data) - } - - if newState == client.StateAskLocation { - fmt.Fprint(os.Stderr, "An invalid secure location is stored. This CLI doesn't support interactively choosing a location yet. Give a correct location with the -country-code flag") - os.Exit(1) - } -} - -// Get a config for Institute Access or Secure Internet Server. -func getConfig(state *client.Client, url string, srvType srvtypes.Type, cc string) (*srvtypes.Configuration, error) { - if !strings.HasPrefix(url, "http") { - url = "https://" + url - } - ck := cookie.NewWithContext(context.Background()) - defer ck.Cancel() //nolint:errcheck - err := state.AddServer(ck, url, srvType, nil) - if err != nil { - // TODO: This is quite hacky :^) - if !strings.Contains(err.Error(), "a secure internet server already exists.") { - return nil, err - } - } - if cc != "" { - err = state.SetSecureLocation(url, cc) - if err != nil { - return nil, err - } - } - - return state.GetConfig(ck, url, srvType, false, false) -} - -// Get a config for a single server, Institute Access or Secure Internet. -func printConfig(url string, cc string, srvType srvtypes.Type, debug bool) { - var c *client.Client - c, err := client.New( - "org.eduvpn.app.linux", - fmt.Sprintf("%s-cli", version.Version), - "configs", - func(oldState client.FSMStateID, newState client.FSMStateID, data interface{}) bool { - stateCallback(c, oldState, newState, data) - return true - }, - debug, - ) - if err != nil { - fmt.Printf("Register error: %v", err) - return - } - _ = c.Register() - - ck := cookie.NewWithContext(context.Background()) - _, err = c.DiscoOrganizations(ck, "") - if err != nil { - panic(err) - } - _, err = c.DiscoServers(ck, "") - if err != nil { - panic(err) - } - - defer c.Deregister() - - cfg, err := getConfig(c, url, srvType, cc) - if err != nil { - fmt.Fprintf(os.Stderr, "failed getting a config: %v\n", err) - return - } - fmt.Printf("Obtained config:\n%s\n", cfg.VPNConfig) -} - -// The main function -// It parses the arguments and executes the correct functions. -func main() { - cu := flag.String("get-custom", "", "The url of a custom server to connect to") - u := flag.String("get-institute", "", "The url of an institute to connect to") - sec := flag.String("get-secure", "", "Gets secure internet servers") - cc := flag.String("country-code", "", "The country code to use in case of a secure internet server") - debug := flag.Bool("debug", false, "Whether or not to enable debugging") - flag.Parse() - - // Connect to a VPN by getting an Institute Access config - switch { - case *cu != "": - printConfig(*cu, "", srvtypes.TypeCustom, *debug) - case *u != "": - printConfig(*u, "", srvtypes.TypeInstituteAccess, *debug) - case *sec != "": - printConfig(*sec, *cc, srvtypes.TypeSecureInternet, *debug) - default: - flag.PrintDefaults() - } -} diff --git a/cmd/eduvpn-cli/main.go b/cmd/eduvpn-cli/main.go new file mode 100644 index 0000000..263f55c --- /dev/null +++ b/cmd/eduvpn-cli/main.go @@ -0,0 +1,182 @@ +// Package main implements an example CLI client +package main + +import ( + "context" + "flag" + "fmt" + "os" + "reflect" + "strings" + + "codeberg.org/eduVPN/eduvpn-common/client" + "codeberg.org/eduVPN/eduvpn-common/internal/version" + "codeberg.org/eduVPN/eduvpn-common/types/cookie" + srvtypes "codeberg.org/eduVPN/eduvpn-common/types/server" + "codeberg.org/eduVPN/eduvpn-common/util" + + "github.com/pkg/browser" +) + +// Open a browser with xdg-open. +func openBrowser(data interface{}) { + str, ok := data.(string) + if !ok { + return + } + fmt.Printf("OAuth: Authorization URL: %s\n", str) + fmt.Println("Opening browser...") + go func() { + err := browser.OpenURL(str) + if err != nil { + fmt.Fprintln(os.Stderr, "failed to open browser with error:", err) + fmt.Println("Please open your browser manually") + } + }() +} + +// Ask for a profile in the command line. +func sendProfile(state *client.Client, data interface{}) { + fmt.Printf("Multiple VPN profiles found. Please select a profile by entering e.g. 1") + d, ok := data.(*srvtypes.RequiredAskTransition) + if !ok { + fmt.Fprintf(os.Stderr, "\ninvalid data type: %v\n", reflect.TypeOf(data)) + return + } + sps, ok := d.Data.(*srvtypes.Profiles) + if !ok { + fmt.Fprintf(os.Stderr, "\ninvalid data type for profiles: %v\n", reflect.TypeOf(d.Data)) + return + } + + ps := "" + var options []string + i := 0 + for k, v := range sps.Map { + ps += fmt.Sprintf("\n%d - %s", i+1, util.GetLanguageMatched(v.DisplayName, "en")) + options = append(options, k) + i++ + } + + // Show the profiles + fmt.Println(ps) + + var idx int + if _, err := fmt.Scanf("%d", &idx); err != nil || idx <= 0 || + idx > len(sps.Map) { + fmt.Fprintln(os.Stderr, "invalid profile chosen, please retry") + sendProfile(state, data) + return + } + + p := options[idx-1] + fmt.Println("Sending profile ID", p) + if err := d.C.Send(p); err != nil { + fmt.Fprintln(os.Stderr, "failed setting profile with error", err) + } +} + +// The callback function +// If OAuth is started we open the browser with the Auth URL +// If we ask for a profile, we send the profile using command line input +// Note that this has an additional argument, the vpn state which was wrapped into this callback function below. +func stateCallback(state *client.Client, _ client.FSMStateID, newState client.FSMStateID, data interface{}) { + if newState == client.StateOAuthStarted { + openBrowser(data) + } + + if newState == client.StateAskProfile { + sendProfile(state, data) + } + + if newState == client.StateAskLocation { + fmt.Fprint(os.Stderr, "An invalid secure location is stored. This CLI doesn't support interactively choosing a location yet. Give a correct location with the -country-code flag") + os.Exit(1) + } +} + +// Get a config for Institute Access or Secure Internet Server. +func getConfig(state *client.Client, url string, srvType srvtypes.Type, cc string) (*srvtypes.Configuration, error) { + if !strings.HasPrefix(url, "http") { + url = "https://" + url + } + ck := cookie.NewWithContext(context.Background()) + defer ck.Cancel() //nolint:errcheck + err := state.AddServer(ck, url, srvType, nil) + if err != nil { + // TODO: This is quite hacky :^) + if !strings.Contains(err.Error(), "a secure internet server already exists.") { + return nil, err + } + } + if cc != "" { + err = state.SetSecureLocation(url, cc) + if err != nil { + return nil, err + } + } + + return state.GetConfig(ck, url, srvType, false, false) +} + +// Get a config for a single server, Institute Access or Secure Internet. +func printConfig(url string, cc string, srvType srvtypes.Type, debug bool) { + var c *client.Client + c, err := client.New( + "org.eduvpn.app.linux", + fmt.Sprintf("%s-cli", version.Version), + "configs", + func(oldState client.FSMStateID, newState client.FSMStateID, data interface{}) bool { + stateCallback(c, oldState, newState, data) + return true + }, + debug, + ) + if err != nil { + fmt.Printf("Register error: %v", err) + return + } + _ = c.Register() + + ck := cookie.NewWithContext(context.Background()) + _, err = c.DiscoOrganizations(ck, "") + if err != nil { + panic(err) + } + _, err = c.DiscoServers(ck, "") + if err != nil { + panic(err) + } + + defer c.Deregister() + + cfg, err := getConfig(c, url, srvType, cc) + if err != nil { + fmt.Fprintf(os.Stderr, "failed getting a config: %v\n", err) + return + } + fmt.Printf("Obtained config:\n%s\n", cfg.VPNConfig) +} + +// The main function +// It parses the arguments and executes the correct functions. +func main() { + cu := flag.String("get-custom", "", "The url of a custom server to connect to") + u := flag.String("get-institute", "", "The url of an institute to connect to") + sec := flag.String("get-secure", "", "Gets secure internet servers") + cc := flag.String("country-code", "", "The country code to use in case of a secure internet server") + debug := flag.Bool("debug", false, "Whether or not to enable debugging") + flag.Parse() + + // Connect to a VPN by getting an Institute Access config + switch { + case *cu != "": + printConfig(*cu, "", srvtypes.TypeCustom, *debug) + case *u != "": + printConfig(*u, "", srvtypes.TypeInstituteAccess, *debug) + case *sec != "": + printConfig(*sec, *cc, srvtypes.TypeSecureInternet, *debug) + default: + flag.PrintDefaults() + } +} diff --git a/docs/md/building-client.md b/docs/md/building-client.md index 77c4eaf..6eaf116 100644 --- a/docs/md/building-client.md +++ b/docs/md/building-client.md @@ -72,16 +72,8 @@ The rest of the states are miscellaneous states, meaning that the client can han This chapter contains code examples that use the API ### Go command line client -The following is an example [in the repository](https://codeberg.org/eduVPN/eduvpn-common/src/branch/main/cmd/cli/main.go). It is a command line client with the following flags -``` - -get-custom string - The url of a custom server to connect to - -get-institute string - The url of an institute to connect to - -get-secure string - Gets secure internet servers -``` +The following is an example [in the repository](https://codeberg.org/eduVPN/eduvpn-common/src/branch/main/cmd/eduvpn-cli/main.go). It is a command line client. ```go -{{!cmd/cli/main.go!}} +{{!cmd/eduvpn-cli/main.go!}} ``` -- cgit v1.2.3