From fe37cac2da8b52d2eaf597ff567d929c74aadfb6 Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 17 Jun 2025 23:12:49 -0400 Subject: [PATCH] lots o progress - added 'command' subcommand - removed viper - setup commands added - still a WIP - readme TODO update --- README.md | 53 +++++++++---------- client/mcr.go | 3 +- cmd/clear.go | 32 ------------ cmd/command/add.go | 50 ++++++++++++++++++ cmd/command/command.go | 22 ++++++++ cmd/{ => command}/delete.go | 19 ++++--- cmd/command/view.go | 35 +++++++++++++ cmd/config.go | 65 ----------------------- cmd/destroy.go | 32 ++++++++++++ cmd/init.go | 30 +++++++++++ cmd/login.go | 55 ++++++------------- cmd/root.go | 4 ++ cmd/run.go | 87 ------------------------------- cmd/save.go | 51 ------------------ cmd/server/add.go | 70 +++++++++++++++++++++++++ cmd/server/delete.go | 36 +++++++++++++ cmd/server/server.go | 21 ++++++++ cmd/server/update.go | 48 +++++++++++++++++ cmd/server/view.go | 37 +++++++++++++ cmd/view.go | 57 -------------------- cryptography/aes.go | 52 ------------------ database/{commands.go => sqlx.go} | 25 ++++++--- go.mod | 13 ----- go.sum | 44 ---------------- model/data.go | 3 -- 25 files changed, 456 insertions(+), 488 deletions(-) delete mode 100644 cmd/clear.go create mode 100644 cmd/command/add.go create mode 100644 cmd/command/command.go rename cmd/{ => command}/delete.go (59%) create mode 100644 cmd/command/view.go delete mode 100644 cmd/config.go create mode 100644 cmd/destroy.go create mode 100644 cmd/init.go delete mode 100644 cmd/run.go delete mode 100644 cmd/save.go create mode 100644 cmd/server/add.go create mode 100644 cmd/server/delete.go create mode 100644 cmd/server/server.go create mode 100644 cmd/server/update.go create mode 100644 cmd/server/view.go delete mode 100644 cmd/view.go delete mode 100644 cryptography/aes.go rename database/{commands.go => sqlx.go} (88%) diff --git a/README.md b/README.md index b93815c..c24324b 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ mctl is a terminal-friendly remote console client # Index 1. [Installation](#installation) -2. [Setup](#setup) -3. [Documentation](#documentation) +2. [Use](#use) 4. [Security](#security) 5. [Development](#development) @@ -13,18 +12,31 @@ mctl is a terminal-friendly remote console client # Installation Install mctl using golang ```bash -go install code.jakeyoungdev.com/jake/mctl@main #it is recommended to use a tagged version +go install code.jakeyoungdev.com/jake/mctl@main ```
-# Setup +# Use ### Configuring mctl -mctl requires a one-time setup via the 'config' command before interacting with any servers, password is entered securely from the terminal and encrypted +mctl requires a one-time setup via the 'init' command before using any other commands ```bash -mctl config -s -p +mctl init ``` +### Add a server +A + + + + + + + + + + + ### Connecting to server Once the client has been configured commands can be sent to the server using the 'login' command. This will authenticate you with the game server and enter the command loop, the session is logged out when the command loop is broken @@ -94,23 +106,6 @@ mctl clear
-# Documentation -### Commands -|Command|Description| -|---|---| -|config|used to update the config file| -|login|makes connection request to the server using saved configuration and enters command loop| -|save \|saves specific command for reuse| -|view \|displays saved command| -|delete \|deletes saved command| -|run \ args...|runs saved command filling placeholders with supplied args| -|clear|clears config file| - -### Configuration file -All configuration data will be kept in the home directory and any sensitive data is encrypted for added security - -
- # Security RCon is an inherently insecure protocol, passwords are sent in plaintext and, if possible, the port should not be exposed to the internet. It is best to keep these connections local or over a VPN. @@ -122,8 +117,10 @@ mctl utilizes [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck this repo is currently in development and may encounter breaking changes, use a tag to prevent any surprises # TODO -1. continue sqlite update -2. migrate the credentials to sqlite as well -3. login too -4. MCR client also has viper stuff, gut it -5. Since we are now using sqlite should we just encode the pwd and insert \ No newline at end of file +6. Do we use CheckErr or use RunE +7. update readme, the commands are all broken +9. do saved commands run on default server only? Yes, but add -s + mctl command run tp nutlift -s server +10. RUN COMMAND +11. Command descriptions, examples, and silence usage +12. lol "default" server functionality (which means setting which one w default command) \ No newline at end of file diff --git a/client/mcr.go b/client/mcr.go index e58db39..ea9452b 100644 --- a/client/mcr.go +++ b/client/mcr.go @@ -9,8 +9,7 @@ import ( ) /* - 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. + This is a simple wrapper for the MCR client to provide easy use of rcon */ type Client struct { diff --git a/cmd/clear.go b/cmd/clear.go deleted file mode 100644 index 692dc2e..0000000 --- a/cmd/clear.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright © 2025 Jake jake.young.dev@gmail.com -*/ -package cmd - -import ( - "fmt" - - "code.jakeyoungdev.com/jake/mctl/database" - "github.com/spf13/cobra" -) - -// clearCmd represents the clear command -var clearCmd = &cobra.Command{ - Use: "clear", - Short: "Clear config file", - Long: `Clears all configuration values for mctl. [WARNING] all server configuration will be lost`, - Run: func(cmd *cobra.Command, args []string) { - db, err := database.New() - cobra.CheckErr(err) - defer db.Close() - - err = db.Destroy() - cobra.CheckErr(err) - - fmt.Println("Configuration is cleared, the 'config' command must be run again.") - }, -} - -func init() { - rootCmd.AddCommand(clearCmd) -} diff --git a/cmd/command/add.go b/cmd/command/add.go new file mode 100644 index 0000000..f54080e --- /dev/null +++ b/cmd/command/add.go @@ -0,0 +1,50 @@ +/* +Copyright © 2025 Jake jake.young.dev@gmail.com +*/ +package command + +import ( + "bufio" + "fmt" + "os" + + "code.jakeyoungdev.com/jake/mctl/database" + "github.com/spf13/cobra" +) + +var addCmd = &cobra.Command{ + Use: "add", + Example: "mctl command add", + // Short: "Saves a new server configuration", + // Long: `Saves server address, alias, port, and password.`, + Run: func(cmd *cobra.Command, args []string) { + scanner := bufio.NewScanner(os.Stdin) + + //get server information + var cfgname string + fmt.Printf("Command alias: ") + if scanner.Scan() { + cfgname = scanner.Text() + } + + var cfgcmd string + fmt.Printf("Command: ") + if scanner.Scan() { + cfgcmd = scanner.Text() + } + + db, err := database.New() + cobra.CheckErr(err) + defer db.Close() + + err = db.Init() + cobra.CheckErr(err) + + err = db.SaveCmd(cfgname, cfgcmd) + cobra.CheckErr(err) + }, +} + +func init() { + CommandCmd.AddCommand(addCmd) +} diff --git a/cmd/command/command.go b/cmd/command/command.go new file mode 100644 index 0000000..a295960 --- /dev/null +++ b/cmd/command/command.go @@ -0,0 +1,22 @@ +/* +Copyright © 2025 Jake jake.young.dev@gmail.com +*/ +package command + +import ( + "github.com/spf13/cobra" +) + +// CommandCmd is such a cool name lol +var CommandCmd = &cobra.Command{ + Use: "command", + Example: "mctl command ", + // Short: "A remote console client", + // Long: `mctl is a terminal-friendly remote console client made to manage game servers.`, + // Version: "v0.3.4", + // Run: func(cmd *cobra.Command, args []string) { }, +} + +func init() { + // rootCmd.AddCommand() +} diff --git a/cmd/delete.go b/cmd/command/delete.go similarity index 59% rename from cmd/delete.go rename to cmd/command/delete.go index 0944d79..d6640e8 100644 --- a/cmd/delete.go +++ b/cmd/command/delete.go @@ -1,7 +1,7 @@ /* Copyright © 2025 Jake jake.young.dev@gmail.com */ -package cmd +package command import ( "errors" @@ -11,12 +11,11 @@ import ( "github.com/spf13/cobra" ) -// deleteCmd represents the delete command var deleteCmd = &cobra.Command{ - Use: "delete ", - Example: "mctl delete newcmd", - Short: "Delete a saved command", - Long: `Deletes a command stored using the save command`, + Use: "delete", + Example: "mctl command delete ", + // Short: "Deletes a server", + // Long: `Deletes server configuration`, Run: func(cmd *cobra.Command, args []string) { db, err := database.New() cobra.CheckErr(err) @@ -24,17 +23,17 @@ var deleteCmd = &cobra.Command{ err = db.DeleteCmd(args[0]) cobra.CheckErr(err) - fmt.Println("Command deleted") + + fmt.Println("Command deleted!") }, PreRunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - return errors.New("name argument is required") + return errors.New("name parameter missing") } - return nil }, } func init() { - rootCmd.AddCommand(deleteCmd) + CommandCmd.AddCommand(deleteCmd) } diff --git a/cmd/command/view.go b/cmd/command/view.go new file mode 100644 index 0000000..85fb091 --- /dev/null +++ b/cmd/command/view.go @@ -0,0 +1,35 @@ +/* +Copyright © 2025 Jake jake.young.dev@gmail.com +*/ +package command + +import ( + "fmt" + + "code.jakeyoungdev.com/jake/mctl/database" + "github.com/spf13/cobra" +) + +var viewCmd = &cobra.Command{ + Use: "view", + Example: "mctl command view", + // Short: "view all commands", + Run: func(cmd *cobra.Command, args []string) { + db, err := database.New() + cobra.CheckErr(err) + defer db.Close() + + ts, err := db.GetAllCmds() + cobra.CheckErr(err) + + for _, s := range ts { + fmt.Println("-----") + fmt.Printf("Name: %s\n", s.Name) + fmt.Printf("Command: %s\n", s.Command) + } + }, +} + +func init() { + CommandCmd.AddCommand(viewCmd) +} diff --git a/cmd/config.go b/cmd/config.go deleted file mode 100644 index d299d13..0000000 --- a/cmd/config.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Copyright © 2025 Jake jake.young.dev@gmail.com -*/ -package cmd - -import ( - "encoding/base64" - "fmt" - "os" - - "code.jakeyoungdev.com/jake/mctl/database" - "code.jakeyoungdev.com/jake/mctl/model" - "github.com/spf13/cobra" - "golang.org/x/term" -) - -var ( - cfgserver string - cfgport int - cfgname string -) - -// configCmd represents the config command -var configCmd = &cobra.Command{ - Use: "config", - Example: "mctl config -n serverAlias -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`, - Run: func(cmd *cobra.Command, args []string) { - //read in password using term to keep it secure/hidden from bash history - fmt.Printf("Password: ") - ps, err := term.ReadPassword(int(os.Stdin.Fd())) - cobra.CheckErr(err) - - db, err := database.New() - cobra.CheckErr(err) - defer db.Close() - - err = db.Init() - cobra.CheckErr(err) - - err = db.SaveServer(model.Server{ - Name: cfgname, - Server: cfgserver, - Port: cfgport, - Password: base64.StdEncoding.EncodeToString(ps), - }) - cobra.CheckErr(err) - }, -} - -func init() { - configCmd.Flags().StringVarP(&cfgserver, "server", "s", "", "server address") - err := configCmd.MarkFlagRequired("server") - cobra.CheckErr(err) - configCmd.Flags().IntVarP(&cfgport, "port", "p", 0, "server rcon port") - err = configCmd.MarkFlagRequired("port") - cobra.CheckErr(err) - configCmd.Flags().StringVarP(&cfgname, "name", "n", "", "server alias") - err = configCmd.MarkFlagRequired("name") - cobra.CheckErr(err) - rootCmd.AddCommand(configCmd) -} diff --git a/cmd/destroy.go b/cmd/destroy.go new file mode 100644 index 0000000..e574e29 --- /dev/null +++ b/cmd/destroy.go @@ -0,0 +1,32 @@ +/* +Copyright © 2025 Jake jake.young.dev@gmail.com +*/ +package cmd + +import ( + "fmt" + + "code.jakeyoungdev.com/jake/mctl/database" + "github.com/spf13/cobra" +) + +// destroyCmd represents the destroy command +var destroyCmd = &cobra.Command{ + Use: "destroy", + // Short: "Clear all configuration", + // Long: `Clears all configuration values for mctl. [WARNING] all server configuration will be lost`, + Run: func(cmd *cobra.Command, args []string) { + db, err := database.New() + cobra.CheckErr(err) + defer db.Close() + + err = db.Destroy() + cobra.CheckErr(err) + + fmt.Println("Configuration is cleared, the 'init' command must be run again.") + }, +} + +func init() { + rootCmd.AddCommand(destroyCmd) +} diff --git a/cmd/init.go b/cmd/init.go new file mode 100644 index 0000000..698a296 --- /dev/null +++ b/cmd/init.go @@ -0,0 +1,30 @@ +/* +Copyright © 2025 Jake jake.young.dev@gmail.com +*/ +package cmd + +import ( + "code.jakeyoungdev.com/jake/mctl/database" + "github.com/spf13/cobra" +) + +var initCmd = &cobra.Command{ + Use: "init", + Example: "mctl init", + SilenceUsage: true, + // Short: "Login to server and send commands", + // Long: `Login to default server and enter command loop. The default server + // is used unless the server flag is set`, + Run: func(cmd *cobra.Command, args []string) { + db, err := database.New() + cobra.CheckErr(err) + defer db.Close() + + err = db.Init() + cobra.CheckErr(err) + }, +} + +func init() { + rootCmd.AddCommand(loginCmd) +} diff --git a/cmd/login.go b/cmd/login.go index 33e78ea..8ae96ee 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -5,7 +5,6 @@ package cmd import ( "bufio" - "errors" "fmt" "os" @@ -15,19 +14,30 @@ import ( ) var ( - name string + server string ) // loginCmd represents the login command var loginCmd = &cobra.Command{ Use: "login", - Example: "mctl login ", + Example: "mctl login -s ", SilenceUsage: true, 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.`, + Long: `Login to default server and enter command loop. The default server + is used unless the server flag is set`, Run: func(cmd *cobra.Command, args []string) { - server := args[0] + db, err := database.New() + cobra.CheckErr(err) + defer db.Close() + + //get default server + if server == "" { + ds, err := db.GetServer("default") + cobra.CheckErr(err) + + server = ds.Name + } + cli, err := client.New(server) cobra.CheckErr(err) defer cli.Close() @@ -36,10 +46,6 @@ var loginCmd = &cobra.Command{ fmt.Println("Connected! Type 'mctl' to close") scanner := bufio.NewScanner(os.Stdin) - db, err := database.New() - cobra.CheckErr(err) - defer db.Close() - var runningCmd string for runningCmd != "mctl" { fmt.Printf("RCON@%s /> ", server) @@ -56,11 +62,6 @@ var loginCmd = &cobra.Command{ break } - if runningCmd == ".run" { - } - //hmm this gets weird af tbh - dbcmd, err := db.GetCmd(runningCmd) - res, err := cli.Command(runningCmd) cobra.CheckErr(err) fmt.Printf("\n%s\n", res) @@ -69,31 +70,9 @@ var loginCmd = &cobra.Command{ fmt.Printf("Disconnected from %s\n", server) }, - PreRunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return errors.New("must specify which server to login to") - } - srv := args[0] - - db, err := database.New() - if err != nil { - return err - } - defer db.Close() - - server, err := db.GetServer(srv) - if err != nil { - return err - } - - if server.Name == "" { - return fmt.Errorf("server %s not found", server.Name) - } - - return nil - }, } func init() { + loginCmd.Flags().StringVarP(&server, "server", "s", "", "server alias") rootCmd.AddCommand(loginCmd) } diff --git a/cmd/root.go b/cmd/root.go index 3431bea..166eecd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,6 +6,8 @@ package cmd import ( "os" + "code.jakeyoungdev.com/jake/mctl/cmd/command" + srv "code.jakeyoungdev.com/jake/mctl/cmd/server" "github.com/spf13/cobra" ) @@ -31,4 +33,6 @@ func init() { rootCmd.CompletionOptions = cobra.CompletionOptions{ DisableDefaultCmd: true, } + rootCmd.AddCommand(srv.ServerCmd) + rootCmd.AddCommand(command.CommandCmd) //the word command is in this four times, that can't be good. } diff --git a/cmd/run.go b/cmd/run.go deleted file mode 100644 index 4c33e2d..0000000 --- a/cmd/run.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright © 2025 Jake jake.young.dev@gmail.com -*/ -package cmd - -import ( - "database/sql" - "errors" - "fmt" - "strings" - - "code.jakeyoungdev.com/jake/mctl/client" - "code.jakeyoungdev.com/jake/mctl/database" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -// runCmd represents the run command -var runCmd = &cobra.Command{ - Use: "run args...", - Example: "mctl run savedcmd 63 jake", - SilenceUsage: true, - 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) { - db, err := database.New() - cobra.CheckErr(err) - defer db.Close() - - sc, err := db.GetCmd(args[0]) - if err == sql.ErrNoRows { - fmt.Printf("command %s not found", args[0]) - return - } - cobra.CheckErr(err) - - //convert arguments to interface - var nargs []any - for _, a := range args[1:] { - nargs = append(nargs, a) - } - - //inject arguments - fixed := fmt.Sprintf(sc, 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 { - //ensure config command has been run - if !viper.IsSet("server") || !viper.IsSet("password") || !viper.IsSet("port") { - return errors.New("the 'config' command must be run before you can interact with servers") - } - - if !viper.IsSet("customcmd") { - return errors.New("no saved commands to run") - } - - //ensure we have a command name - al := len(args) - if al == 0 { - return errors.New("name argument is required") - } - - cmdMap := viper.Get("customcmd").(map[string]any) - count := strings.Count(cmdMap[args[0]].(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) -} diff --git a/cmd/save.go b/cmd/save.go deleted file mode 100644 index 0cbc4d3..0000000 --- a/cmd/save.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright © 2025 Jake jake.young.dev@gmail.com -*/ -package cmd - -import ( - "bufio" - "errors" - "fmt" - "os" - - "code.jakeyoungdev.com/jake/mctl/database" - "github.com/spf13/cobra" -) - -// saveCmd represents the save command -var saveCmd = &cobra.Command{ - 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.Printf("Use %s as a wildcard in your command\n", "%s") //this is ugly - fmt.Printf("Command: ") - sc := bufio.NewScanner(os.Stdin) - if sc.Scan() { - txt := sc.Text() - if txt != "" { - db, err := database.New() - cobra.CheckErr(err) - defer db.Close() - - err = db.SaveCmd(args[0], txt) - cobra.CheckErr(err) - fmt.Println("\nSaved!") - } - } - }, - 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) -} diff --git a/cmd/server/add.go b/cmd/server/add.go new file mode 100644 index 0000000..8f7bbb6 --- /dev/null +++ b/cmd/server/add.go @@ -0,0 +1,70 @@ +/* +Copyright © 2025 Jake jake.young.dev@gmail.com +*/ +package server + +import ( + "bufio" + "encoding/base64" + "fmt" + "os" + "strconv" + + "code.jakeyoungdev.com/jake/mctl/database" + "code.jakeyoungdev.com/jake/mctl/model" + "github.com/spf13/cobra" + "golang.org/x/term" +) + +var addCmd = &cobra.Command{ + Use: "add", + Example: "mctl server add", + Short: "Saves a new server configuration", + Long: `Saves server address, alias, port, and password.`, + Run: func(cmd *cobra.Command, args []string) { + scanner := bufio.NewScanner(os.Stdin) + + //get server information + var cfgname string + fmt.Printf("Server alias: ") + if scanner.Scan() { + cfgname = scanner.Text() + } + + var cfgaddress string + fmt.Printf("Server address: ") + if scanner.Scan() { + cfgaddress = scanner.Text() + } + + var cfgport string + fmt.Printf("Server port: ") + if scanner.Scan() { + cfgport = scanner.Text() + } + + //read in password using term to keep it secure/hidden from bash history + fmt.Printf("Password: ") + ps, err := term.ReadPassword(int(os.Stdin.Fd())) + cobra.CheckErr(err) + + db, err := database.New() + cobra.CheckErr(err) + defer db.Close() + + fp, err := strconv.Atoi(cfgport) + cobra.CheckErr(err) + + err = db.SaveServer(model.Server{ + Name: cfgname, + Server: cfgaddress, + Port: fp, + Password: base64.StdEncoding.EncodeToString(ps), + }) + cobra.CheckErr(err) + }, +} + +func init() { + ServerCmd.AddCommand(addCmd) +} diff --git a/cmd/server/delete.go b/cmd/server/delete.go new file mode 100644 index 0000000..0b94987 --- /dev/null +++ b/cmd/server/delete.go @@ -0,0 +1,36 @@ +/* +Copyright © 2025 Jake jake.young.dev@gmail.com +*/ +package server + +import ( + "errors" + + "code.jakeyoungdev.com/jake/mctl/database" + "github.com/spf13/cobra" +) + +var deleteCmd = &cobra.Command{ + Use: "delete", + Example: "mctl server delete ", + // Short: "Deletes a server", + // Long: `Deletes server configuration`, + Run: func(cmd *cobra.Command, args []string) { + db, err := database.New() + cobra.CheckErr(err) + defer db.Close() + + err = db.DeleteServer(args[0]) + cobra.CheckErr(err) + }, + PreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("server parameter missing") + } + return nil + }, +} + +func init() { + ServerCmd.AddCommand(deleteCmd) +} diff --git a/cmd/server/server.go b/cmd/server/server.go new file mode 100644 index 0000000..1c07002 --- /dev/null +++ b/cmd/server/server.go @@ -0,0 +1,21 @@ +/* +Copyright © 2025 Jake jake.young.dev@gmail.com +*/ +package server + +import ( + "github.com/spf13/cobra" +) + +var ServerCmd = &cobra.Command{ + Use: "server", + Example: "mctl server ", + // Short: "A remote console client", + // Long: `mctl is a terminal-friendly remote console client made to manage game servers.`, + // Version: "v0.3.4", + // Run: func(cmd *cobra.Command, args []string) { }, +} + +func init() { + // rootCmd.AddCommand() +} diff --git a/cmd/server/update.go b/cmd/server/update.go new file mode 100644 index 0000000..dfa68cf --- /dev/null +++ b/cmd/server/update.go @@ -0,0 +1,48 @@ +/* +Copyright © 2025 Jake jake.young.dev@gmail.com +*/ +package server + +import ( + "encoding/base64" + "errors" + "fmt" + "os" + + "code.jakeyoungdev.com/jake/mctl/database" + "github.com/spf13/cobra" + "golang.org/x/term" +) + +var updateCmd = &cobra.Command{ + Use: "update", + Example: "mctl server update ", + // Short: "Saves a new server configuration", + // Long: `Saves server address, alias, port, and password.`, + Run: func(cmd *cobra.Command, args []string) { + //read in password using term to keep it secure/hidden from bash history + fmt.Printf("Password: ") + ps, err := term.ReadPassword(int(os.Stdin.Fd())) + cobra.CheckErr(err) + + db, err := database.New() + cobra.CheckErr(err) + defer db.Close() + + err = db.UpdateServer(args[0], base64.StdEncoding.EncodeToString(ps)) + cobra.CheckErr(err) + + fmt.Printf("%s password updated!", args[0]) + }, + PreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("name command required") + } + + return nil + }, +} + +func init() { + ServerCmd.AddCommand(updateCmd) +} diff --git a/cmd/server/view.go b/cmd/server/view.go new file mode 100644 index 0000000..34516dd --- /dev/null +++ b/cmd/server/view.go @@ -0,0 +1,37 @@ +/* +Copyright © 2025 Jake jake.young.dev@gmail.com +*/ +package server + +import ( + "fmt" + + "code.jakeyoungdev.com/jake/mctl/database" + "github.com/spf13/cobra" +) + +var viewCmd = &cobra.Command{ + Use: "view", + Example: "mctl server view", + Short: "view all servers", + Run: func(cmd *cobra.Command, args []string) { + db, err := database.New() + cobra.CheckErr(err) + defer db.Close() + + ts, err := db.GetAllServers() + cobra.CheckErr(err) + + for _, s := range ts { + fmt.Println("-----") + fmt.Printf("Name: %s\n", s.Name) + fmt.Printf("Address: %s\n", s.Server) + fmt.Printf("Port: %d\n", s.Port) + fmt.Printf("Password: %s\n", s.Password) + } + }, +} + +func init() { + ServerCmd.AddCommand(viewCmd) +} diff --git a/cmd/view.go b/cmd/view.go deleted file mode 100644 index 929bed5..0000000 --- a/cmd/view.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright © 2025 Jake jake.young.dev@gmail.com -*/ -package cmd - -import ( - "errors" - "fmt" - "strings" - - "code.jakeyoungdev.com/jake/mctl/database" - "github.com/spf13/cobra" -) - -// viewCmd represents the view command -var viewCmd = &cobra.Command{ - Use: "view ", - Example: "mctl view test", - Short: "View saved commands", - Long: `Load command using the supplied name and displays it in the terminal, 'all' will list every saved command`, - Run: func(cmd *cobra.Command, args []string) { - db, err := database.New() - cobra.CheckErr(err) - defer db.Close() - if strings.EqualFold(args[0], "all") { - cmds, err := db.GetAllCmds() - cobra.CheckErr(err) - - fmt.Println("\nCommands: ") - for _, c := range cmds { - fmt.Printf("%s - %s\n", c.Name, c.Command) - } - fmt.Println() - } else { - cmds, err := db.GetCmd(args[0]) - cobra.CheckErr(err) - - if cmds == "" { - fmt.Println("Command not found") - return - } - - fmt.Printf("Command: %s\n", cmds) - } - }, - 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(viewCmd) -} diff --git a/cryptography/aes.go b/cryptography/aes.go deleted file mode 100644 index d5f8012..0000000 --- a/cryptography/aes.go +++ /dev/null @@ -1,52 +0,0 @@ -package cryptography - -import ( - "crypto/aes" - "crypto/cipher" - - "github.com/spf13/viper" -) - -func EncryptPassword(b []byte) ([]byte, error) { - nonce := viper.Get("nonce").(string) - dev := viper.Get("device").(string) - - block, err := aes.NewCipher([]byte(dev)) - if err != nil { - return nil, err - } - - aesg, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - //adding #nosec trigger here since gosec interprets this as a hardcoded nonce value. The nonce is calculated using crypto/rand when the - //config command is ran and is pulled from memory when used any times after, for now we must prevent the scan from catching here until gosec - //is updated to account for this properly - ct := aesg.Seal(nil, []byte(nonce), []byte(b), nil) // #nosec - return ct, nil -} - -func DecryptPassword(b []byte) (string, error) { - nonce := viper.Get("nonce").(string) - password := viper.Get("password").(string) - dev := viper.Get("device").(string) - - block, err := aes.NewCipher([]byte(dev)) - if err != nil { - return "", err - } - - aesg, err := cipher.NewGCM(block) - if err != nil { - return "", err - } - - op, err := aesg.Open(nil, []byte(nonce), []byte(password), nil) - if err != nil { - return "", err - } - - return string(op), nil -} diff --git a/database/commands.go b/database/sqlx.go similarity index 88% rename from database/commands.go rename to database/sqlx.go index 80cc041..159bc95 100644 --- a/database/commands.go +++ b/database/sqlx.go @@ -10,6 +10,10 @@ import ( _ "github.com/ncruces/go-sqlite3/embed" ) +/* +all sqlx methods for CRUD functionalities of commands and servers +*/ + type database struct { *sqlx.DB } @@ -32,6 +36,8 @@ type Database interface { DeleteServer(name string) error } +// creates a new sqlite connection to the mctl database. Database files are +// kept in the the user home directory func New() (Database, error) { home, err := os.UserHomeDir() if err != nil { @@ -47,29 +53,32 @@ func New() (Database, error) { }, nil } +// intitial database setup, creates commands and servers tables func (d *database) Init() error { query := ` CREATE TABLE IF NOT EXISTS commands( name TEXT PRIMARY KEY, command TEXT - ) + ); CREATE TABLE IF NOT EXISTS servers( name TEXT PRIMARY KEY, server TEXT, password TEXT, - port NUMBER - ) + port NUMBER, + --active TEXT? bit? or something + ); ` _, err := d.Exec(query) return err } +// drops commands and servers tables func (d *database) Destroy() error { query := ` - DROP TABLE commands - DROP TABLE servers + DROP TABLE commands; + DROP TABLE servers; ` _, err := d.Exec(query) @@ -77,9 +86,10 @@ func (d *database) Destroy() error { } func (d *database) Close() error { - return d.Close() + return d.DB.Close() } +// gets command using name func (d *database) GetCmd(name string) (string, error) { query := ` SELECT @@ -99,6 +109,7 @@ func (d *database) GetCmd(name string) (string, error) { return name, nil } +// gets all saved commands func (d *database) GetAllCmds() ([]model.Command, error) { query := ` SELECT @@ -128,6 +139,7 @@ func (d *database) GetAllCmds() ([]model.Command, error) { return res, nil } +// save a new command func (d *database) SaveCmd(name, cmd string) error { query := ` INSERT INTO commands(name, command) @@ -138,6 +150,7 @@ func (d *database) SaveCmd(name, cmd string) error { return err } +// DO WE NEED THIS? func (d *database) UpdateCmd(name, cmd string) error { query := ` UPDATE diff --git a/go.mod b/go.mod index 68fac98..9a37cd3 100644 --- a/go.mod +++ b/go.mod @@ -7,26 +7,13 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/ncruces/go-sqlite3 v0.26.1 github.com/spf13/cobra v1.9.1 - github.com/spf13/viper v1.20.1 golang.org/x/term v0.31.0 ) require ( - github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ncruces/julianday v1.0.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.12.0 // indirect - github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.6 // indirect - github.com/subosito/gotenv v1.6.0 // indirect github.com/tetratelabs/wazero v1.9.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6fee408..8adc870 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,14 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jake-young-dev/mcr v1.4.0 h1:cXZImkfI8aNIiVPrONE6qP+nfblTGsD2iXpPKTcA25U= github.com/jake-young-dev/mcr v1.4.0/go.mod h1:74yZHGf9h3tLUDUpInA17grKLrNp9lVesWvisCFCXKY= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= @@ -32,39 +17,13 @@ github.com/ncruces/go-sqlite3 v0.26.1 h1:lBXmbmucH1Bsj57NUQR6T84UoMN7jnNImhF+ibE github.com/ncruces/go-sqlite3 v0.26.1/go.mod h1:XFTPtFIo1DmGCh+XVP8KGn9b/o2f+z0WZuT09x2N6eo= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= -github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= @@ -72,7 +31,4 @@ golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/model/data.go b/model/data.go index 38841a6..71c3d9d 100644 --- a/model/data.go +++ b/model/data.go @@ -1,8 +1,5 @@ package model -//list of all fields kept in config file -// var ConfigFields = [6]string{"customcmd", "device", "nonce", "port", "server", "password"} - type Command struct { Name string `db:"name"` Command string `db:"command"`