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
# TODO
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)
8. Maybe move off of fmt

View File

@ -5,6 +5,7 @@ import (
"fmt"
"code.jakeyoungdev.com/jake/mctl/database"
"code.jakeyoungdev.com/jake/mctl/model"
"github.com/jake-young-dev/mcr"
)
@ -29,10 +30,18 @@ func New(name string) (*Client, error) {
}
defer db.Close()
srv, err := db.GetServer(name)
var srv model.Server
if name != "" {
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)
var p []byte

View File

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

View File

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

View File

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

View File

@ -4,7 +4,10 @@ Copyright © 2025 Jake jake.young.dev@gmail.com
package cmd
import (
"bufio"
"fmt"
"os"
"strings"
"code.jakeyoungdev.com/jake/mctl/database"
"github.com/spf13/cobra"
@ -13,9 +16,14 @@ import (
// 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`,
Short: "clears all configuration",
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) {
scanner := bufio.NewScanner(os.Stdin)
fmt.Printf("Are you sure you want to destroy your config? (yes|no): ")
if scanner.Scan() {
if strings.EqualFold(scanner.Text(), "yes") {
db, err := database.New()
cobra.CheckErr(err)
defer db.Close()
@ -24,6 +32,10 @@ var destroyCmd = &cobra.Command{
cobra.CheckErr(err)
fmt.Println("Configuration is cleared, the 'init' command must be run again.")
}
} else {
return
}
},
}

View File

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

View File

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

View File

@ -16,8 +16,7 @@ var rootCmd = &cobra.Command{
Use: "mctl",
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) { },
Version: "v0.3.4", //change version number
}
// 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",
Example: "mctl server add",
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) {
scanner := bufio.NewScanner(os.Stdin)

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,11 @@
package database
import (
"context"
"database/sql"
"fmt"
"os"
"time"
"code.jakeyoungdev.com/jake/mctl/model"
"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 {
*sqlx.DB
}
@ -22,6 +30,11 @@ type Database interface {
Init() error
Destroy() 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
GetCmd(name string) (string, error)
GetAllCmds() ([]model.Command, error)
@ -30,7 +43,9 @@ type Database interface {
DeleteCmd(name string) error
//server methods
GetServer(name string) (model.Server, error)
GetActiveServer() (model.Server, error)
GetAllServers() ([]model.Server, error)
SetActiveServer(name string) error
SaveServer(srv model.Server) error
UpdateServer(name, password string) error
DeleteServer(name string) error
@ -65,12 +80,12 @@ func (d *database) Init() error {
name TEXT PRIMARY KEY,
server TEXT,
password TEXT,
port NUMBER,
--active TEXT? bit? or something
port INTEGER,
active INTEGER NOT NULL DEFAULT 0
);
`
_, err := d.Exec(query)
_, err := d.exec(query)
return err
}
@ -81,7 +96,7 @@ func (d *database) Destroy() error {
DROP TABLE servers;
`
_, err := d.Exec(query)
_, err := d.exec(query)
return err
}
@ -89,6 +104,31 @@ func (d *database) Close() error {
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
func (d *database) GetCmd(name string) (string, error) {
query := `
@ -101,7 +141,7 @@ func (d *database) GetCmd(name string) (string, error) {
`
var cmd string
err := d.QueryRowx(query, name).Scan(&cmd)
err := d.queryRow(query, name).Scan(&cmd)
if err != nil {
return "", err
}
@ -119,7 +159,7 @@ func (d *database) GetAllCmds() ([]model.Command, error) {
commands
`
rows, err := d.Queryx(query)
rows, err := d.query(query)
if err != nil {
return nil, err
}
@ -146,7 +186,7 @@ func (d *database) SaveCmd(name, cmd string) error {
VALUES(?, ?)
`
_, err := d.Exec(query, name, cmd)
_, err := d.exec(query, name, cmd)
return err
}
@ -161,7 +201,7 @@ func (d *database) UpdateCmd(name, cmd string) error {
name = ?
`
_, err := d.Exec(query, cmd, name)
_, err := d.exec(query, cmd, name)
return err
}
@ -171,7 +211,7 @@ func (d *database) DeleteCmd(name string) error {
WHERE name = ?
`
_, err := d.Exec(query, name)
_, err := d.exec(query, name)
return err
}
@ -189,13 +229,31 @@ func (d *database) GetServer(name string) (model.Server, error) {
`
var s model.Server
err := d.QueryRowx(query, name).StructScan(&s)
err := d.queryRow(query, name).StructScan(&s)
if err != nil {
return model.Server{}, err
}
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) {
query := `
SELECT
@ -207,7 +265,7 @@ func (d *database) GetAllServers() ([]model.Server, error) {
servers
`
rows, err := d.Queryx(query)
rows, err := d.query(query)
if err != nil {
return nil, err
}
@ -233,7 +291,26 @@ func (d *database) SaveServer(srv model.Server) error {
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
}
@ -247,7 +324,7 @@ func (d *database) UpdateServer(name, password string) error {
name = ?
`
_, err := d.Exec(query, password, name)
_, err := d.exec(query, password, name)
return err
}
@ -258,6 +335,6 @@ func (d *database) DeleteServer(name string) error {
name = ?
`
_, err := d.Exec(query, name)
_, err := d.exec(query, name)
return err
}

View File

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