Files
panel/agent/internal/rcon/rcon.go
T
2026-07-14 23:18:05 -07:00

82 lines
2.9 KiB
Go

// Package rcon provides pluggable RCON adapters. Games speak different
// dialects — Source RCON (Counter-Strike, Minecraft with Mojang auth),
// telnet line protocol (7 Days to Die), HTTP admin APIs (some community
// implementations), stdin-pipe for headless servers without a network
// surface. The Agent picks an adapter based on the module manifest's
// `rcon.adapter` field.
package rcon
import (
"context"
"fmt"
"io"
"time"
)
// Client is a connected RCON session. Exec runs one command and returns its
// raw text response. Clients must be safe for concurrent Exec calls — the
// poll loop and a future ad-hoc-command path both need to call through the
// same session.
type Client interface {
Exec(ctx context.Context, cmd string) (string, error)
Close() error
}
// DialOptions configures one RCON connection attempt.
type DialOptions struct {
Host string
Port int
Password string
ConnectTimeout time.Duration
// ReadIdle is how long to wait with no new bytes before treating an Exec
// response as complete. Telnet has no packet framing, so this heuristic
// is needed; Source RCON overrides it to 0 (ignored).
ReadIdle time.Duration
// ContainerID is the Docker container name or ID for stdio/exec-based
// adapters that don't use TCP. Ignored by telnet/source_rcon.
ContainerID string
// Stdio is the backend that knows how to attach to a container's
// stdin/stdout. Provided by the Docker runtime when the adapter is
// "stdio". Nil for TCP adapters.
Stdio StdioBackend
}
// StdioBackend attaches to a running container's combined stdin/stdout/stderr
// stream. Needed by the stdio adapter for games whose only admin console is
// stdin (Valheim, Terraria, Barotrauma, Enshrouded, Sons Of The Forest).
//
// Returned ReadWriteCloser writes go to the container's stdin; reads are the
// demux-multiplexed stdout+stderr. Close shuts down the hijacked connection.
type StdioBackend interface {
AttachStdio(ctx context.Context, containerID string) (io.ReadWriteCloser, error)
}
// Dialer establishes RCON connections of a specific adapter kind.
type Dialer interface {
Dial(ctx context.Context, opts DialOptions) (Client, error)
Name() string
}
// DialerFor returns the dialer for the given adapter id, or an error if the
// adapter is unknown. Adapter ids come directly from the module manifest's
// `rcon.adapter` field.
func DialerFor(adapter string) (Dialer, error) {
switch adapter {
case "telnet":
return &TelnetDialer{}, nil
case "source_rcon":
return &SourceDialer{}, nil
case "stdio":
return &StdioDialer{}, nil
case "websocket_rcon":
return &WebSocketDialer{}, nil
case "docker_exec_rcon":
return &DockerExecDialer{}, nil
case "be_rcon":
return &BattlEyeDialer{}, nil
default:
return nil, fmt.Errorf("unknown rcon adapter %q (known: telnet, source_rcon, docker_exec_rcon, stdio, websocket_rcon, be_rcon)", adapter)
}
}