241 lines
6.4 KiB
Go
241 lines
6.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"log"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"SimpleArmaAdmin/internal/crypto"
|
|
"SimpleArmaAdmin/internal/parser"
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
type DevServer struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
EncryptedRcon string `json:"encryptedRcon"`
|
|
}
|
|
|
|
type DecryptedRcon struct {
|
|
Address string `json:"address"`
|
|
Port int `json:"port"`
|
|
Pass string `json:"pass"`
|
|
}
|
|
|
|
func handleServerConfig(server DevServer, masterKey []byte) {
|
|
if server.EncryptedRcon == "" {
|
|
log.Printf("[RCON] Server %s has no RCON configuration", server.Name)
|
|
return
|
|
}
|
|
|
|
// 1. Decode Base64
|
|
encryptedBytes, err := base64.StdEncoding.DecodeString(server.EncryptedRcon)
|
|
if err != nil {
|
|
log.Printf("[RCON] Failed to decode RCON blob for %s: %v", server.Name, err)
|
|
return
|
|
}
|
|
|
|
// 2. Decrypt using local Master Key (E2EE)
|
|
decryptedJSON, err := crypto.Decrypt(encryptedBytes, masterKey)
|
|
if err != nil {
|
|
log.Printf("[RCON] Failed to decrypt RCON for %s: %v (Key mismatch?)", server.Name, err)
|
|
return
|
|
}
|
|
|
|
// 3. Parse RCON parameters
|
|
var rcon DecryptedRcon
|
|
if err := json.Unmarshal(decryptedJSON, &rcon); err != nil {
|
|
log.Printf("[RCON] Failed to parse decrypted RCON JSON for %s: %v", server.Name, err)
|
|
return
|
|
}
|
|
|
|
log.Printf("[RCON] Successfully decrypted credentials for %s", server.Name)
|
|
log.Printf("[RCON] Target established: %s:%d (Auth secret verified)", rcon.Address, rcon.Port)
|
|
|
|
// TODO: Establish persistent RCON connection
|
|
}
|
|
|
|
var mockLogs = []string{
|
|
"12:30:01.122 SCRIPT : [RJSSupport][Chat] [Global] Zauberklöte: hi, leute kurze frage. zock seit monaten wieder mal arma, was ist aus dem gtg#4 und #5 geworden, da ist ja nix los",
|
|
"09:37:50.865 DEFAULT : BattlEye Server: 'Player #0 Mike1Delta (92.209.175.19:6679) connected'",
|
|
"13:29:19.727 SCRIPT : [RJSSupport][Chat] [Global] 纱雾.: WHAT",
|
|
"09:38:53.842 DEFAULT : BattlEye Server: 'Player #0 Mike1Delta disconnected'",
|
|
"14:56:34.622 SCRIPT : [RJSSupport][Chat] [Global] Toope: help?",
|
|
"15:04:22.868 SCRIPT : [RJSSupport][Chat] [Global] vatrano: Transpo 5-10min abwesend",
|
|
}
|
|
|
|
// extractPlayerName extracts the player name from event content
|
|
func extractPlayerName(content string) string {
|
|
// For JOIN/LEAVE: "Mike1Delta connected to server"
|
|
if strings.Contains(content, "connected to server") {
|
|
parts := strings.Split(content, " connected")
|
|
if len(parts) > 0 {
|
|
return strings.TrimSpace(parts[0])
|
|
}
|
|
}
|
|
if strings.Contains(content, "left the server") {
|
|
parts := strings.Split(content, " left")
|
|
if len(parts) > 0 {
|
|
return strings.TrimSpace(parts[0])
|
|
}
|
|
}
|
|
// For CHAT: "PlayerName: message"
|
|
if strings.Contains(content, ":") {
|
|
parts := strings.SplitN(content, ":", 2)
|
|
if len(parts) > 0 {
|
|
return strings.TrimSpace(parts[0])
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func main() {
|
|
gatewayURL := os.Getenv("GATEWAY_URL")
|
|
logFilePath := os.Getenv("LOG_FILE_PATH")
|
|
mockMode := os.Getenv("MOCK_MODE") == "true"
|
|
if logFilePath == "" {
|
|
logFilePath = "arma_server.rpt"
|
|
}
|
|
|
|
masterKey := []byte("this-is-a-32-byte-master-key-xyz")
|
|
communityID := "comm-123-abc"
|
|
|
|
// TODO: Initialize SQLite buffer for offline mode
|
|
// offlineBuffer, err := initOfflineBuffer("worker_buffer.db")
|
|
// if err != nil {
|
|
// log.Fatalf("Failed to init offline buffer: %v", err)
|
|
// }
|
|
// defer offlineBuffer.Close()
|
|
|
|
u, _ := url.Parse(gatewayURL)
|
|
log.Printf("Worker starting for community %s. MockMode: %v, Offline-Buffer: enabled", communityID, mockMode)
|
|
|
|
for {
|
|
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
|
if err != nil {
|
|
log.Printf("Dial failed: %v. Retrying in 5s...", err)
|
|
time.Sleep(5 * time.Second)
|
|
continue
|
|
}
|
|
|
|
log.Println("Connected to gateway")
|
|
var mu sync.Mutex
|
|
|
|
// 0. Listen for Configuration Updates (E2EE)
|
|
go func() {
|
|
for {
|
|
_, message, err := c.ReadMessage()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var msg struct {
|
|
Type string `json:"type"`
|
|
Server DevServer `json:"server"`
|
|
}
|
|
if err := json.Unmarshal(message, &msg); err == nil && msg.Type == "CONFIG_UPDATE" {
|
|
log.Printf("[CONFIG] Received update for server: %s", msg.Server.Name)
|
|
handleServerConfig(msg.Server, masterKey)
|
|
}
|
|
}
|
|
}()
|
|
|
|
// 1. Telemetry Loop
|
|
go func() {
|
|
for {
|
|
telemetry := map[string]interface{}{
|
|
"type": "TELEMETRY",
|
|
"community_id": communityID,
|
|
"fps": 45.5 + float64(time.Now().Unix()%5),
|
|
"players": 12,
|
|
"ai_count": 142 + (time.Now().Unix() % 10),
|
|
"vehicle_count": 24,
|
|
}
|
|
data, _ := json.Marshal(telemetry)
|
|
mu.Lock()
|
|
err := c.WriteMessage(websocket.TextMessage, data)
|
|
mu.Unlock()
|
|
if err != nil {
|
|
return
|
|
}
|
|
time.Sleep(5 * time.Second)
|
|
}
|
|
}()
|
|
|
|
// 2. Log Tailing / Mocking
|
|
go func() {
|
|
if mockMode {
|
|
i := 0
|
|
for {
|
|
line := mockLogs[i%len(mockLogs)]
|
|
event := parser.ParseLine(line)
|
|
if event != nil {
|
|
event.ServerID = "server-123"
|
|
event.ServerName = "AMS-NODE-01"
|
|
|
|
if event.PlayerName != "" {
|
|
event.PlayerNameHash = crypto.GenerateBlindIndex(event.PlayerName, masterKey)
|
|
} else {
|
|
event.PlayerName = "SYSTEM"
|
|
event.PlayerNameHash = "system-blind-index"
|
|
}
|
|
|
|
payload, _ := json.Marshal(event)
|
|
encrypted, _ := crypto.Encrypt(payload, masterKey)
|
|
mu.Lock()
|
|
err := c.WriteMessage(websocket.BinaryMessage, encrypted)
|
|
mu.Unlock()
|
|
if err != nil {
|
|
return
|
|
}
|
|
log.Printf("Sent MOCK event: %s", event.Type)
|
|
}
|
|
i++
|
|
time.Sleep(10 * time.Second)
|
|
}
|
|
} else {
|
|
file, err := os.Open(logFilePath)
|
|
if err != nil {
|
|
log.Printf("Could not open log file: %v", err)
|
|
return
|
|
}
|
|
file.Seek(0, 2)
|
|
scanner := bufio.NewScanner(file)
|
|
for {
|
|
if scanner.Scan() {
|
|
line := scanner.Text()
|
|
event := parser.ParseLine(line)
|
|
if event != nil {
|
|
payload, _ := json.Marshal(event)
|
|
encrypted, _ := crypto.Encrypt(payload, masterKey)
|
|
mu.Lock()
|
|
err := c.WriteMessage(websocket.BinaryMessage, encrypted)
|
|
mu.Unlock()
|
|
if err != nil {
|
|
return
|
|
}
|
|
log.Printf("Sent LIVE event: %s", event.Type)
|
|
}
|
|
}
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
}
|
|
}()
|
|
|
|
for {
|
|
if _, _, err := c.ReadMessage(); err != nil {
|
|
log.Printf("Read error: %v. Reconnecting...", err)
|
|
break
|
|
}
|
|
}
|
|
c.Close()
|
|
time.Sleep(2 * time.Second)
|
|
}
|
|
}
|