lots o updates

- moved mcr code to package
- added the save, run, and view commands
- commands can now be saved
- saved commands support placeholders
This commit is contained in:
jake 2025-04-17 11:20:39 -04:00
parent bef3521770
commit f943639a88
6 changed files with 136 additions and 107 deletions

59
client/mcr.go Normal file
View File

@ -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)
}

View File

@ -16,14 +16,15 @@ import (
) )
var ( var (
server string cfgserver string
port int cfgport int
) )
// configCmd represents the config command // configCmd represents the config command
var configCmd = &cobra.Command{ var configCmd = &cobra.Command{
Use: "config", Use: "config",
Short: "Create and populate config file", 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 Long: `Creates the .mctl file in the user home directory
populating it with the server address, rcon password, and populating it with the server address, rcon password, and
rcon port to be pulled when using Login command`, 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())) ps, err := term.ReadPassword(int(os.Stdin.Fd()))
cobra.CheckErr(err) cobra.CheckErr(err)
//setup aes encrypter
// block, err := aes.NewCipher([]byte(viper.Get("device").(string)))
// cobra.CheckErr(err)
//generate and apply random nonce //generate and apply random nonce
nonce := make([]byte, 12) nonce := make([]byte, 12)
_, err = io.ReadFull(rand.Reader, nonce) _, err = io.ReadFull(rand.Reader, nonce)
cobra.CheckErr(err) cobra.CheckErr(err)
viper.Set("nonce", string(nonce)) viper.Set("nonce", string(nonce))
// aesg, err := cipher.NewGCM(block)
// cobra.CheckErr(err)
// //encrypt rcon password //encrypt password
// ciphert := aesg.Seal(nil, nonce, ps, nil)
ciphert, err := cryptography.EncryptPassword(ps) ciphert, err := cryptography.EncryptPassword(ps)
cobra.CheckErr(err) cobra.CheckErr(err)
//update config file with new values //update config file with new values
viper.Set("server", server) viper.Set("server", cfgserver)
viper.Set("password", string(ciphert)) viper.Set("password", string(ciphert))
viper.Set("port", port) viper.Set("port", cfgport)
viper.WriteConfig() viper.WriteConfig()
fmt.Println() fmt.Println()
fmt.Println("Config file updated!") fmt.Println("Config file updated!")
@ -61,13 +56,14 @@ var configCmd = &cobra.Command{
func init() { func init() {
initConfig() initConfig()
configCmd.Flags().StringVarP(&server, "server", "s", "", "server address") configCmd.Flags().StringVarP(&cfgserver, "server", "s", "", "server address")
configCmd.MarkFlagRequired("server") 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") configCmd.MarkFlagRequired("port")
rootCmd.AddCommand(configCmd) rootCmd.AddCommand(configCmd)
} }
// init config sets viper config and checks for config file, creating it if it doesn't exist
func initConfig() { func initConfig() {
home, err := os.UserHomeDir() home, err := os.UserHomeDir()
cobra.CheckErr(err) cobra.CheckErr(err)
@ -75,15 +71,14 @@ func initConfig() {
viper.AddConfigPath(home) viper.AddConfigPath(home)
viper.SetConfigType("yaml") viper.SetConfigType("yaml")
viper.SetConfigName(".mctl") viper.SetConfigName(".mctl")
viper.AutomaticEnv() viper.AutomaticEnv()
viper.ReadInConfig() viper.ReadInConfig()
if err := viper.ReadInConfig(); err != nil { if err := viper.ReadInConfig(); err != nil {
//file does not exist, create it //file does not exist, create it
viper.Set("server", server) viper.Set("server", cfgserver)
viper.Set("password", "") viper.Set("password", "")
viper.Set("port", port) viper.Set("port", cfgport)
viper.Set("nonce", "") viper.Set("nonce", "")
//generate psuedo-random key //generate psuedo-random key

View File

@ -8,43 +8,22 @@ import (
"fmt" "fmt"
"os" "os"
"code.jakeyoungdev.com/jake/mctl/cryptography" "code.jakeyoungdev.com/jake/mctl/client"
"github.com/jake-young-dev/mcr"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
// loginCmd represents the login command // loginCmd represents the login command
var loginCmd = &cobra.Command{ var loginCmd = &cobra.Command{
Use: "login", Use: "login",
Short: "Login to server and send commands", Example: "mctl login",
Short: "Login to server and send commands",
Long: `Login to server using saved config and enter command loop Long: `Login to server using saved config and enter command loop
sending commands to server and printing the response.`, sending commands to server and printing the response.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
//grab saved credentials //grab saved credentials
server := viper.Get("server") server := viper.Get("server").(string)
password := viper.Get("password") cli, err := client.New()
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))
cobra.CheckErr(err) cobra.CheckErr(err)
defer cli.Close() defer cli.Close()
@ -62,6 +41,7 @@ var loginCmd = &cobra.Command{
continue continue
} }
//mctl exits command terminal
if runningCmd == "mctl" { if runningCmd == "mctl" {
break break
} }

View File

@ -1,50 +1,64 @@
/* /*
Copyright © 2025 NAME HERE <EMAIL ADDRESS> Copyright © 2025 Jake jake.young.dev@gmail.com
*/ */
package cmd package cmd
import ( import (
"errors" "errors"
"fmt"
"strings"
"code.jakeyoungdev.com/jake/mctl/client"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
) )
// runCmd represents the run command // runCmd represents the run command
var runCmd = &cobra.Command{ var runCmd = &cobra.Command{
Use: "run", Use: "run <name> args...",
Short: "A brief description of your command", Example: "mctl run savedcmd 63 jake",
Long: `A longer description that spans multiple lines and likely contains examples Short: "Runs a previously saved command with supplied arguments on remote server",
and usage of using your command. For example: Long: `Loads a saved command, injects the supplied arguments into the command, and sends the command to the remove server
printing the response`,
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.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// fmt.Println(viper.Get(runname)) //grab saved command
// server := viper.Get("server") cmdName := viper.Get(fmt.Sprintf("customCmd-%s", args[0])).(string)
// r := viper.Get(args[0]) //convert arguments to interface
// cli, err := mcr.NewClient() 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 { 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") 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 return nil
}, },
} }
func init() { func init() {
rootCmd.AddCommand(runCmd) 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")
} }

View File

@ -1,10 +1,11 @@
/* /*
Copyright © 2025 NAME HERE <EMAIL ADDRESS> Copyright © 2025 Jake jake.young.dev@gmail.com
*/ */
package cmd package cmd
import ( import (
"bufio" "bufio"
"errors"
"fmt" "fmt"
"os" "os"
@ -12,56 +13,36 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
var (
name string
// cmd string
)
// saveCmd represents the save command // saveCmd represents the save command
var saveCmd = &cobra.Command{ var saveCmd = &cobra.Command{
Use: "save", Use: "save <name>",
Short: "A brief description of your command", Example: "mctl save newcmd",
Long: `A longer description that spans multiple lines and likely contains examples Short: "Saves a server command under an alias for quick execution",
and usage of using your command. For example: Long: `Saves supplied command using alias <name> 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`,
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.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// fmt.Println("save called") fmt.Printf("Use %s as a wildcard in your command\n", "%s") //this is ugly, have to use printf to stop issues with compiler
// 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("Command: ") fmt.Printf("Command: ")
sc := bufio.NewScanner(os.Stdin) sc := bufio.NewScanner(os.Stdin)
if sc.Scan() { if sc.Scan() {
txt := sc.Text() txt := sc.Text()
if txt != "" { if txt != "" {
viper.Set(name, txt) viper.Set(fmt.Sprintf("customCmd-%s", args[0]), txt)
viper.WriteConfig() viper.WriteConfig()
fmt.Println("\nSaved!") fmt.Println("\nSaved!")
return return
} }
} }
}, },
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("name argument is required")
}
return nil
},
} }
func init() { func init() {
rootCmd.AddCommand(saveCmd) 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")
} }

View File

@ -1,5 +1,5 @@
/* /*
Copyright © 2025 NAME HERE <EMAIL ADDRESS> Copyright © 2025 Jake jake.young.dev@gmail.com
*/ */
package cmd package cmd