Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
release:
uses: itzg/github-workflows/.github/workflows/go-with-releaser-image.yml@main
with:
go-version: "1.20.10"
go-version: "1.21.6"
secrets:
image-registry-username: ${{ secrets.DOCKERHUB_USERNAME }}
image-registry-password: ${{ secrets.DOCKERHUB_TOKEN }}
Expand Down
7 changes: 2 additions & 5 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ builds:
- goos:
- linux
goarch:
- 386
- amd64
- arm
- arm64
goarm:
- 6
- 7
- "6"
- "7"
main: .
env:
- CGO_ENABLED=0
Expand All @@ -25,8 +24,6 @@ archives:
- format_overrides:
- goos: windows
format: zip
snapshot:
name_template: SNAPSHOT-{{ .Commit }}
changelog:
filters:
exclude:
Expand Down
116 changes: 75 additions & 41 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"errors"
"flag"
"fmt"
"io"
Expand Down Expand Up @@ -67,14 +68,43 @@ func main() {
logger.Error("Unable to get stdin", zap.Error(err))
}

stdout, err := cmd.StdoutPipe()
if err != nil {
logger.Error("Unable to get stdout", zap.Error(err))
}
if args.RemoteConsole {
stdout, err := cmd.StdoutPipe()
if err != nil {
logger.Error("Unable to get stdout", zap.Error(err))
}

stderr, err := cmd.StderrPipe()
if err != nil {
logger.Error("Unable to get stderr", zap.Error(err))
stderr, err := cmd.StderrPipe()
if err != nil {
logger.Error("Unable to get stderr", zap.Error(err))
}

console := makeConsole(stdin, stdout, stderr)

// Relay stdin between outside and server
if !args.DetachStdin {
go consoleInRoutine(os.Stdin, console, logger)
}

go consoleOutRoutine(os.Stdout, console, stdOutTarget, logger)
go consoleOutRoutine(os.Stderr, console, stdErrTarget, logger)

go runRemoteShellServer(console, logger)

logger.Info("Running with remote console support")
} else {
logger.Debug("Directly assigning stdout/stderr")
// directly assign stdout/err to pass through terminal, if applicable
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if hasRconCli() {
logger.Debug("Directly assigning stdin")
cmd.Stdin = os.Stdin
stdin = os.Stdin
} else {
go relayStdin(logger, stdin)
}
}

err = cmd.Start()
Expand All @@ -93,26 +123,11 @@ func main() {
}
}

console := makeConsole(stdin, stdout, stderr)

// Relay stdin between outside and server
if !args.DetachStdin {
go consoleInRoutine(os.Stdin, console, logger)
}

go consoleOutRoutine(os.Stdout, console, stdOutTarget, logger)
go consoleOutRoutine(os.Stderr, console, stdErrTarget, logger)

// Start the remote server if intended
if args.RemoteConsole {
go startRemoteShellServer(console, logger)
}

ctx, cancel := context.WithCancel(context.Background())
errors := make(chan error, 1)
errorChan := make(chan error, 1)

if args.NamedPipe != "" {
err2 := handleNamedPipe(ctx, args.NamedPipe, stdin, errors)
err2 := handleNamedPipe(ctx, args.NamedPipe, stdin, errorChan)
if err2 != nil {
logger.Fatal("Failed to setup named pipe", zap.Error(err2))
}
Expand All @@ -123,14 +138,12 @@ func main() {
go func() {
waitErr := cmd.Wait()
if waitErr != nil {
if exitErr, ok := waitErr.(*exec.ExitError); ok {
var exitErr *exec.ExitError
if errors.As(waitErr, &exitErr) {
exitCode := exitErr.ExitCode()
logger.Warn("Minecraft server failed. Inspect logs above for errors that indicate cause. DO NOT report this line as an error.",
zap.Int("exitCode", exitCode))
cmdExitChan <- exitCode
} else {
logger.Error("Command failed abnormally", zap.Error(waitErr))
cmdExitChan <- 1
}
return
} else {
Expand All @@ -142,7 +155,7 @@ func main() {
select {
case <-signalChan:
if args.StopServerAnnounceDelay > 0 {
announceStopViaConsole(logger, stdin, args.StopServerAnnounceDelay)
announceStop(logger, stdin, args.StopServerAnnounceDelay)
logger.Info("Sleeping before server stop", zap.Duration("sleepTime", args.StopServerAnnounceDelay))
time.Sleep(args.StopServerAnnounceDelay)
}
Expand All @@ -168,7 +181,7 @@ func main() {
})
}

case namedPipeErr := <-errors:
case namedPipeErr := <-errorChan:
logger.Error("Error during named pipe handling", zap.Error(namedPipeErr))

case exitCode := <-cmdExitChan:
Expand All @@ -180,6 +193,13 @@ func main() {

}

func relayStdin(logger *zap.Logger, stdin io.WriteCloser) {
_, err := io.Copy(stdin, os.Stdin)
if err != nil {
logger.Error("Failed to relay standard input", zap.Error(err))
}
}

func hasRconCli() bool {
if strings.ToUpper(os.Getenv("ENABLE_RCON")) == "TRUE" {
_, err := exec.LookPath("rcon-cli")
Expand All @@ -189,9 +209,7 @@ func hasRconCli() bool {
}
}

func stopWithRconCli() error {
log.Println("Stopping with rcon-cli")

func sendRconCommand(cmd ...string) error {
rconConfigFile := os.Getenv("RCON_CONFIG_FILE")
if rconConfigFile == "" {
port := os.Getenv("RCON_PORT")
Expand All @@ -204,23 +222,39 @@ func stopWithRconCli() error {
password = "minecraft"
}

rconCliCmd := exec.Command("rcon-cli",
"--port", port,
"--password", password,
"stop")
args := []string{"--port", port,
"--password", password}
args = append(args, cmd...)

rconCliCmd := exec.Command("rcon-cli", args...)

return rconCliCmd.Run()
} else {
rconCliCmd := exec.Command("rcon-cli",
"--config", rconConfigFile,
"stop")

args := []string{"--config", rconConfigFile}
args = append(args, cmd...)

rconCliCmd := exec.Command("rcon-cli", args...)

return rconCliCmd.Run()
}
}

func announceStopViaConsole(logger *zap.Logger, stdin io.Writer, shutdownDelay time.Duration) {
func stopWithRconCli() error {
log.Println("Stopping with rcon-cli")

return sendRconCommand("stop")
}

func announceStop(logger *zap.Logger, stdin io.Writer, shutdownDelay time.Duration) {
logger.Info("Sending shutdown announce 'say' to Minecraft server")
if hasRconCli() {
err := sendRconCommand("say", fmt.Sprintf("Server shutting down in %0.f seconds", shutdownDelay.Seconds()))
if err != nil {
logger.Error("Failed to send 'say' command", zap.Error(err))
}
}

_, err := stdin.Write([]byte(fmt.Sprintf("say Server shutting down in %0.f seconds\n", shutdownDelay.Seconds())))
if err != nil {
logger.Error("Failed to write say command to server console", zap.Error(err))
Expand Down
4 changes: 2 additions & 2 deletions remote_shell_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func consoleOutRoutine(output io.Writer, console *Console, target ConsoleTarget,

// Use os.Stdin for console.
func consoleInRoutine(stdIn io.Reader, console *Console, logger *zap.Logger) {
scanner := bufio.NewScanner(os.Stdin)
scanner := bufio.NewScanner(stdIn)
for scanner.Scan() {
text := scanner.Text()
outBytes := []byte(fmt.Sprintf("%s\n", text))
Expand Down Expand Up @@ -229,7 +229,7 @@ func ensureHostKey(logger *zap.Logger) (string, error) {
return keyfilePath, err
}

func startRemoteShellServer(console *Console, logger *zap.Logger) {
func runRemoteShellServer(console *Console, logger *zap.Logger) {
logger.Info("Starting remote shell server on 2222...")
ssh.Handle(func(s ssh.Session) { handleSession(s, console, logger) })

Expand Down