// 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) } }