diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.cs
index ef947d63..177b5a91 100644
--- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.cs
+++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.cs
@@ -437,7 +437,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
{
try
{
- var ntpServer = _configuration.NtpServer ?? "time.windows.com";
+ var ntpServer = string.IsNullOrEmpty(_configuration.NtpServer) ? "time.windows.com" : _configuration.NtpServer;
var result = NtpTimeCheckService.CheckTime(ntpServer, _logger);
if (result.Success)
diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
index fdeeeba2..3e40c389 100644
--- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
+++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
@@ -1 +1 @@
-737
+738
diff --git a/src/Backend/AutopilotMonitor.Functions/DataAccess/TableStorage/TableConfigRepository.cs b/src/Backend/AutopilotMonitor.Functions/DataAccess/TableStorage/TableConfigRepository.cs
index 94c2855d..bc920e5d 100644
--- a/src/Backend/AutopilotMonitor.Functions/DataAccess/TableStorage/TableConfigRepository.cs
+++ b/src/Backend/AutopilotMonitor.Functions/DataAccess/TableStorage/TableConfigRepository.cs
@@ -464,7 +464,7 @@ namespace AutopilotMonitor.Functions.DataAccess.TableStorage
RebootDelaySeconds = entity.GetInt32("RebootDelaySeconds"),
EnableGeoLocation = entity.GetBoolean("EnableGeoLocation"),
EnableTimezoneAutoSet = entity.GetBoolean("EnableTimezoneAutoSet"),
- NtpServer = entity.GetString("NtpServer") ?? "time.windows.com",
+ NtpServer = string.IsNullOrWhiteSpace(entity.GetString("NtpServer")) ? "time.windows.com" : entity.GetString("NtpServer"),
EnableImeMatchLog = entity.GetBoolean("EnableImeMatchLog"),
LogLevel = entity.GetString("LogLevel"),
MaxBatchSize = entity.GetInt32("MaxBatchSize"),
diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAgentConfigFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAgentConfigFunction.cs
index 9e33487a..37d6e95f 100644
--- a/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAgentConfigFunction.cs
+++ b/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAgentConfigFunction.cs
@@ -163,7 +163,7 @@ namespace AutopilotMonitor.Functions.Functions.Config
EnableSoftwareInventoryAnalyzer = tenantConfig.EnableSoftwareInventoryAnalyzer ?? false
},
LatestAgentSha256 = adminConfig.LatestAgentSha256,
- NtpServer = tenantConfig.NtpServer ?? "time.windows.com",
+ NtpServer = string.IsNullOrEmpty(tenantConfig.NtpServer) ? "time.windows.com" : tenantConfig.NtpServer,
EnableTimezoneAutoSet = tenantConfig.EnableTimezoneAutoSet ?? false,
SendTraceEvents = tenantConfig.SendTraceEvents,
UnrestrictedMode = tenantConfig.UnrestrictedModeEnabled && tenantConfig.UnrestrictedMode,
diff --git a/src/Backend/AutopilotMonitor.Functions/Services/MaintenanceService.cs b/src/Backend/AutopilotMonitor.Functions/Services/MaintenanceService.cs
index df418531..1e512510 100644
--- a/src/Backend/AutopilotMonitor.Functions/Services/MaintenanceService.cs
+++ b/src/Backend/AutopilotMonitor.Functions/Services/MaintenanceService.cs
@@ -155,6 +155,10 @@ namespace AutopilotMonitor.Functions.Services
// Backfill OnboardedAt for tenants that don't have it yet
// TODO: Remove once all tenants have been backfilled
await BackfillTenantOnboardedAtAsync();
+
+ // Backfill NtpServer for tenants with null/empty value
+ // TODO: Remove after 2026-06-01
+ await BackfillNtpServerDefaultAsync();
}
await RecomputePlatformStatsAsync();
@@ -406,6 +410,51 @@ namespace AutopilotMonitor.Functions.Services
}
}
+ ///
+ /// Backfills NtpServer for tenants that have null or empty value.
+ /// Sets them to the default "time.windows.com".
+ /// Self-healing: runs every manual maintenance cycle, no-ops once all tenants are backfilled.
+ /// TODO: Remove after 2026-06-01
+ ///
+ private async Task BackfillNtpServerDefaultAsync()
+ {
+ _logger.LogInformation("Backfilling NtpServer for tenants with missing value...");
+
+ try
+ {
+ var allConfigs = await _tenantConfigService.GetAllConfigurationsAsync();
+ var configsMissingNtp = allConfigs.Where(c => string.IsNullOrEmpty(c.NtpServer)).ToList();
+
+ if (configsMissingNtp.Count == 0)
+ {
+ _logger.LogInformation("All tenants already have NtpServer set");
+ return;
+ }
+
+ int backfilledCount = 0;
+
+ foreach (var config in configsMissingNtp)
+ {
+ try
+ {
+ config.NtpServer = "time.windows.com";
+ await _tenantConfigService.SaveConfigurationAsync(config);
+ backfilledCount++;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Failed to backfill NtpServer for tenant {TenantId}", config.TenantId);
+ }
+ }
+
+ _logger.LogInformation("NtpServer backfill completed: {Count} tenants updated", backfilledCount);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to backfill NtpServer for tenants");
+ }
+ }
+
///
/// Aggregates metrics for any missed days in the last 7 days, plus yesterday.
/// Checks the UsageMetrics table for existing snapshots to avoid re-aggregation.
diff --git a/src/Shared/AutopilotMonitor.Shared/Models/Config/TenantConfiguration.cs b/src/Shared/AutopilotMonitor.Shared/Models/Config/TenantConfiguration.cs
index 05c40ae9..5ff44286 100644
--- a/src/Shared/AutopilotMonitor.Shared/Models/Config/TenantConfiguration.cs
+++ b/src/Shared/AutopilotMonitor.Shared/Models/Config/TenantConfiguration.cs
@@ -545,7 +545,8 @@ namespace AutopilotMonitor.Shared.Models
SelfDestructOnComplete = true,
KeepLogFile = false,
ShowScriptOutput = true,
- OnboardedAt = DateTime.UtcNow
+ OnboardedAt = DateTime.UtcNow,
+ NtpServer = "time.windows.com"
};
}
}