Compare commits
No commits in common. "8daa6215283712baf3d4541830376c6cceb892ff" and "8068b090ed11477bf57314a0db65a8718345bc6d" have entirely different histories.
8daa621528
...
8068b090ed
2
.gitignore
vendored
2
.gitignore
vendored
@ -24,4 +24,4 @@ go.work.sum
|
|||||||
|
|
||||||
# env file
|
# env file
|
||||||
.env
|
.env
|
||||||
todo.txt
|
|
||||||
|
49
README.md
49
README.md
@ -6,11 +6,10 @@ Install mctl using golang
|
|||||||
```bash
|
```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 #it is recommended to use a tagged version
|
||||||
```
|
```
|
||||||
<br />
|
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
### Configuring mctl
|
### Configuring mctl
|
||||||
mctl requires a one-time setup via the 'config' command before interacting with any servers, password is entered securely from the terminal
|
mctl requires a one-time setup via the 'config' command, password is entered securely from the terminal
|
||||||
```bash
|
```bash
|
||||||
mctl config -s <serveraddress> -p <rconport>
|
mctl config -s <serveraddress> -p <rconport>
|
||||||
```
|
```
|
||||||
@ -32,55 +31,12 @@ RCON@X.X.X.X /> list
|
|||||||
There are 0 of a max of 20 players online:
|
There are 0 of a max of 20 players online:
|
||||||
```
|
```
|
||||||
|
|
||||||
### Saving commands
|
|
||||||
Commands can be saved under an alias for quick execution later, saved commands can contain placeholders '%s' that can be populated at runtime to allow for commands with unique runtime args to still be saved:
|
|
||||||
```bash
|
|
||||||
mctl save <name>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Viewing commands
|
|
||||||
Saved commands can be viewed with:
|
|
||||||
```bash
|
|
||||||
mctl view <name>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Running saved commands
|
|
||||||
Commands that have been saved can be run with:
|
|
||||||
```bash
|
|
||||||
mctl run <name>
|
|
||||||
```
|
|
||||||
If the saved command contains placeholders, the necessary arguments must be supplied:
|
|
||||||
```bash
|
|
||||||
mctl run <name> args...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Saving and running example
|
|
||||||
```bash
|
|
||||||
#saving command named "test" to run "tp %s 0 0 0"
|
|
||||||
mctl save test
|
|
||||||
Command: tp %s 0 0 0
|
|
||||||
|
|
||||||
#run command on user "jake"
|
|
||||||
mctl run test jake
|
|
||||||
#will run: tp jake 0 0 0 on remote server
|
|
||||||
```
|
|
||||||
|
|
||||||
### Delete saved command
|
|
||||||
Commands can be deleted with:
|
|
||||||
```bash
|
|
||||||
mctl delete <name>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
### Commands
|
### Commands
|
||||||
|Command|Description|
|
|Command|Description|
|
||||||
|---|---|
|
|---|---|
|
||||||
|config|used to update the config file|
|
|config|used to update the config file|
|
||||||
|login|makes connection request to the server using saved configuration and enters command loop|
|
|login|makes connection request to the server using saved configuration and enters command loop|
|
||||||
|save \<name>|saves specific command for reuse|
|
|
||||||
|view \<name>|displays saved command|
|
|
||||||
|delete \<name>|deletes saved command|
|
|
||||||
|run \<name> args...|runs saved command filling placeholders with supplied args|
|
|
||||||
|
|
||||||
### Flags
|
### Flags
|
||||||
#### config
|
#### config
|
||||||
@ -89,6 +45,9 @@ mctl delete <name>
|
|||||||
|port|p|yes|RCon port|
|
|port|p|yes|RCon port|
|
||||||
|server|s|yes|RCon address|
|
|server|s|yes|RCon address|
|
||||||
|
|
||||||
|
#### login
|
||||||
|
no flags
|
||||||
|
|
||||||
### Configuration file
|
### Configuration file
|
||||||
All configuration data will be kept in /home/.mctl.yaml or C:\\Users\\username\\.mctl.yaml, passwords are encrypted for an added layer of security
|
All configuration data will be kept in /home/.mctl.yaml or C:\\Users\\username\\.mctl.yaml, passwords are encrypted for an added layer of security
|
||||||
|
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -4,26 +4,26 @@ Copyright © 2025 Jake jake.young.dev@gmail.com
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"code.jakeyoungdev.com/jake/mctl/cryptography"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfgserver string
|
server string
|
||||||
cfgport int
|
port 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
|
||||||
@ -34,20 +34,24 @@ 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))
|
aesg, err := cipher.NewGCM(block)
|
||||||
|
|
||||||
//encrypt password
|
|
||||||
ciphert, err := cryptography.EncryptPassword(ps)
|
|
||||||
cobra.CheckErr(err)
|
cobra.CheckErr(err)
|
||||||
|
|
||||||
|
//encrypt rcon password
|
||||||
|
ciphert := aesg.Seal(nil, nonce, ps, nil)
|
||||||
|
|
||||||
//update config file with new values
|
//update config file with new values
|
||||||
viper.Set("server", cfgserver)
|
viper.Set("server", server)
|
||||||
viper.Set("password", string(ciphert))
|
viper.Set("password", string(ciphert))
|
||||||
viper.Set("port", cfgport)
|
viper.Set("port", port)
|
||||||
|
viper.Set("nonce", string(nonce))
|
||||||
viper.WriteConfig()
|
viper.WriteConfig()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Config file updated!")
|
fmt.Println("Config file updated!")
|
||||||
@ -56,14 +60,13 @@ var configCmd = &cobra.Command{
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
initConfig()
|
initConfig()
|
||||||
configCmd.Flags().StringVarP(&cfgserver, "server", "s", "", "server address")
|
configCmd.Flags().StringVarP(&server, "server", "s", "", "server address")
|
||||||
configCmd.MarkFlagRequired("server")
|
configCmd.MarkFlagRequired("server")
|
||||||
configCmd.Flags().IntVarP(&cfgport, "port", "p", 0, "server rcon port")
|
configCmd.Flags().IntVarP(&port, "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)
|
||||||
@ -71,14 +74,15 @@ 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", cfgserver)
|
viper.Set("server", server)
|
||||||
viper.Set("password", "")
|
viper.Set("password", "")
|
||||||
viper.Set("port", cfgport)
|
viper.Set("port", port)
|
||||||
viper.Set("nonce", "")
|
viper.Set("nonce", "")
|
||||||
|
|
||||||
//generate psuedo-random key
|
//generate psuedo-random key
|
||||||
@ -86,11 +90,6 @@ func initConfig() {
|
|||||||
_, err := rand.Read(uu)
|
_, err := rand.Read(uu)
|
||||||
cobra.CheckErr(err)
|
cobra.CheckErr(err)
|
||||||
|
|
||||||
//create custom command map
|
|
||||||
cmdMap := make(map[string]any, 0)
|
|
||||||
|
|
||||||
//write config
|
|
||||||
viper.Set("customcmd", cmdMap)
|
|
||||||
viper.Set("device", string(uu))
|
viper.Set("device", string(uu))
|
||||||
viper.SafeWriteConfig()
|
viper.SafeWriteConfig()
|
||||||
}
|
}
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
|
|
||||||
*/
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// deleteCmd represents the delete command
|
|
||||||
var deleteCmd = &cobra.Command{
|
|
||||||
Use: "delete <name>",
|
|
||||||
Example: "mctl delete newcmd",
|
|
||||||
Short: "Delete a saved command",
|
|
||||||
Long: `Deletes a command stored using the save command`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
var cm map[string]any
|
|
||||||
cmdMap := viper.Get("customcmd")
|
|
||||||
if cmdMap == nil {
|
|
||||||
cm = make(map[string]any, 0)
|
|
||||||
} else {
|
|
||||||
cm = cmdMap.(map[string]any)
|
|
||||||
}
|
|
||||||
delete(cm, args[0])
|
|
||||||
viper.Set("customcmd", cmdMap)
|
|
||||||
viper.WriteConfig()
|
|
||||||
},
|
|
||||||
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(deleteCmd)
|
|
||||||
}
|
|
38
cmd/login.go
38
cmd/login.go
@ -5,11 +5,12 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"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,15 +18,32 @@ 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",
|
|
||||||
SilenceUsage: true,
|
|
||||||
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").(string)
|
server := viper.Get("server")
|
||||||
cli, err := client.New()
|
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)
|
||||||
|
|
||||||
|
//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()
|
||||||
|
|
||||||
@ -43,7 +61,6 @@ var loginCmd = &cobra.Command{
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
//mctl exits command terminal
|
|
||||||
if runningCmd == "mctl" {
|
if runningCmd == "mctl" {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -56,13 +73,6 @@ var loginCmd = &cobra.Command{
|
|||||||
|
|
||||||
fmt.Printf("Disconnected from %s\n", server)
|
fmt.Printf("Disconnected from %s\n", server)
|
||||||
},
|
},
|
||||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
//ensure config command has been run
|
|
||||||
if viper.Get("server") == "" || viper.Get("password") == "" || viper.Get("port") == 0 {
|
|
||||||
return errors.New("the 'config' command must be run before you can interact with servers")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -14,7 +14,7 @@ var rootCmd = &cobra.Command{
|
|||||||
Use: "mctl",
|
Use: "mctl",
|
||||||
Short: "A remote console client",
|
Short: "A remote console client",
|
||||||
Long: `mctl is a terminal-friendly remote console client made to manage game servers.`,
|
Long: `mctl is a terminal-friendly remote console client made to manage game servers.`,
|
||||||
Version: "v0.2.0",
|
Version: "v0.1.1",
|
||||||
// Run: func(cmd *cobra.Command, args []string) { },
|
// Run: func(cmd *cobra.Command, args []string) { },
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,5 +28,6 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
//dont show completion subcommand in help message, makes the syntax confusing
|
||||||
|
rootCmd.Root().CompletionOptions.DisableDefaultCmd = true
|
||||||
}
|
}
|
||||||
|
85
cmd/run.go
85
cmd/run.go
@ -1,85 +0,0 @@
|
|||||||
/*
|
|
||||||
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 <name> 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) {
|
|
||||||
//check for command map
|
|
||||||
var cm map[string]any
|
|
||||||
cmdMap := viper.Get("customcmd")
|
|
||||||
if cmdMap == nil {
|
|
||||||
cm = make(map[string]any, 0)
|
|
||||||
} else {
|
|
||||||
cm = cmdMap.(map[string]any)
|
|
||||||
}
|
|
||||||
//is this an existing command
|
|
||||||
cmdRun, ok := cm[args[0]]
|
|
||||||
if !ok {
|
|
||||||
fmt.Printf("command %s not found", args[0])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//convert arguments to interface
|
|
||||||
var nargs []any
|
|
||||||
for _, a := range args[1:] {
|
|
||||||
nargs = append(nargs, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
//inject arguments
|
|
||||||
fixed := fmt.Sprintf(cmdRun.(string), 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 configuration has been setup
|
|
||||||
if viper.Get("server") == "" || viper.Get("password") == "" || viper.Get("port") == 0 {
|
|
||||||
return errors.New("the 'config' command must be run before you can interact with servers")
|
|
||||||
}
|
|
||||||
|
|
||||||
//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)
|
|
||||||
}
|
|
55
cmd/save.go
55
cmd/save.go
@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright © 2025 Jake jake.young.dev@gmail.com
|
|
||||||
*/
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// saveCmd represents the save command
|
|
||||||
var saveCmd = &cobra.Command{
|
|
||||||
Use: "save <name>",
|
|
||||||
Example: "mctl save newcmd",
|
|
||||||
Short: "Saves a server command under an alias for quick execution",
|
|
||||||
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`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
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 != "" {
|
|
||||||
var cmdMap map[string]any
|
|
||||||
cm := viper.Get("customcmd")
|
|
||||||
if cmdMap == nil {
|
|
||||||
cmdMap = make(map[string]any, 0)
|
|
||||||
} else {
|
|
||||||
cmdMap = cm.(map[string]any)
|
|
||||||
}
|
|
||||||
cmdMap[args[0]] = txt
|
|
||||||
viper.Set("customcmd", cmdMap)
|
|
||||||
viper.WriteConfig()
|
|
||||||
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)
|
|
||||||
}
|
|
48
cmd/view.go
48
cmd/view.go
@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright © 2025 Jake jake.young.dev@gmail.com
|
|
||||||
*/
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// viewCmd represents the view command
|
|
||||||
var viewCmd = &cobra.Command{
|
|
||||||
Use: "view <name>",
|
|
||||||
Example: "mctl view test",
|
|
||||||
Short: "View saved commands",
|
|
||||||
Long: `Load command using the supplied name and displays it in the terminal`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
var cm map[string]any
|
|
||||||
cmdMap := viper.Get("customcmd")
|
|
||||||
if cmdMap == nil {
|
|
||||||
fmt.Println("no custom commands found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cm = cmdMap.(map[string]any)
|
|
||||||
custom, ok := cm[args[0]]
|
|
||||||
if !ok {
|
|
||||||
fmt.Println("command not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Command: %s", custom.(string))
|
|
||||||
},
|
|
||||||
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)
|
|
||||||
}
|
|
@ -1,49 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
ct := aesg.Seal(nil, []byte(nonce), []byte(b), nil)
|
|
||||||
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
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user