mirror of
https://github.com/hamburghammer/rcon-cli.git
synced 2024-10-31 19:03:50 +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/
|
||||
/*.idl
|
||||
*.iml
|
||||
/dist/
|
||||
/vendor/
|
||||
/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
|
||||
COPY rcon-cli /
|
||||
ENTRYPOINT /rcon-cli
|
||||
COPY --from=build /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
|
||||
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.
|
||||
|
|
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
|
||||
### 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 ...]
|
||||
|
||||
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")
|
||||
|
||||
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
|
||||
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.
|
||||
|
|
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
|
||||
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue