From f943639a886f8815d7a05b9318a9b98313a4f363 Mon Sep 17 00:00:00 2001 From: jake Date: Thu, 17 Apr 2025 11:20:39 -0400 Subject: [PATCH] lots o updates - moved mcr code to package - added the save, run, and view commands - commands can now be saved - saved commands support placeholders --- client/mcr.go | 59 +++++++++++++++++++++++++++++++++++++++++++++ cmd/config.go | 31 ++++++++++-------------- cmd/login.go | 34 ++++++-------------------- cmd/run.go | 66 +++++++++++++++++++++++++++++++-------------------- cmd/save.go | 51 +++++++++++++-------------------------- cmd/view.go | 2 +- 6 files changed, 136 insertions(+), 107 deletions(-) create mode 100644 client/mcr.go diff --git a/client/mcr.go b/client/mcr.go new file mode 100644 index 0000000..4ddd754 --- /dev/null +++ b/client/mcr.go @@ -0,0 +1,59 @@ +package client + +import ( + "fmt" + + "code.jakeyoungdev.com/jake/mctl/cryptography" + "github.com/jake-young-dev/mcr" + "github.com/spf13/viper" +) + +/* + This is a simple wrapper for the MCR client to provide easy use of mcr without having to manually + decrypt the password/hit viper each time. +*/ + +type Client struct { + cli *mcr.Client +} + +type IClient interface { + Close() + Command(cmd string) (string, error) +} + +// creates a new mcr client using saved credentials and decrypted password +func New() (*Client, error) { + //grab saved credentials + server := viper.Get("server").(string) + password := viper.Get("password").(string) + port := viper.Get("port").(int) + fmt.Printf("Logging into %s on port %d\n", server, port) + + //decrypt password + pt, err := cryptography.DecryptPassword([]byte(password)) + if err != nil { + return nil, err + } + + //connect to game server + cli := mcr.NewClient(server, mcr.WithPort(port)) + err = cli.Connect(string(pt)) + if err != nil { + return nil, err + } + + return &Client{ + cli: cli, + }, nil +} + +// closes client connection +func (c *Client) Close() error { + return c.cli.Close() +} + +// sends command to server, only exists to prevent exposing cli field +func (c *Client) Command(cmd string) (string, error) { + return c.cli.Command(cmd) +} diff --git a/cmd/config.go b/cmd/config.go index 90d331f..7657368 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -16,14 +16,15 @@ import ( ) var ( - server string - port int + cfgserver string + cfgport int ) // configCmd represents the config command var configCmd = &cobra.Command{ - Use: "config", - Short: "Create and populate config file", + Use: "config", + Example: "mctl config -s x.x.x.x -p 61695", + Short: "Create and populate config file", Long: `Creates the .mctl file in the user home directory populating it with the server address, rcon password, and rcon port to be pulled when using Login command`, @@ -33,26 +34,20 @@ var configCmd = &cobra.Command{ ps, err := term.ReadPassword(int(os.Stdin.Fd())) cobra.CheckErr(err) - //setup aes encrypter - // block, err := aes.NewCipher([]byte(viper.Get("device").(string))) - // cobra.CheckErr(err) //generate and apply random nonce nonce := make([]byte, 12) _, err = io.ReadFull(rand.Reader, nonce) cobra.CheckErr(err) viper.Set("nonce", string(nonce)) - // aesg, err := cipher.NewGCM(block) - // cobra.CheckErr(err) - // //encrypt rcon password - // ciphert := aesg.Seal(nil, nonce, ps, nil) + //encrypt password ciphert, err := cryptography.EncryptPassword(ps) cobra.CheckErr(err) //update config file with new values - viper.Set("server", server) + viper.Set("server", cfgserver) viper.Set("password", string(ciphert)) - viper.Set("port", port) + viper.Set("port", cfgport) viper.WriteConfig() fmt.Println() fmt.Println("Config file updated!") @@ -61,13 +56,14 @@ var configCmd = &cobra.Command{ func init() { initConfig() - configCmd.Flags().StringVarP(&server, "server", "s", "", "server address") + configCmd.Flags().StringVarP(&cfgserver, "server", "s", "", "server address") configCmd.MarkFlagRequired("server") - configCmd.Flags().IntVarP(&port, "port", "p", 0, "server rcon port") + configCmd.Flags().IntVarP(&cfgport, "port", "p", 0, "server rcon port") configCmd.MarkFlagRequired("port") rootCmd.AddCommand(configCmd) } +// init config sets viper config and checks for config file, creating it if it doesn't exist func initConfig() { home, err := os.UserHomeDir() cobra.CheckErr(err) @@ -75,15 +71,14 @@ func initConfig() { viper.AddConfigPath(home) viper.SetConfigType("yaml") viper.SetConfigName(".mctl") - viper.AutomaticEnv() viper.ReadInConfig() if err := viper.ReadInConfig(); err != nil { //file does not exist, create it - viper.Set("server", server) + viper.Set("server", cfgserver) viper.Set("password", "") - viper.Set("port", port) + viper.Set("port", cfgport) viper.Set("nonce", "") //generate psuedo-random key diff --git a/cmd/login.go b/cmd/login.go index 7a68ab0..a0a5e97 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -8,43 +8,22 @@ import ( "fmt" "os" - "code.jakeyoungdev.com/jake/mctl/cryptography" - "github.com/jake-young-dev/mcr" + "code.jakeyoungdev.com/jake/mctl/client" "github.com/spf13/cobra" "github.com/spf13/viper" ) // loginCmd represents the login command var loginCmd = &cobra.Command{ - Use: "login", - Short: "Login to server and send commands", + Use: "login", + Example: "mctl login", + Short: "Login to server and send commands", Long: `Login to server using saved config and enter command loop sending commands to server and printing the response.`, Run: func(cmd *cobra.Command, args []string) { //grab saved credentials - server := viper.Get("server") - password := viper.Get("password") - port := viper.Get("port") - fmt.Printf("Logging into %s on port %d\n", server, port) - - //setup decrypter - // nonce := viper.Get("nonce") - // block, err := aes.NewCipher([]byte(viper.Get("device").(string))) - // cobra.CheckErr(err) - // aesg, err := cipher.NewGCM(block) - // cobra.CheckErr(err) - - // //decrypt password - pwd := []byte(password.(string)) - // nn := []byte(nonce.(string)) - // pt, err := aesg.Open(nil, nn, pwd, nil) - // cobra.CheckErr(err) - pt, err := cryptography.DecryptPassword(pwd) - cobra.CheckErr(err) - - //connect to game server - cli := mcr.NewClient(server.(string), mcr.WithPort(port.(int))) - err = cli.Connect(string(pt)) + server := viper.Get("server").(string) + cli, err := client.New() cobra.CheckErr(err) defer cli.Close() @@ -62,6 +41,7 @@ var loginCmd = &cobra.Command{ continue } + //mctl exits command terminal if runningCmd == "mctl" { break } diff --git a/cmd/run.go b/cmd/run.go index d91f60b..5c84158 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -1,50 +1,64 @@ /* -Copyright © 2025 NAME HERE +Copyright © 2025 Jake jake.young.dev@gmail.com */ package cmd import ( "errors" + "fmt" + "strings" + "code.jakeyoungdev.com/jake/mctl/client" "github.com/spf13/cobra" + "github.com/spf13/viper" ) // runCmd represents the run command var runCmd = &cobra.Command{ - Use: "run", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Use: "run args...", + Example: "mctl run savedcmd 63 jake", + Short: "Runs a previously saved command with supplied arguments on remote server", + Long: `Loads a saved command, injects the supplied arguments into the command, and sends the command to the remove server + printing the response`, Run: func(cmd *cobra.Command, args []string) { - // fmt.Println(viper.Get(runname)) - // server := viper.Get("server") - // r := viper.Get(args[0]) - // cli, err := mcr.NewClient() + //grab saved command + cmdName := viper.Get(fmt.Sprintf("customCmd-%s", args[0])).(string) + //convert arguments to interface + var nargs []any + for _, a := range args[1:] { + nargs = append(nargs, a) + } + + //inject arguments + fixed := fmt.Sprintf(cmdName, nargs...) + fmt.Printf("Running saved command %s\n", fixed) + + //create client and send command + cli, err := client.New() + cobra.CheckErr(err) + defer cli.Close() + + res, err := cli.Command(fixed) + cobra.CheckErr(err) + + fmt.Println(res) }, PreRunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { + //ensure we have a command name + al := len(args) + if al == 0 { return errors.New("name argument is required") } + cmdCheck := viper.Get(fmt.Sprintf("customCmd-%s", args[0])) + count := strings.Count(cmdCheck.(string), "%s") + //make sure enough arguments are sent to fill command placeholders + if al < count+1 { + return fmt.Errorf("not enough arguments to populate command. Supplied: %d, Needed: %d", al-1, count) + } return nil }, } func init() { rootCmd.AddCommand(runCmd) - // runCmd.Flags().StringVar(&runname, "name", "", "") - // runCmd.MarkFlagRequired("name") - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // runCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // runCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/cmd/save.go b/cmd/save.go index af18c45..24fff42 100644 --- a/cmd/save.go +++ b/cmd/save.go @@ -1,10 +1,11 @@ /* -Copyright © 2025 NAME HERE +Copyright © 2025 Jake jake.young.dev@gmail.com */ package cmd import ( "bufio" + "errors" "fmt" "os" @@ -12,56 +13,36 @@ import ( "github.com/spf13/viper" ) -var ( - name string - // cmd string -) - // saveCmd represents the save command var saveCmd = &cobra.Command{ - Use: "save", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Use: "save ", + Example: "mctl save newcmd", + Short: "Saves a server command under an alias for quick execution", + Long: `Saves supplied command using alias to allow the command to be executed using the run command. The %s placeholder can be + used as a wildcard to be injected when running the command`, Run: func(cmd *cobra.Command, args []string) { - // fmt.Println("save called") - // viper.Set("testcmd", "im doin stuff and testing a command idea ' idk") - // viper.WriteConfig() - // fmt.Println(viper.Get("testcmd")) - // fmt.Println(name) - // fmt.Println(cmd) + fmt.Printf("Use %s as a wildcard in your command\n", "%s") //this is ugly, have to use printf to stop issues with compiler fmt.Printf("Command: ") sc := bufio.NewScanner(os.Stdin) if sc.Scan() { txt := sc.Text() if txt != "" { - viper.Set(name, txt) + viper.Set(fmt.Sprintf("customCmd-%s", args[0]), txt) viper.WriteConfig() fmt.Println("\nSaved!") return } } }, + PreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("name argument is required") + } + + return nil + }, } func init() { rootCmd.AddCommand(saveCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // saveCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // saveCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") - saveCmd.Flags().StringVar(&name, "name", "", "") - saveCmd.MarkFlagRequired("name") - // saveCmd.Flags().StringVar(&cmd, "command", "", "") - // saveCmd.MarkFlagRequired("command") } diff --git a/cmd/view.go b/cmd/view.go index 07a095f..260f8d4 100644 --- a/cmd/view.go +++ b/cmd/view.go @@ -1,5 +1,5 @@ /* -Copyright © 2025 NAME HERE +Copyright © 2025 Jake jake.young.dev@gmail.com */ package cmd