using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using RmmAgent.Security; using RmmAgent.Services; namespace RmmAgent; /// /// Worker en mode POLLING pur : /// - Toutes les 30s : envoie heartbeat → reçoit commandes en attente → exécute /// - Toutes les 60 min : envoie inventaire complet /// - Pas de WebSocket (compatible hosting mutualisé PHP) /// - Pas de capture automatique (uniquement sur commande "screenshot_now") /// public class Worker : BackgroundService { private readonly ILogger _logger; private readonly IConfiguration _config; private readonly ConfigStore _configStore; private readonly EnrollmentService _enrollment; private readonly ApiClient _api; private readonly HeartbeatService _heartbeat; private readonly InventoryCollector _inventory; private readonly CommandExecutor _commandExec; public Worker( ILogger logger, IConfiguration config, ConfigStore configStore, EnrollmentService enrollment, ApiClient api, HeartbeatService heartbeat, InventoryCollector inventory, CommandExecutor commandExec) { _logger = logger; _config = config; _configStore = configStore; _enrollment = enrollment; _api = api; _heartbeat = heartbeat; _inventory = inventory; _commandExec = commandExec; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Worker started (polling mode)."); // 1. Enrollment if (!_configStore.HasAgentToken()) { _logger.LogInformation("No agent token - enrolling..."); await EnsureEnrolledWithRetry(stoppingToken); } if (stoppingToken.IsCancellationRequested) return; _api.Initialize(); // 2. Initial inventory push await TryPushInventory(stoppingToken); // 3. Main polling loop var heartbeatInterval = TimeSpan.FromSeconds(_config.GetValue("Rmm:HeartbeatIntervalSeconds", 30)); var inventoryInterval = TimeSpan.FromMinutes(_config.GetValue("Rmm:InventoryIntervalMinutes", 60)); var lastInventory = DateTime.UtcNow; while (!stoppingToken.IsCancellationRequested) { try { // Heartbeat + récupère commandes en attente var commands = await _heartbeat.SendAsync(stoppingToken); // Exécute les commandes en parallèle if (commands.Length > 0) { _logger.LogInformation("Received {Count} command(s) from server", commands.Length); var tasks = commands.Select(cmd => Task.Run(() => _commandExec.ExecuteAsync(cmd), stoppingToken)).ToArray(); await Task.WhenAll(tasks); } // Inventaire périodique if (DateTime.UtcNow - lastInventory >= inventoryInterval) { if (await TryPushInventory(stoppingToken)) lastInventory = DateTime.UtcNow; } } catch (Exception ex) { _logger.LogError(ex, "Worker loop error"); } try { await Task.Delay(heartbeatInterval, stoppingToken); } catch { break; } } _logger.LogInformation("Worker stopping."); } private async Task EnsureEnrolledWithRetry(CancellationToken ct) { var attempt = 0; while (!ct.IsCancellationRequested) { attempt++; try { await _enrollment.EnrollAsync(ct); _logger.LogInformation("Enrollment successful."); return; } catch (Exception ex) { var delay = Math.Min(60 * attempt, 600); _logger.LogWarning(ex, "Enrollment failed (attempt {Attempt}) - retry in {Delay}s", attempt, delay); try { await Task.Delay(TimeSpan.FromSeconds(delay), ct); } catch { return; } } } } private async Task TryPushInventory(CancellationToken ct) { try { var inv = _inventory.Collect(); await _api.PushInventoryAsync(inv, ct); _logger.LogInformation("Inventory pushed."); return true; } catch (Exception ex) { _logger.LogWarning(ex, "Inventory push failed (will retry)"); return false; } } }