mirror of
https://github.com/hamburghammer/rcon-cli.git
synced 2024-11-01 03:03:52 +01:00
Compare commits
32 commits
Author | SHA1 | Date | |
---|---|---|---|
8f3304b56f | |||
f515849caa | |||
2608746df0 | |||
017288f674 | |||
54b2977dc5 | |||
a532d2ef99 | |||
00a1fd3f0e | |||
5490b90887 | |||
9db0f9f923 | |||
df5d02cbe4 | |||
6ee166e754 | |||
07cae8959a | |||
db64e663c7 | |||
5d78c7478a | |||
e98f292695 | |||
aed30e4a74 | |||
e3e863fe36 | |||
2ef044623a | |||
01257b69a7 | |||
fd3c96e6b7 | |||
ac41268ddb | |||
0b56f020dc | |||
|
31d6d63079 | ||
|
c811acde0d | ||
|
6ab7bbd412 | ||
|
ef0727479c | ||
|
0425a4511a | ||
|
6c9910b1aa | ||
|
9f807041e5 | ||
|
b80e38df63 | ||
|
136449d1ce | ||
|
722c7b7e2e |
13 changed files with 286 additions and 506 deletions
|
@ -1,62 +0,0 @@
|
||||||
version: 2
|
|
||||||
|
|
||||||
defaults: &defaults
|
|
||||||
docker:
|
|
||||||
- image: circleci/golang:1.10
|
|
||||||
|
|
||||||
working_directory: /go/src/github.com/itzg/rcon-cli
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
<<: *defaults
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gopkg.lock" }}
|
|
||||||
- cache-{{ arch }}-{{ .Branch }}
|
|
||||||
- cache
|
|
||||||
|
|
||||||
- run: dep ensure -vendor-only
|
|
||||||
|
|
||||||
- save_cache:
|
|
||||||
key: cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gopkg.lock" }}
|
|
||||||
paths:
|
|
||||||
- vendor
|
|
||||||
|
|
||||||
- run: go test
|
|
||||||
|
|
||||||
release:
|
|
||||||
<<: *defaults
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
|
|
||||||
- run: dep ensure -vendor-only
|
|
||||||
|
|
||||||
- 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:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /.*/
|
|
||||||
|
|
||||||
- release:
|
|
||||||
requires:
|
|
||||||
- build
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /^\d+\.\d+\.\d+/
|
|
||||||
branches:
|
|
||||||
ignore: /.*/
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,5 +1,8 @@
|
||||||
/.idea/
|
/.idea/
|
||||||
/*.idl
|
/*.idl
|
||||||
|
*.iml
|
||||||
/dist/
|
/dist/
|
||||||
/vendor/
|
/vendor/
|
||||||
/rcon-cli
|
/rcon-cli
|
||||||
|
/rcon-cli.exe
|
||||||
|
/config.yml
|
|
@ -1,49 +0,0 @@
|
||||||
project_name: rcon-cli
|
|
||||||
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: itzg/rcon-cli
|
|
||||||
goos: linux
|
|
||||||
goarch: amd64
|
|
||||||
tag_templates:
|
|
||||||
- "{{ .Tag }}"
|
|
||||||
- latest
|
|
||||||
scoop:
|
|
||||||
bucket:
|
|
||||||
owner: itzg
|
|
||||||
name: scoop-bucket
|
|
||||||
license: Apache2
|
|
||||||
description: Enables REST-like access to HTML pages by scraping and parsing them into JSON
|
|
15
Dockerfile
15
Dockerfile
|
@ -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
|
FROM scratch
|
||||||
COPY rcon-cli /
|
COPY --from=build /rcon-cli /
|
||||||
ENTRYPOINT /rcon-cli
|
|
||||||
|
ENTRYPOINT [ "/rcon-cli" ]
|
119
Gopkg.lock
generated
119
Gopkg.lock
generated
|
@ -1,119 +0,0 @@
|
||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/fsnotify/fsnotify"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "629574ca2a5df945712d3079857300b5e4da0236"
|
|
||||||
version = "v1.4.2"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/hashicorp/hcl"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"hcl/ast",
|
|
||||||
"hcl/parser",
|
|
||||||
"hcl/scanner",
|
|
||||||
"hcl/strconv",
|
|
||||||
"hcl/token",
|
|
||||||
"json/parser",
|
|
||||||
"json/scanner",
|
|
||||||
"json/token"
|
|
||||||
]
|
|
||||||
revision = "630949a3c5fa3c613328e1b8256052cbc2327c9b"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/inconshreveable/mousetrap"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
|
||||||
version = "v1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/james4k/rcon"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "8fbb8268b60ac3303900326e126be05d504ab63d"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/magiconair/properties"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "51463bfca2576e06c62a8504b5c0f06d61312647"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/mitchellh/mapstructure"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "53818660ed4955e899c0bcafa97299a388bd7c8e"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/pelletier/go-buffruneio"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "c37440a7cf42ac63b919c752ca73a85067e05992"
|
|
||||||
version = "v0.2.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/pelletier/go-toml"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "fe206efb84b2bc8e8cfafe6b4c1826622be969e3"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/spf13/afero"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"mem"
|
|
||||||
]
|
|
||||||
revision = "9be650865eab0c12963d8753212f4f9c66cdcf12"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/spf13/cast"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "ce135a4ebeee6cfe9a26c93ee0d37825f26113c7"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/spf13/cobra"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "fcd0c5a1df88f5d6784cb4feead962c3f3d0b66c"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/spf13/jwalterweatherman"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "fa7ca7e836cf3a8bb4ebf799f472c12d7e903d66"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/spf13/pflag"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "9ff6c6923cfffbcd502984b8e0c80539a94968b7"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/spf13/viper"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "84f94806c67f59dd7ae87bc5351f7a9c94a4558d"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "golang.org/x/sys"
|
|
||||||
packages = ["unix"]
|
|
||||||
revision = "76cc09b634294339fa19ec41b5f2a0b3932cea8b"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "golang.org/x/text"
|
|
||||||
packages = [
|
|
||||||
"internal/gen",
|
|
||||||
"internal/triegen",
|
|
||||||
"internal/ucd",
|
|
||||||
"transform",
|
|
||||||
"unicode/cldr",
|
|
||||||
"unicode/norm"
|
|
||||||
]
|
|
||||||
revision = "f4b4367115ec2de254587813edaa901bc1c723a8"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "gopkg.in/yaml.v2"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "a3f3340b5840cee44f372bddb5880fcbc419b46a"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
inputs-digest = "695d97daca68e207f4dcdb27c59f8306dc43ca0c87848df23a8f13bdbaa084ce"
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
34
Gopkg.toml
34
Gopkg.toml
|
@ -1,34 +0,0 @@
|
||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
#
|
|
||||||
# [prune]
|
|
||||||
# non-go = false
|
|
||||||
# go-tests = true
|
|
||||||
# unused-packages = true
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/james4k/rcon"
|
|
||||||
|
|
||||||
[prune]
|
|
||||||
go-tests = true
|
|
||||||
unused-packages = true
|
|
3
LICENSE
3
LICENSE
|
@ -187,7 +187,8 @@
|
||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
59
README.md
59
README.md
|
@ -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
|
## 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
|
## Usage
|
||||||
|
|
||||||
```text
|
```text
|
||||||
rcon-cli is a CLI for attaching to an RCON enabled game server, such as Minecraft.
|
rcon-cli is a CLI to interact with a RCON server.
|
||||||
Without any additional arguments, the CLI will start an interactive session with
|
It can be run in an interactive mode or to execute a single command.
|
||||||
the RCON server.
|
|
||||||
|
|
||||||
If arguments are passed into the CLI, then the arguments are sent
|
USAGE:
|
||||||
as a single command (joined by spaces), the response is displayed,
|
rcon-cli [FLAGS] [RCON command ...]
|
||||||
and the CLI will exit.
|
|
||||||
|
|
||||||
Usage:
|
FLAGS:
|
||||||
rcon-cli [flags] [RCON command ...]
|
-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")
|
||||||
|
|
||||||
Examples:
|
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!
|
||||||
|
|
||||||
rcon-cli --host mc1 --port 25575
|
EXAMPLES:
|
||||||
rcon-cli --port 25575 stop
|
rcon-cli --host 127.0.0.1 --port 25575
|
||||||
RCON_PORT=25575 rcon-cli stop
|
rcon-cli --password admin123 stop
|
||||||
|
RCON_CLI_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
93
cli/entry.go
|
@ -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)
|
|
||||||
}
|
|
103
cmd/root.go
103
cmd/root.go
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
8
go.mod
Normal file
8
go.mod
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
module github.com/hamburghammer/rcon-cli
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/hamburghammer/rcon v1.0.1
|
||||||
|
github.com/spf13/pflag v1.0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
go 1.13
|
4
go.sum
Normal file
4
go.sum
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
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
234
main.go
|
@ -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
|
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() {
|
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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue