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