Compare commits

..

11 commits

Author SHA1 Message Date
Geoff Bourne
1372fe49c6 ci: go back to proper semantic version tags 2019-01-10 18:07:35 -06:00
Geoff Bourne
e2bb4fe15a ci: allow for rc tags 2019-01-10 18:05:35 -06:00
Geoff Bourne
0c13c68ed9 ci: restore release non-branch builds 2019-01-10 17:59:52 -06:00
Geoff Bourne
9b0d301d23 ci: adjust release tags matching 2019-01-10 17:57:45 -06:00
Geoff Bourne
9fb82783dd ci: split CircleCI release into its own workflow 2019-01-10 17:51:39 -06:00
Geoff Bourne
65c6b383ad docs: adding release instructions 2019-01-10 17:47:04 -06:00
Geoff Bourne
07e76ff43a ci: tidy up the go.mod 2019-01-10 17:42:05 -06:00
Geoff Bourne
5b750bc4d8 ci: touch go.sum to invalidate CircleCI cache 2019-01-10 17:34:38 -06:00
Geoff Bourne
748bbc869c ci: fixing cached pkg directory path 2019-01-10 17:31:52 -06:00
Geoff Bourne
7a03e3051c ci: use go mod download to prep 2019-01-10 17:28:08 -06:00
Geoff Bourne
bd5043436b Switch to Go modules 2019-01-09 22:22:24 -06:00
12 changed files with 405 additions and 280 deletions

54
.circleci/config.yml Normal file
View file

@ -0,0 +1,54 @@
version: 2
defaults: &defaults
docker:
- image: circleci/golang:1.11
jobs:
build:
<<: *defaults
steps:
- checkout
- restore_cache:
keys:
- cache-{{ arch }}-{{ .Branch }}-{{ checksum "go.sum" }}
- cache-{{ arch }}-{{ .Branch }}
- cache
- run: go mod download
- save_cache:
key: cache-{{ arch }}-{{ .Branch }}-{{ checksum "go.sum" }}
paths:
- /go/pkg
- run: go test -mod=readonly
release:
<<: *defaults
steps:
- checkout
- setup_remote_docker
- run:
name: docker login
command: echo $DOCKER_PASSWORD | docker login -u $DOCKER_USER --password-stdin
- run:
name: goreleaser
command: curl -sL https://git.io/goreleaser | bash
workflows:
version: 2
main:
jobs:
- build
release:
jobs:
- release:
filters:
tags:
only: /\d+\.\d+\.\d+/
branches:
ignore: /.*/

1
.gitignore vendored
View file

@ -1,6 +1,5 @@
/.idea/
/*.idl
*.iml
/dist/
/vendor/
/rcon-cli

51
.goreleaser.yml Normal file
View file

@ -0,0 +1,51 @@
project_name: rcon-cli
before:
hooks:
- go mod download
release:
github:
owner: itzg
name: rcon-cli
builds:
- goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm
main: .
binary: rcon-cli
env:
- CGO_ENABLED=0
archive:
format: tar.gz
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{
.Arm }}{{ end }}'
files:
- licence*
- LICENCE*
- license*
- LICENSE*
- readme*
- README*
- changelog*
- CHANGELOG*
snapshot:
name_template: SNAPSHOT-{{ .Commit }}
changelog:
filters:
exclude:
- '^ci:'
dockers:
- image_templates:
- "itzg/rcon-cli:{{ .Tag }}"
- "itzg/rcon-cli:latest"
goos: linux
goarch: amd64
scoop:
bucket:
owner: itzg
name: scoop-bucket
license: Apache2
description: Enables REST-like access to HTML pages by scraping and parsing them into JSON

4
DEVELOPMENT.md Normal file
View file

@ -0,0 +1,4 @@
# Creating a release
Tag the source repository and push the tag. The CircleCI configuration includes a `release`
workflow that will take care of invoking goreleaser.

View file

@ -1,14 +1,3 @@
FROM docker.io/golang:1.16 AS build
COPY ./go.* /src/
WORKDIR /src
RUN go mod download
COPY . /src
RUN CGO_ENABLED=0 go build -o /rcon-cli
FROM scratch
COPY --from=build /rcon-cli /
ENTRYPOINT [ "/rcon-cli" ]
COPY rcon-cli /
ENTRYPOINT /rcon-cli

View file

@ -187,9 +187,8 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2021 Augusto Dwenger J.
Copyright 2017 Rackspace
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

View file

@ -1,48 +1,37 @@
# rcon-cli
A little RCON cli written in golang.
This is a fork from [itzg/rcon-cli](https://github.com/itzg/rcon-cli) with following extra features:
- Provied a smaller binary (ca. 50% smaller).
- Replace base RCON lib from [james4k/rcon](https://github.com/james4k/rcon) with [hamburghammer/rcon](https://github.com/hamburghammer/rcon).
- Remove `config.yml` support.
- Change the `Dockerfile` to have build support.
Note: *This tool is primarily being maintained to support minecraft RCON implementation.*
A little RCON cli based on james4k's RCON library for golang.
## Installation
### From Source
Clone the repository and install it with `go install` (requires working `go` installation)
### Docker/Podman
Clone the repository and use docker/podman to build a image with the executable `docker build -t hamburghammer/rcon-cli .`
Start the image `docker run hamburghammer/rcon-cli -h`
1. Download the appropriate binary for your platform from the [latest releases](https://github.com/itzg/rcon-cli/releases/latest)
2. On UNIX-y platforms, set the binary to be executable
Done.
## Usage
```text
rcon-cli is a CLI to interact with a RCON server.
It can be run in an interactive mode or to execute a single command.
rcon-cli is a CLI for attaching to an RCON enabled game server, such as Minecraft.
Without any additional arguments, the CLI will start an interactive session with
the RCON server.
USAGE:
rcon-cli [FLAGS] [RCON command ...]
FLAGS:
-h, --help Prints this help message and exits.
--host string RCON server's hostname. (default "localhost")
--password string RCON server's password.
--port string RCON server's port. (default "25575")
If arguments are passed into the CLI, then the arguments are sent
as a single command (joined by spaces), the response is displayed,
and the CLI will exit.
ENVIRONMENT VARIABLE:
All flags can be set through the flag name in capslock with the RCON_CLI_ prefix (see examples).
Flags have allways priority over env vars!
Usage:
rcon-cli [flags] [RCON command ...]
EXAMPLES:
rcon-cli --host 127.0.0.1 --port 25575
rcon-cli --password admin123 stop
RCON_CLI_PORT=25575 rcon-cli stop
Examples:
rcon-cli --host mc1 --port 25575
rcon-cli --port 25575 stop
RCON_PORT=25575 rcon-cli stop
Flags:
--config string config file (default is $HOME/.rcon-cli.yaml)
--host string RCON server's hostname (default "localhost")
--password string RCON server's password
--port int Server's RCON port (default 27015)
```
## LICENSE
The software is licensed under the [Apache-2.0](LICENSE) license.

93
cli/entry.go Normal file
View file

@ -0,0 +1,93 @@
//
// Copyright 2017 Rackspace
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cli
import (
"github.com/james4k/rcon"
"os"
"log"
"bufio"
"io"
"fmt"
"strings"
)
func Start(hostPort string, password string, in io.Reader, out io.Writer) {
remoteConsole, err := rcon.Dial(hostPort, password)
if err != nil {
log.Fatal("Failed to connect to RCON server", err)
}
defer remoteConsole.Close()
scanner := bufio.NewScanner(in)
out.Write([]byte("> "))
for scanner.Scan() {
cmd := scanner.Text()
reqId, err := remoteConsole.Write(cmd)
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to send command:", err.Error())
continue
}
resp, respReqId, err := remoteConsole.Read()
if err != nil {
if err == io.EOF {
return
}
fmt.Fprintln(os.Stderr, "Failed to read command:", err.Error())
continue
}
if reqId != respReqId {
fmt.Fprintln(out, "Weird. This response is for another request.")
}
fmt.Fprintln(out, resp)
out.Write([]byte("> "))
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading standard input:", err)
}
}
func Execute(hostPort string, password string, out io.Writer, command ...string) {
remoteConsole, err := rcon.Dial(hostPort, password)
if err != nil {
log.Fatal("Failed to connect to RCON server", err)
}
defer remoteConsole.Close()
preparedCmd := strings.Join(command, " ")
reqId, err := remoteConsole.Write(preparedCmd)
resp, respReqId, err := remoteConsole.Read()
if err != nil {
if err == io.EOF {
return
}
fmt.Fprintln(os.Stderr, "Failed to read command:", err.Error())
return
}
if reqId != respReqId {
fmt.Fprintln(out, "Weird. This response is for another request.")
}
fmt.Fprintln(out, resp)
}

103
cmd/root.go Normal file
View file

@ -0,0 +1,103 @@
// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"log"
"os"
"github.com/itzg/rcon-cli/cli"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"net"
"strconv"
)
var (
cfgFile string
)
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "rcon-cli [flags] [RCON command ...]",
Short: "A CLI for attaching to an RCON enabled game server",
Example: `
rcon-cli --host mc1 --port 25575
rcon-cli --port 25575 stop
RCON_PORT=25575 rcon-cli stop
`,
Long: `
rcon-cli is a CLI for attaching to an RCON enabled game server, such as Minecraft.
Without any additional arguments, the CLI will start an interactive session with
the RCON server.
If arguments are passed into the CLI, then the arguments are sent
as a single command (joined by spaces), the response is displayed,
and the CLI will exit.
`,
Run: func(cmd *cobra.Command, args []string) {
hostPort := net.JoinHostPort(viper.GetString("host"), strconv.Itoa(viper.GetInt("port")))
password := viper.GetString("password")
if len(args) == 0 {
cli.Start(hostPort, password, os.Stdin, os.Stdout)
} else {
cli.Execute(hostPort, password, os.Stdout, args...)
}
},
}
// Execute adds all child commands to the root command sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(-1)
}
}
func init() {
cobra.OnInitialize(initConfig)
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.rcon-cli.yaml)")
RootCmd.PersistentFlags().String("host", "localhost", "RCON server's hostname")
RootCmd.PersistentFlags().String("password", "", "RCON server's password")
RootCmd.PersistentFlags().Int("port", 27015, "Server's RCON port")
err := viper.BindPFlags(RootCmd.PersistentFlags())
if err != nil {
log.Fatal(err)
}
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" { // enable ability to specify config file via flag
viper.SetConfigFile(cfgFile)
} else {
viper.SetConfigName(".rcon-cli") // name of config file (without extension)
viper.AddConfigPath("$HOME") // adding home directory as first search path
}
// This will allow for env vars like RCON_PORT
viper.SetEnvPrefix("rcon")
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}

11
go.mod
View file

@ -1,8 +1,9 @@
module github.com/hamburghammer/rcon-cli
module github.com/itzg/rcon-cli
require (
github.com/hamburghammer/rcon v1.0.1
github.com/spf13/pflag v1.0.5
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/james4k/rcon v0.0.0-20120923215419-8fbb8268b60a
github.com/spf13/cobra v0.0.3
github.com/spf13/viper v1.3.1
)
go 1.13

53
go.sum
View file

@ -1,4 +1,49 @@
github.com/hamburghammer/rcon v1.0.1 h1:VRxMQdHVDn4cCuakrJkNMNNU7rsvrxNU8J2C7aBvzqs=
github.com/hamburghammer/rcon v1.0.1/go.mod h1:kAh73BJ+hmOzggl8k7oPJrolw7FEjKx9LvCzJH9VOF0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/james4k/rcon v0.0.0-20120923215419-8fbb8268b60a h1:JxcWget6X/VfBMKxPIc28Jel37LGREut2fpV+ObkwJ0=
github.com/james4k/rcon v0.0.0-20120923215419-8fbb8268b60a/go.mod h1:1qNVsDcmNQDsAXYfUuF/Z0rtK5eT8x9D6Pi7S3PjXAg=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38=
github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

234
main.go
View file

@ -1,223 +1,21 @@
// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"errors"
"fmt"
"io"
"net"
"os"
"runtime"
"strings"
"github.com/hamburghammer/rcon"
flags "github.com/spf13/pflag"
)
const (
defaultHost = "localhost"
defaultPort = "25575"
defaultPassword = ""
envVarPrefix = "RCON_CLI_"
)
// resetColor is the ASCII code for resetting the color.
const resetColor = "\u001B[0m"
// colors a map with the ASCII color codes for Unix terms.
var colors = map[string]string{
"0": "\u001B[30m", // black
"1": "\u001B[34m", // dark blue
"2": "\u001B[32m", // dark green
"3": "\u001B[36m", // dark aqua
"4": "\u001B[31m", // dark red
"5": "\u001B[35m", // dark purple
"6": "\u001B[33m", // gold
"7": "\u001B[37m", // gray
"8": "\u001B[30m", // dark gray
"9": "\u001B[34m", // blue
"a": "\u001B[32m", // green
"b": "\u001B[32m", // aqua
"c": "\u001B[31m", // red
"d": "\u001B[35m", // light purple
"e": "\u001B[33m", // yellow
"f": "\u001B[37m", // white
"k": "", // random
"m": "\u001B[9m", // strikethrough
"o": "\u001B[3m", // italic
"l": "\u001B[1m", // bold
"n": "\u001B[4m", // underline
"r": resetColor, // reset
}
// vars holding the parsed configuration
var (
host string
port string
password string
help bool
)
import "github.com/itzg/rcon-cli/cmd"
func main() {
commands, _ := parseArgs(os.Args[1:])
loadEnvIfNotSet()
if help {
printHelp()
os.Exit(0)
return
}
err := run(strings.Join(commands, " "))
if err != nil {
_, err = fmt.Fprintln(os.Stderr, err.Error())
if err != nil {
panic(err)
}
os.Exit(1)
}
}
func parseArgs(args []string) ([]string, error) {
flags.StringVar(&host, "host", defaultHost, "RCON server's hostname.")
flags.StringVar(&port, "port", defaultPort, "RCON server's port.")
flags.StringVar(&password, "password", defaultPassword, "RCON server's password.")
flags.BoolVarP(&help, "help", "h", false, "Prints this help message and exits.")
err := flags.CommandLine.Parse(args)
if err != nil {
return []string{}, err
}
command := flags.Args()
return command, nil
}
func loadEnvIfNotSet() {
envHost := os.Getenv(fmt.Sprintf("%sHOST", envVarPrefix))
if envHost != "" && host == defaultHost {
host = envHost
}
envPort := os.Getenv(fmt.Sprintf("%sPORT", envVarPrefix))
if envPort != "" && port == defaultPort {
port = envPort
}
envPassword := os.Getenv(fmt.Sprintf("%sPASSWORD", envVarPrefix))
if envPassword != "" && password == defaultPassword {
password = envPassword
}
}
func run(cmd string) error {
hostPort := net.JoinHostPort(host, port)
remoteConsole, err := rcon.Dial(hostPort, password)
if err != nil {
return fmt.Errorf("failed to connect to RCON server: %w", err)
}
defer remoteConsole.Close()
if len(cmd) == 0 {
err = executeInteractive(remoteConsole)
} else {
resp, err := execute(cmd, remoteConsole)
if err != nil {
return err
}
_, err = fmt.Println(resp)
}
return err
}
func printHelp() {
_, _ = fmt.Println(`rcon-cli is a CLI to interact with a RCON server.
It can be run in an interactive mode or to execute a single command.
USAGE:
rcon-cli [FLAGS] [RCON command ...]
FLAGS:`)
flags.PrintDefaults()
fmt.Printf(`
ENVIRONMENT VARIABLE:
All flags can be set through the flag name in capslock with the %s prefix (see examples).
Flags have allways priority over env vars!
EXAMPLES:
rcon-cli --host 127.0.0.1 --port 25575
rcon-cli --password admin123 stop
%sPORT=25575 rcon-cli stop
`, envVarPrefix, envVarPrefix)
}
func executeInteractive(remoteConsole *rcon.RemoteConsole) error {
scanner := bufio.NewScanner(os.Stdin)
_, _ = fmt.Println("To quit the session type 'exit'.")
_, _ = fmt.Print("> ")
for scanner.Scan() {
cmd := scanner.Text()
if cmd == "exit" {
return nil
}
resp, err := execute(cmd, remoteConsole)
if err != nil {
return err
}
_, _ = fmt.Println(resp)
_, _ = fmt.Print("> ")
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("reading standard input: %w", err)
}
return nil
}
func execute(cmd string, remoteConsole *rcon.RemoteConsole) (string, error) {
resp := ""
reqID, err := remoteConsole.Write(cmd)
if err != nil {
return resp, fmt.Errorf("failed to send command: %w", err)
}
resp, respID, err := remoteConsole.Read()
if err != nil {
if err == io.EOF {
return resp, nil
}
return resp, fmt.Errorf("failed to read command: %w", err)
}
if reqID != respID {
return resp, errors.New("the response id didn't match the request id")
}
resp = colorize(resp)
return resp, nil
}
// colorize tries to add the color codes for the terminal.
// works on none windows machines.
func colorize(str string) string {
const sectionSign = "§"
for code := range colors {
if runtime.GOOS == "windows" {
str = strings.ReplaceAll(str, sectionSign+code, "")
} else {
str = strings.ReplaceAll(str, sectionSign+code, colors[code])
}
}
// reset color after each new line
if runtime.GOOS != "windows" {
return strings.ReplaceAll(str, "\n", "\n"+resetColor)
}
return str
cmd.Execute()
}