finishing sqlite changes

- finished default/active server logic
- dev done
- needs testing
This commit is contained in:
jake 2025-06-18 18:38:01 -04:00
parent fe37cac2da
commit c15c16be8d
19 changed files with 254 additions and 69 deletions

View File

@ -117,10 +117,5 @@ 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 this repo is currently in development and may encounter breaking changes, use a tag to prevent any surprises
# TODO # TODO
6. Do we use CheckErr or use RunE
7. update readme, the commands are all broken 7. update readme, the commands are all broken
9. do saved commands run on default server only? Yes, but add -s 8. Maybe move off of fmt
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)

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"code.jakeyoungdev.com/jake/mctl/database" "code.jakeyoungdev.com/jake/mctl/database"
"code.jakeyoungdev.com/jake/mctl/model"
"github.com/jake-young-dev/mcr" "github.com/jake-young-dev/mcr"
) )
@ -29,9 +30,17 @@ func New(name string) (*Client, error) {
} }
defer db.Close() defer db.Close()
srv, err := db.GetServer(name) var srv model.Server
if err != nil { if name != "" {
return nil, err srv, err = db.GetServer(name)
if err != nil {
return nil, err
}
} else {
srv, err = db.GetActiveServer()
if err != nil {
return nil, err
}
} }
fmt.Printf("Logging into %s on port %d\n", srv.Server, srv.Port) fmt.Printf("Logging into %s on port %d\n", srv.Server, srv.Port)

View File

@ -15,8 +15,7 @@ import (
var addCmd = &cobra.Command{ var addCmd = &cobra.Command{
Use: "add", Use: "add",
Example: "mctl command add", Example: "mctl command add",
// Short: "Saves a new server configuration", Short: "Saves a new command to the database",
// Long: `Saves server address, alias, port, and password.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
scanner := bufio.NewScanner(os.Stdin) scanner := bufio.NewScanner(os.Stdin)

View File

@ -11,12 +11,6 @@ import (
var CommandCmd = &cobra.Command{ var CommandCmd = &cobra.Command{
Use: "command", Use: "command",
Example: "mctl command <subcommand>", Example: "mctl command <subcommand>",
// 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() { func init() {}
// rootCmd.AddCommand()
}

View File

@ -14,8 +14,7 @@ import (
var deleteCmd = &cobra.Command{ var deleteCmd = &cobra.Command{
Use: "delete", Use: "delete",
Example: "mctl command delete <name>", Example: "mctl command delete <name>",
// Short: "Deletes a server", Short: "Deletes a command from the database",
// Long: `Deletes server configuration`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
db, err := database.New() db, err := database.New()
cobra.CheckErr(err) cobra.CheckErr(err)

75
cmd/command/run.go Normal file
View File

@ -0,0 +1,75 @@
/*
Copyright © 2025 Jake jake.young.dev@gmail.com
*/
package command
import (
"errors"
"fmt"
"strings"
"code.jakeyoungdev.com/jake/mctl/client"
"code.jakeyoungdev.com/jake/mctl/database"
"github.com/spf13/cobra"
)
var (
cfgserver string
)
var runCmd = &cobra.Command{
Use: "run",
Example: "mctl command run -s <server> <command> args...",
Short: "Runs a saved command on a server",
Long: `Runs the named command with the provided args on the default/active server unless -s is specified`,
RunE: func(cmd *cobra.Command, args []string) error {
cname := args[0]
db, err := database.New()
if err != nil {
return err
}
defer db.Close()
crun, err := db.GetCmd(cname)
if err != nil {
return err
}
count := strings.Count(crun, "%s")
l := len(args)
if l-1 != count {
return fmt.Errorf("not enough arguments to fill all command placeholders, required: %d - have: %d", count, l)
}
cli, err := client.New(cfgserver)
cobra.CheckErr(err)
defer cli.Close()
var fargs []any
for _, x := range args[1:] {
fargs = append(fargs, x)
}
res, err := cli.Command(fmt.Sprintf(crun, fargs...))
if err != nil {
return err
}
fmt.Println(res)
return nil
},
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("name parameter required")
}
return nil
},
}
func init() {
runCmd.Flags().StringVarP(&cfgserver, "server", "s", "", "server name")
CommandCmd.AddCommand(runCmd)
}

View File

@ -13,7 +13,7 @@ import (
var viewCmd = &cobra.Command{ var viewCmd = &cobra.Command{
Use: "view", Use: "view",
Example: "mctl command view", Example: "mctl command view",
// Short: "view all commands", Short: "view all saved commands",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
db, err := database.New() db, err := database.New()
cobra.CheckErr(err) cobra.CheckErr(err)

View File

@ -4,7 +4,10 @@ Copyright © 2025 Jake jake.young.dev@gmail.com
package cmd package cmd
import ( import (
"bufio"
"fmt" "fmt"
"os"
"strings"
"code.jakeyoungdev.com/jake/mctl/database" "code.jakeyoungdev.com/jake/mctl/database"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -12,18 +15,27 @@ import (
// destroyCmd represents the destroy command // destroyCmd represents the destroy command
var destroyCmd = &cobra.Command{ var destroyCmd = &cobra.Command{
Use: "destroy", Use: "destroy",
// Short: "Clear all configuration", Short: "clears all configuration",
// Long: `Clears all configuration values for mctl. [WARNING] all server configuration will be lost`, Long: `clear all data and drop database tables, this will delete all previously saved data and the 'inti' command
must be run again before use`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
db, err := database.New() scanner := bufio.NewScanner(os.Stdin)
cobra.CheckErr(err) fmt.Printf("Are you sure you want to destroy your config? (yes|no): ")
defer db.Close() if scanner.Scan() {
if strings.EqualFold(scanner.Text(), "yes") {
db, err := database.New()
cobra.CheckErr(err)
defer db.Close()
err = db.Destroy() err = db.Destroy()
cobra.CheckErr(err) cobra.CheckErr(err)
fmt.Println("Configuration is cleared, the 'init' command must be run again.") fmt.Println("Configuration is cleared, the 'init' command must be run again.")
}
} else {
return
}
}, },
} }

View File

@ -9,12 +9,9 @@ import (
) )
var initCmd = &cobra.Command{ var initCmd = &cobra.Command{
Use: "init", Use: "init",
Example: "mctl init", Example: "mctl init",
SilenceUsage: true, Short: "initializes the database tables, must be run before mctl can be used",
// 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) { Run: func(cmd *cobra.Command, args []string) {
db, err := database.New() db, err := database.New()
cobra.CheckErr(err) cobra.CheckErr(err)
@ -26,5 +23,5 @@ var initCmd = &cobra.Command{
} }
func init() { func init() {
rootCmd.AddCommand(loginCmd) rootCmd.AddCommand(initCmd)
} }

View File

@ -32,7 +32,7 @@ var loginCmd = &cobra.Command{
//get default server //get default server
if server == "" { if server == "" {
ds, err := db.GetServer("default") ds, err := db.GetActiveServer()
cobra.CheckErr(err) cobra.CheckErr(err)
server = ds.Name server = ds.Name

View File

@ -16,8 +16,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.3.4", Version: "v0.3.4", //change version number
// Run: func(cmd *cobra.Command, args []string) { },
} }
// Execute adds all child commands to the root command and sets flags appropriately. // Execute adds all child commands to the root command and sets flags appropriately.

35
cmd/server/active.go Normal file
View File

@ -0,0 +1,35 @@
/*
Copyright © 2025 Jake jake.young.dev@gmail.com
*/
package server
import (
"errors"
"code.jakeyoungdev.com/jake/mctl/database"
"github.com/spf13/cobra"
)
var activeCmd = &cobra.Command{
Use: "active",
Example: "mctl server active <server>",
Short: "sets the active server to run commands on",
Run: func(cmd *cobra.Command, args []string) {
db, err := database.New()
cobra.CheckErr(err)
defer db.Close()
err = db.SetActiveServer(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(activeCmd)
}

View File

@ -20,7 +20,7 @@ var addCmd = &cobra.Command{
Use: "add", Use: "add",
Example: "mctl server add", Example: "mctl server add",
Short: "Saves a new server configuration", Short: "Saves a new server configuration",
Long: `Saves server address, alias, port, and password.`, Long: `Saves server address, alias, port, and password to the database.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
scanner := bufio.NewScanner(os.Stdin) scanner := bufio.NewScanner(os.Stdin)

View File

@ -13,8 +13,7 @@ import (
var deleteCmd = &cobra.Command{ var deleteCmd = &cobra.Command{
Use: "delete", Use: "delete",
Example: "mctl server delete <server>", Example: "mctl server delete <server>",
// Short: "Deletes a server", Short: "deletes a server from the database",
// Long: `Deletes server configuration`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
db, err := database.New() db, err := database.New()
cobra.CheckErr(err) cobra.CheckErr(err)

View File

@ -10,12 +10,6 @@ import (
var ServerCmd = &cobra.Command{ var ServerCmd = &cobra.Command{
Use: "server", Use: "server",
Example: "mctl server <subcommand>", Example: "mctl server <subcommand>",
// 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() { func init() {}
// rootCmd.AddCommand()
}

View File

@ -17,8 +17,7 @@ import (
var updateCmd = &cobra.Command{ var updateCmd = &cobra.Command{
Use: "update", Use: "update",
Example: "mctl server update <name>", Example: "mctl server update <name>",
// Short: "Saves a new server configuration", Short: "updates a saved servers password in the database",
// Long: `Saves server address, alias, port, and password.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
//read in password using term to keep it secure/hidden from bash history //read in password using term to keep it secure/hidden from bash history
fmt.Printf("Password: ") fmt.Printf("Password: ")

View File

@ -13,7 +13,7 @@ import (
var viewCmd = &cobra.Command{ var viewCmd = &cobra.Command{
Use: "view", Use: "view",
Example: "mctl server view", Example: "mctl server view",
Short: "view all servers", Short: "view all saved servers",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
db, err := database.New() db, err := database.New()
cobra.CheckErr(err) cobra.CheckErr(err)
@ -27,7 +27,8 @@ var viewCmd = &cobra.Command{
fmt.Printf("Name: %s\n", s.Name) fmt.Printf("Name: %s\n", s.Name)
fmt.Printf("Address: %s\n", s.Server) fmt.Printf("Address: %s\n", s.Server)
fmt.Printf("Port: %d\n", s.Port) fmt.Printf("Port: %d\n", s.Port)
fmt.Printf("Password: %s\n", s.Password) fmt.Println("Password: [REDACTED]")
fmt.Printf("Default: %t\n", s.Default)
} }
}, },
} }

View File

@ -1,8 +1,11 @@
package database package database
import ( import (
"context"
"database/sql"
"fmt" "fmt"
"os" "os"
"time"
"code.jakeyoungdev.com/jake/mctl/model" "code.jakeyoungdev.com/jake/mctl/model"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
@ -11,9 +14,14 @@ import (
) )
/* /*
all sqlx methods for CRUD functionalities of commands and servers all sqlx methods for CRUD functionalities of commands and servers. All database methods should use the internal
methods query, exec, queryrow -- these wrapper functions handle timeouts and other configuration
*/ */
const (
DB_TIMEOUT = time.Second * 1
)
type database struct { type database struct {
*sqlx.DB *sqlx.DB
} }
@ -22,6 +30,11 @@ type Database interface {
Init() error Init() error
Destroy() error Destroy() error
Close() error Close() error
//internals
timeout() (context.Context, context.CancelFunc)
query(query string, args ...any) (*sqlx.Rows, error)
queryRow(query string, args ...any) *sqlx.Row
exec(query string, args ...any) (sql.Result, error)
//command methods //command methods
GetCmd(name string) (string, error) GetCmd(name string) (string, error)
GetAllCmds() ([]model.Command, error) GetAllCmds() ([]model.Command, error)
@ -30,7 +43,9 @@ type Database interface {
DeleteCmd(name string) error DeleteCmd(name string) error
//server methods //server methods
GetServer(name string) (model.Server, error) GetServer(name string) (model.Server, error)
GetActiveServer() (model.Server, error)
GetAllServers() ([]model.Server, error) GetAllServers() ([]model.Server, error)
SetActiveServer(name string) error
SaveServer(srv model.Server) error SaveServer(srv model.Server) error
UpdateServer(name, password string) error UpdateServer(name, password string) error
DeleteServer(name string) error DeleteServer(name string) error
@ -65,12 +80,12 @@ func (d *database) Init() error {
name TEXT PRIMARY KEY, name TEXT PRIMARY KEY,
server TEXT, server TEXT,
password TEXT, password TEXT,
port NUMBER, port INTEGER,
--active TEXT? bit? or something active INTEGER NOT NULL DEFAULT 0
); );
` `
_, err := d.Exec(query) _, err := d.exec(query)
return err return err
} }
@ -81,7 +96,7 @@ func (d *database) Destroy() error {
DROP TABLE servers; DROP TABLE servers;
` `
_, err := d.Exec(query) _, err := d.exec(query)
return err return err
} }
@ -89,6 +104,31 @@ func (d *database) Close() error {
return d.DB.Close() return d.DB.Close()
} }
func (d *database) timeout() (context.Context, context.CancelFunc) {
return context.WithTimeout(context.Background(), DB_TIMEOUT)
}
func (d *database) query(query string, args ...any) (*sqlx.Rows, error) {
ctx, cl := d.timeout()
defer cl()
return d.QueryxContext(ctx, query, args...)
}
func (d *database) queryRow(query string, args ...any) *sqlx.Row {
ctx, cl := d.timeout()
defer cl()
return d.QueryRowxContext(ctx, query, args...)
}
func (d *database) exec(query string, args ...any) (sql.Result, error) {
ctx, cl := d.timeout()
defer cl()
return d.ExecContext(ctx, query, args...)
}
// gets command using name // gets command using name
func (d *database) GetCmd(name string) (string, error) { func (d *database) GetCmd(name string) (string, error) {
query := ` query := `
@ -101,7 +141,7 @@ func (d *database) GetCmd(name string) (string, error) {
` `
var cmd string var cmd string
err := d.QueryRowx(query, name).Scan(&cmd) err := d.queryRow(query, name).Scan(&cmd)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -119,7 +159,7 @@ func (d *database) GetAllCmds() ([]model.Command, error) {
commands commands
` `
rows, err := d.Queryx(query) rows, err := d.query(query)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -146,7 +186,7 @@ func (d *database) SaveCmd(name, cmd string) error {
VALUES(?, ?) VALUES(?, ?)
` `
_, err := d.Exec(query, name, cmd) _, err := d.exec(query, name, cmd)
return err return err
} }
@ -161,7 +201,7 @@ func (d *database) UpdateCmd(name, cmd string) error {
name = ? name = ?
` `
_, err := d.Exec(query, cmd, name) _, err := d.exec(query, cmd, name)
return err return err
} }
@ -171,7 +211,7 @@ func (d *database) DeleteCmd(name string) error {
WHERE name = ? WHERE name = ?
` `
_, err := d.Exec(query, name) _, err := d.exec(query, name)
return err return err
} }
@ -189,13 +229,31 @@ func (d *database) GetServer(name string) (model.Server, error) {
` `
var s model.Server var s model.Server
err := d.QueryRowx(query, name).StructScan(&s) err := d.queryRow(query, name).StructScan(&s)
if err != nil { if err != nil {
return model.Server{}, err return model.Server{}, err
} }
return s, nil return s, nil
} }
func (d *database) GetActiveServer() (model.Server, error) {
query := `
SELECT
name,
server,
password,
port
FROM
servers
WHERE
active = 1
`
var s model.Server
err := d.queryRow(query).StructScan(&s)
return s, err
}
func (d *database) GetAllServers() ([]model.Server, error) { func (d *database) GetAllServers() ([]model.Server, error) {
query := ` query := `
SELECT SELECT
@ -207,7 +265,7 @@ func (d *database) GetAllServers() ([]model.Server, error) {
servers servers
` `
rows, err := d.Queryx(query) rows, err := d.query(query)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -233,7 +291,26 @@ func (d *database) SaveServer(srv model.Server) error {
VALUES(?, ?, ?, ?) VALUES(?, ?, ?, ?)
` `
_, err := d.Exec(query, srv.Name, srv.Server, srv.Password, srv.Port) _, err := d.exec(query, srv.Name, srv.Server, srv.Password, srv.Port)
return err
}
func (d *database) SetActiveServer(name string) error {
query := `
UPDATE
servers
SET
active = 0;
UPDATE
servers
SET
active = 1
WHERE
name = ?
`
_, err := d.exec(query, name)
return err return err
} }
@ -247,7 +324,7 @@ func (d *database) UpdateServer(name, password string) error {
name = ? name = ?
` `
_, err := d.Exec(query, password, name) _, err := d.exec(query, password, name)
return err return err
} }
@ -258,6 +335,6 @@ func (d *database) DeleteServer(name string) error {
name = ? name = ?
` `
_, err := d.Exec(query, name) _, err := d.exec(query, name)
return err return err
} }

View File

@ -10,4 +10,5 @@ type Server struct {
Server string `db:"server"` Server string `db:"server"`
Password string `db:"password"` Password string `db:"password"`
Port int `db:"port"` Port int `db:"port"`
Default bool `db:"active"`
} }