mirror of
https://github.com/onyx-and-iris/q3rcon.git
synced 2026-04-19 16:23:31 +00:00
Compare commits
6 Commits
c04301562e
...
v0.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 3be7ddb36b | |||
| c22b07808f | |||
| c015770c2c | |||
| cd15e89837 | |||
| c3e8013c4f | |||
| 89dd2d2eb1 |
@@ -22,6 +22,7 @@ builds:
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
|
||||
|
||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -11,6 +11,18 @@ Before any major/minor/patch bump all unit tests will be run to verify they pass
|
||||
|
||||
- [x]
|
||||
|
||||
# [0.4.0] - 2026-02-15
|
||||
|
||||
### Added
|
||||
|
||||
- macos build to releases
|
||||
|
||||
### Changed
|
||||
|
||||
- exitOnError() removed in favour of a [deferred exit function](https://github.com/onyx-and-iris/q3rcon/blob/cd15e8983726177d6edd985a8bf3d7f4e0d7f346/cmd/q3rcon/main.go#L21), this ensures the closer() cleanup function is always called.
|
||||
- the included CLI now uses a [timeouts map](https://github.com/onyx-and-iris/q3rcon/blob/cd15e8983726177d6edd985a8bf3d7f4e0d7f346/cmd/q3rcon/main.go#L109).
|
||||
- even though this is only an example implementation it should still be basically usable.
|
||||
|
||||
# [0.3.0] - 2025-04-05
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -7,18 +7,35 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/onyx-and-iris/q3rcon"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/onyx-and-iris/q3rcon"
|
||||
)
|
||||
|
||||
func exitOnError(err error) {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Error: %s\n", err)
|
||||
os.Exit(1)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 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
|
||||
@@ -30,9 +47,19 @@ func main() {
|
||||
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.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.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")
|
||||
@@ -44,53 +71,69 @@ func main() {
|
||||
|
||||
level, err := log.ParseLevel(loglevel)
|
||||
if err != nil {
|
||||
exitOnError(fmt.Errorf("invalid log level: %s", loglevel))
|
||||
return nil, fmt.Errorf("invalid log level: %s", loglevel)
|
||||
}
|
||||
log.SetLevel(level)
|
||||
|
||||
if port < 1024 || port > 65535 {
|
||||
exitOnError(fmt.Errorf("invalid port value, got: (%d) expected: in range 1024-65535", port))
|
||||
return nil, fmt.Errorf("invalid port value, got: (%d) expected: in range 1024-65535", port)
|
||||
}
|
||||
|
||||
if len(rconpass) < 8 {
|
||||
exitOnError(fmt.Errorf("invalid rcon password, got: (%s) expected: at least 8 characters", rconpass))
|
||||
return nil, fmt.Errorf(
|
||||
"invalid rcon password, got: (%s) expected: at least 8 characters",
|
||||
rconpass,
|
||||
)
|
||||
}
|
||||
|
||||
rcon, err := connectRcon(host, port, rconpass)
|
||||
client, closer, err := connectRcon(host, port, rconpass)
|
||||
if err != nil {
|
||||
exitOnError(err)
|
||||
}
|
||||
defer rcon.Close()
|
||||
|
||||
if !interactive {
|
||||
runCommands(flag.Args(), rcon)
|
||||
return
|
||||
return nil, fmt.Errorf("failed to connect to rcon: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Enter 'Q' to exit.\n>> ")
|
||||
err = interactiveMode(rcon, os.Stdin)
|
||||
if err != nil {
|
||||
exitOnError(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
|
||||
}
|
||||
|
||||
func connectRcon(host string, port int, password string) (*q3rcon.Rcon, error) {
|
||||
rcon, err := q3rcon.New(host, port, password)
|
||||
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, err
|
||||
return nil, nil, err
|
||||
}
|
||||
return rcon, nil
|
||||
|
||||
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(commands []string, rcon *q3rcon.Rcon) {
|
||||
if len(commands) == 0 {
|
||||
commands = append(commands, "status")
|
||||
}
|
||||
|
||||
func runCommands(client *q3rcon.Rcon, commands []string) {
|
||||
for _, cmd := range commands {
|
||||
resp, err := rcon.Send(cmd)
|
||||
resp, err := client.Send(cmd)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
continue
|
||||
@@ -100,7 +143,7 @@ func runCommands(commands []string, rcon *q3rcon.Rcon) {
|
||||
}
|
||||
|
||||
// interactiveMode continuously reads from input until a quit signal is given.
|
||||
func interactiveMode(rcon *q3rcon.Rcon, input io.Reader) error {
|
||||
func interactiveMode(client *q3rcon.Rcon, input io.Reader) error {
|
||||
scanner := bufio.NewScanner(input)
|
||||
for scanner.Scan() {
|
||||
cmd := scanner.Text()
|
||||
@@ -108,7 +151,7 @@ func interactiveMode(rcon *q3rcon.Rcon, input io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := rcon.Send(cmd)
|
||||
resp, err := client.Send(cmd)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
continue
|
||||
|
||||
BIN
img/mapname.png
BIN
img/mapname.png
Binary file not shown.
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 4.2 KiB |
BIN
img/status.png
BIN
img/status.png
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 10 KiB |
Reference in New Issue
Block a user