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,13 +16,14 @@ 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",
Example: "mctl config -s x.x.x.x -p 61695",
Short: "Create and populate config file", 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
@ -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,8 +8,7 @@ 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"
) )
@ -17,34 +16,14 @@ import (
// loginCmd represents the login command // loginCmd represents the login command
var loginCmd = &cobra.Command{ var loginCmd = &cobra.Command{
Use: "login", Use: "login",
Example: "mctl login",
Short: "Login to server and send commands", 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