All checks were successful
Test Asgard Runner / test (push) Successful in 3s
Wire gorilla/websocket into the Go companion agent to send arbitrary console commands (e.g. oxide.reload BetterLoot) to the Rust Dedicated Server's WebRCON endpoint. Adds RCON_PORT and RCON_PASSWORD env vars, a new "command" action on the existing cmd.server NATS subject, and the internal/rcon package that handles the JSON-over-WebSocket protocol. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
81 lines
1.9 KiB
Go
81 lines
1.9 KiB
Go
package rcon
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
// RconRequest is the JSON payload sent to Rust's WebRCON.
|
|
type RconRequest struct {
|
|
Identifier int `json:"Identifier"`
|
|
Message string `json:"Message"`
|
|
Name string `json:"Name"`
|
|
}
|
|
|
|
// RconResponse is the JSON payload received from Rust's WebRCON.
|
|
type RconResponse struct {
|
|
Identifier int `json:"Identifier"`
|
|
Message string `json:"Message"`
|
|
Type string `json:"Type"`
|
|
}
|
|
|
|
// SendCommand opens a WebSocket to the Rust server's RCON port, sends
|
|
// a single command, reads the response, and closes the connection.
|
|
func SendCommand(port int, password string, command string) (string, error) {
|
|
u := url.URL{
|
|
Scheme: "ws",
|
|
Host: fmt.Sprintf("127.0.0.1:%d", port),
|
|
Path: fmt.Sprintf("/%s", password),
|
|
}
|
|
|
|
dialer := websocket.Dialer{
|
|
HandshakeTimeout: 5 * time.Second,
|
|
}
|
|
|
|
conn, _, err := dialer.Dial(u.String(), nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("rcon dial failed: %w", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
// Set read deadline
|
|
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
|
|
|
req := RconRequest{
|
|
Identifier: 1,
|
|
Message: command,
|
|
Name: "Corrosion",
|
|
}
|
|
|
|
data, err := json.Marshal(req)
|
|
if err != nil {
|
|
return "", fmt.Errorf("rcon marshal failed: %w", err)
|
|
}
|
|
|
|
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
|
return "", fmt.Errorf("rcon write failed: %w", err)
|
|
}
|
|
|
|
// Read response — may get multiple messages (Generic, Warning, etc.)
|
|
// We want the first response with our Identifier.
|
|
for {
|
|
_, message, err := conn.ReadMessage()
|
|
if err != nil {
|
|
return "", fmt.Errorf("rcon read failed: %w", err)
|
|
}
|
|
|
|
var resp RconResponse
|
|
if err := json.Unmarshal(message, &resp); err != nil {
|
|
continue // skip unparseable messages
|
|
}
|
|
|
|
if resp.Identifier == req.Identifier {
|
|
return resp.Message, nil
|
|
}
|
|
}
|
|
}
|