mirror of
https://github.com/onyx-and-iris/q3rcon.git
synced 2026-02-16 02:07:53 +00:00
191 lines
4.3 KiB
Go
191 lines
4.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"runtime/debug"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/charmbracelet/log"
|
|
|
|
"github.com/onyx-and-iris/q3rcon"
|
|
)
|
|
|
|
var version string // Version will be set at build time
|
|
|
|
func main() {
|
|
var exitCode int
|
|
|
|
// Defer exit with the final exit code
|
|
defer func() {
|
|
if exitCode != 0 {
|
|
os.Exit(exitCode)
|
|
}
|
|
}()
|
|
|
|
closer, err := run()
|
|
if closer != nil {
|
|
defer closer()
|
|
}
|
|
if err != nil {
|
|
log.Error(err)
|
|
exitCode = 1
|
|
}
|
|
}
|
|
|
|
// run executes the main logic of the application and returns a cleanup function and an error if any.
|
|
func run() (func(), error) {
|
|
var (
|
|
host string
|
|
port int
|
|
rconpass string
|
|
interactive bool
|
|
loglevel string
|
|
versionFlag bool
|
|
)
|
|
|
|
flag.StringVar(&host, "host", "localhost", "hostname of the gameserver")
|
|
flag.StringVar(&host, "h", "localhost", "hostname of the gameserver (shorthand)")
|
|
flag.IntVar(&port, "port", 28960, "port on which the gameserver resides, default is 28960")
|
|
flag.IntVar(
|
|
&port,
|
|
"p",
|
|
28960,
|
|
"port on which the gameserver resides, default is 28960 (shorthand)",
|
|
)
|
|
flag.StringVar(&rconpass, "rconpass", os.Getenv("RCON_PASS"), "rcon password of the gameserver")
|
|
flag.StringVar(
|
|
&rconpass,
|
|
"r",
|
|
os.Getenv("RCON_PASS"),
|
|
"rcon password of the gameserver (shorthand)",
|
|
)
|
|
|
|
flag.BoolVar(&interactive, "interactive", false, "run in interactive mode")
|
|
flag.BoolVar(&interactive, "i", false, "run in interactive mode")
|
|
|
|
flag.StringVar(&loglevel, "loglevel", "warn", "log level")
|
|
flag.StringVar(&loglevel, "l", "warn", "log level (shorthand)")
|
|
|
|
flag.BoolVar(&versionFlag, "version", false, "print version information and exit")
|
|
flag.BoolVar(&versionFlag, "v", false, "print version information and exit (shorthand)")
|
|
|
|
flag.Parse()
|
|
|
|
if versionFlag {
|
|
fmt.Printf("q3rcon version: %s\n", versionFromBuild())
|
|
return nil, nil
|
|
}
|
|
|
|
level, err := log.ParseLevel(loglevel)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid log level: %s", loglevel)
|
|
}
|
|
log.SetLevel(level)
|
|
|
|
if port < 1024 || port > 65535 {
|
|
return nil, fmt.Errorf("invalid port value, got: (%d) expected: in range 1024-65535", port)
|
|
}
|
|
|
|
if len(rconpass) < 8 {
|
|
return nil, fmt.Errorf(
|
|
"invalid rcon password, got: (%s) expected: at least 8 characters",
|
|
rconpass,
|
|
)
|
|
}
|
|
|
|
client, closer, err := connectRcon(host, port, rconpass)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to connect to rcon: %w", err)
|
|
}
|
|
|
|
if interactive {
|
|
fmt.Printf("Enter 'Q' to exit.\n>> ")
|
|
err := interactiveMode(client, os.Stdin)
|
|
if err != nil {
|
|
return closer, fmt.Errorf("interactive mode error: %w", err)
|
|
}
|
|
return closer, nil
|
|
}
|
|
|
|
commands := flag.Args()
|
|
if len(commands) == 0 {
|
|
log.Debug("no commands provided, defaulting to 'status'")
|
|
commands = append(commands, "status")
|
|
}
|
|
runCommands(client, commands)
|
|
|
|
return closer, nil
|
|
}
|
|
|
|
// versionFromBuild retrieves the version information from the build metadata.
|
|
func versionFromBuild() string {
|
|
if version == "" {
|
|
info, ok := debug.ReadBuildInfo()
|
|
if !ok {
|
|
return "(unable to read build info)"
|
|
}
|
|
version = strings.Split(info.Main.Version, "-")[0]
|
|
}
|
|
return version
|
|
}
|
|
|
|
func connectRcon(host string, port int, password string) (*q3rcon.Rcon, func(), error) {
|
|
client, err := q3rcon.New(host, port, password, q3rcon.WithTimeouts(map[string]time.Duration{
|
|
"map": time.Second,
|
|
"map_rotate": time.Second,
|
|
"map_restart": time.Second,
|
|
}))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
closer := func() {
|
|
if err := client.Close(); err != nil {
|
|
log.Error(err)
|
|
}
|
|
}
|
|
|
|
return client, closer, nil
|
|
}
|
|
|
|
// runCommands runs the commands given in the flag.Args slice.
|
|
// If no commands are given, it defaults to running the "status" command.
|
|
func runCommands(client *q3rcon.Rcon, commands []string) {
|
|
for _, cmd := range commands {
|
|
resp, err := client.Send(cmd)
|
|
if err != nil {
|
|
log.Error(err)
|
|
continue
|
|
}
|
|
fmt.Print(resp)
|
|
}
|
|
}
|
|
|
|
// interactiveMode continuously reads from input until a quit signal is given.
|
|
func interactiveMode(client *q3rcon.Rcon, input io.Reader) error {
|
|
scanner := bufio.NewScanner(input)
|
|
for scanner.Scan() {
|
|
cmd := scanner.Text()
|
|
if strings.EqualFold(cmd, "Q") {
|
|
return nil
|
|
}
|
|
|
|
resp, err := client.Send(cmd)
|
|
if err != nil {
|
|
log.Error(err)
|
|
continue
|
|
}
|
|
fmt.Printf("%s>> ", resp)
|
|
}
|
|
|
|
if scanner.Err() != nil {
|
|
return scanner.Err()
|
|
}
|
|
return nil
|
|
}
|