Compare commits

...

31 commits

Author SHA1 Message Date
Augusto Dwenger J. 8f3304b56f Fix prompt indicator 2023-07-28 19:10:04 +02:00
Augusto Dwenger J. f515849caa Remove color chars on windows
We only support Unix like systems for colors and windows gets the color
information removed to reduce the output clutter.
2023-07-28 18:20:11 +02:00
Augusto Dwenger J. 2608746df0 Add note about the tested support for minecraft in readme 2023-07-28 14:52:31 +02:00
Augusto Dwenger J. 017288f674 Add license section to the readme 2023-07-28 14:48:09 +02:00
Augusto Dwenger J. 54b2977dc5 Add craftbukkit color support
Except on Windows.

Taken from b39477abe1
2023-07-28 14:30:17 +02:00
Augusto Dwenger J. a532d2ef99 Acknowledge return values from printing functions
Most of the time we don't care about the return values of a print
function, but we should at leased visualize that there is a return
value that we don't care about or want to ignore with a "_".
2023-07-28 13:51:56 +02:00
Augusto Dwenger J. 00a1fd3f0e
Change module name to match repository name 2022-03-04 01:13:46 +01:00
Augusto Dwenger J. 5490b90887
Update rcon lib to version 1.0.1
Fixes parsing of empty and null containing packets
2022-03-04 01:06:10 +01:00
Augusto Dwenger J. 9db0f9f923
Refactor cmd execution function chain to be more readable
The execution was originaly build to be used as a lib, but this is no
longer the case and so I removed all the `io.Writer` and `io.Reader`
interactions. The app reads now from Stdin and writes to Stdout as it
did before.
It also redoes the error handling so that all errors that are not
treated are being handled in the `main` function.
2022-02-03 00:27:16 +01:00
Augusto Dwenger J. df5d02cbe4
Extract shared cmd execution into own function 2022-02-02 23:20:08 +01:00
Augusto Dwenger J. 6ee166e754
Move cli/entry.go into main.go
This is to remove the directory that is used in cobra setups.
While we could kept the file inside the root directory but it only
contains two small functions that can be moved into the `main.go` file.
2022-02-02 22:44:49 +01:00
Augusto Dwenger J. 07cae8959a Remove old project badges 2021-03-09 00:56:25 +01:00
Augusto Dwenger J. db64e663c7 Add exit key word for interactive session to quit it 2021-03-09 00:54:18 +01:00
Augusto Dwenger J. 5d78c7478a Update README with fork information 2021-03-08 16:57:30 +01:00
Augusto Dwenger J. e98f292695 Replace james4k/rcon with hamburghamer/rcon
The rcon lib from hamburghammer is a rework of james4k ones.
It provides a better readable implementation.
2021-03-08 16:44:58 +01:00
Augusto Dwenger J. aed30e4a74 Remove cobra as cli flag manager
This reduces the package size by ca. 50%. One draw back of this
change is that there is no longer support for the `config.yml` file.
There is also a change inside the env vars naming: The variables have to
start with `RCON_CLI`.
2021-03-08 16:39:07 +01:00
Augusto Dwenger J. e3e863fe36 Add build stage to the docker image build 2021-03-08 00:26:53 +01:00
Augusto Dwenger J. 2ef044623a Remove gorelease and circleci config 2021-03-08 00:03:47 +01:00
Augusto Dwenger J. 01257b69a7 Fix golint warnings 2021-03-08 00:00:25 +01:00
Augusto Dwenger J. fd3c96e6b7 Remove license/copyright holder headers 2021-03-07 23:54:25 +01:00
Augusto Dwenger J. ac41268ddb Add copyright holder to the license file
Used the information from the projects files to update the copyright
holder part inside the license file.
2021-03-07 23:51:58 +01:00
Augusto Dwenger J. 0b56f020dc Fix fmt 2021-03-07 23:46:21 +01:00
Geoff Bourne 31d6d63079 docs: added release and build badges 2020-05-16 12:42:53 -05:00
Geoff Bourne c811acde0d ci: fixed archives config 2020-05-16 12:39:23 -05:00
futek 6ab7bbd412
Use ENTRYPOINT exec form instead of shell form (#6)
The image has no shell at /bin/sh so the shell form does not work
2020-05-16 12:16:29 -05:00
Geoff Bourne ef0727479c ci: go mod tidy 2019-09-17 21:41:27 -05:00
Geoff Bourne 0425a4511a Add 386 build and upgrade to Go 1.13 2019-09-17 21:37:09 -05:00
Geoff Bourne 6c9910b1aa
Merge pull request #3 from zlotny/patch-1
Adds config file information on readme
2019-05-27 21:12:35 -05:00
Andrés Vieira 9f807041e5
Adds config file information on readme 2019-05-27 14:40:45 +02:00
Geoff Bourne b80e38df63 Add armv7 (RPi) support 2019-05-05 13:55:27 -05:00
Geoff Bourne 136449d1ce Add arm64 support 2019-05-05 09:21:06 -05:00
12 changed files with 280 additions and 405 deletions

View file

@ -1,54 +0,0 @@
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+)*(-.*)*/
branches:
ignore: /.*/

1
.gitignore vendored
View file

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

View file

@ -1,51 +0,0 @@
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

View file

@ -1,4 +0,0 @@
# 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,3 +1,14 @@
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 rcon-cli /
ENTRYPOINT /rcon-cli
COPY --from=build /rcon-cli /
ENTRYPOINT [ "/rcon-cli" ]

View file

@ -187,8 +187,9 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2021 Augusto Dwenger J.
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

View file

@ -1,37 +1,48 @@
A little RCON cli based on james4k's RCON library for golang.
# 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.*
## Installation
### From Source
Clone the repository and install it with `go install` (requires working `go` installation)
1. Download the appropriate binary for your platform from the [latest releases](https://github.com/itzg/rcon-cli/releases/latest)
### 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`
2. On UNIX-y platforms, set the binary to be executable
Done.
## Usage
```text
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.
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.
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.
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")
Usage:
rcon-cli [flags] [RCON command ...]
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!
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)
EXAMPLES:
rcon-cli --host 127.0.0.1 --port 25575
rcon-cli --password admin123 stop
RCON_CLI_PORT=25575 rcon-cli stop
```
## LICENSE
The software is licensed under the [Apache-2.0](LICENSE) license.

View file

@ -1,93 +0,0 @@
//
// 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)
}

View file

@ -1,103 +0,0 @@
// 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,9 +1,8 @@
module github.com/itzg/rcon-cli
module github.com/hamburghammer/rcon-cli
require (
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
github.com/hamburghammer/rcon v1.0.1
github.com/spf13/pflag v1.0.5
)
go 1.13

53
go.sum
View file

@ -1,49 +1,4 @@
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=
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=

234
main.go
View file

@ -1,21 +1,223 @@
// 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 "github.com/itzg/rcon-cli/cmd"
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
)
func main() {
cmd.Execute()
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
}