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