diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/Services/BackupService.cs b/NEW/src/Utils/JdeScoping.ConfigManager/Services/BackupService.cs
new file mode 100644
index 0000000..264431e
--- /dev/null
+++ b/NEW/src/Utils/JdeScoping.ConfigManager/Services/BackupService.cs
@@ -0,0 +1,94 @@
+using System.Globalization;
+using Microsoft.Extensions.Logging;
+
+namespace JdeScoping.ConfigManager.Services;
+
+///
+/// Service for managing configuration file backups.
+///
+public class BackupService : IBackupService
+{
+ private readonly IFileSystem _fileSystem;
+ private readonly ILogger? _logger;
+ private const string TimestampFormat = "yyyy-MM-dd_HHmmss";
+
+ public BackupService(IFileSystem fileSystem, ILogger? logger = null)
+ {
+ _fileSystem = fileSystem;
+ _logger = logger;
+ }
+
+ public async Task CreateBackupAsync(string filePath, CancellationToken ct = default)
+ {
+ if (!_fileSystem.FileExists(filePath))
+ throw new FileNotFoundException("Source file not found", filePath);
+
+ var directory = _fileSystem.GetDirectoryName(filePath);
+ var baseName = _fileSystem.GetFileNameWithoutExtension(filePath);
+ var timestamp = DateTime.Now.ToString(TimestampFormat);
+ var backupPath = _fileSystem.Combine(directory, $"{baseName}.{timestamp}.bak");
+
+ await _fileSystem.CopyFileAsync(filePath, backupPath, ct);
+ _logger?.LogInformation("Created backup at {BackupPath}", backupPath);
+
+ return backupPath;
+ }
+
+ public async Task> GetBackupsAsync(string filePath, CancellationToken ct = default)
+ {
+ var directory = _fileSystem.GetDirectoryName(filePath);
+ var baseName = _fileSystem.GetFileNameWithoutExtension(filePath);
+ var pattern = $"{baseName}.*.bak";
+
+ var files = await _fileSystem.GetFilesAsync(directory, pattern, ct);
+ var backups = new List();
+
+ foreach (var file in files)
+ {
+ if (TryParseTimestamp(file, baseName, out var timestamp))
+ {
+ backups.Add(new BackupInfo
+ {
+ Path = file,
+ Timestamp = timestamp,
+ Size = 0 // Would need file info for actual size
+ });
+ }
+ }
+
+ return backups.OrderByDescending(b => b.Timestamp).ToList();
+ }
+
+ public async Task RestoreBackupAsync(string backupPath, string targetPath, CancellationToken ct = default)
+ {
+ _logger?.LogInformation("Restoring backup from {BackupPath} to {TargetPath}", backupPath, targetPath);
+ await _fileSystem.CopyFileAsync(backupPath, targetPath, ct);
+ }
+
+ public async Task CleanupOldBackupsAsync(string filePath, int keepCount = 10, CancellationToken ct = default)
+ {
+ var backups = await GetBackupsAsync(filePath, ct);
+ var toDelete = backups.Skip(keepCount).ToList();
+
+ foreach (var backup in toDelete)
+ {
+ await _fileSystem.DeleteFileAsync(backup.Path, ct);
+ _logger?.LogInformation("Deleted old backup {BackupPath}", backup.Path);
+ }
+ }
+
+ private bool TryParseTimestamp(string filePath, string baseName, out DateTime timestamp)
+ {
+ timestamp = default;
+ var fileName = _fileSystem.GetFileNameWithoutExtension(filePath);
+
+ // Expected format: baseName.yyyy-MM-dd_HHmmss
+ var prefix = $"{baseName}.";
+ if (!fileName.StartsWith(prefix))
+ return false;
+
+ var timestampPart = fileName[prefix.Length..];
+ return DateTime.TryParseExact(timestampPart, TimestampFormat,
+ CultureInfo.InvariantCulture, DateTimeStyles.None, out timestamp);
+ }
+}
diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/Services/IBackupService.cs b/NEW/src/Utils/JdeScoping.ConfigManager/Services/IBackupService.cs
new file mode 100644
index 0000000..f93cb82
--- /dev/null
+++ b/NEW/src/Utils/JdeScoping.ConfigManager/Services/IBackupService.cs
@@ -0,0 +1,22 @@
+namespace JdeScoping.ConfigManager.Services;
+
+///
+/// Represents backup file information.
+///
+public class BackupInfo
+{
+ public required string Path { get; init; }
+ public required DateTime Timestamp { get; init; }
+ public required long Size { get; init; }
+}
+
+///
+/// Service for managing configuration file backups.
+///
+public interface IBackupService
+{
+ Task CreateBackupAsync(string filePath, CancellationToken ct = default);
+ Task> GetBackupsAsync(string filePath, CancellationToken ct = default);
+ Task RestoreBackupAsync(string backupPath, string targetPath, CancellationToken ct = default);
+ Task CleanupOldBackupsAsync(string filePath, int keepCount = 10, CancellationToken ct = default);
+}
diff --git a/NEW/tests/JdeScoping.ConfigManager.Tests/Services/BackupServiceTests.cs b/NEW/tests/JdeScoping.ConfigManager.Tests/Services/BackupServiceTests.cs
new file mode 100644
index 0000000..cd76e50
--- /dev/null
+++ b/NEW/tests/JdeScoping.ConfigManager.Tests/Services/BackupServiceTests.cs
@@ -0,0 +1,66 @@
+using JdeScoping.ConfigManager.Services;
+
+namespace JdeScoping.ConfigManager.Tests.Services;
+
+public class BackupServiceTests
+{
+ private readonly IFileSystem _fileSystem;
+ private readonly BackupService _sut;
+
+ public BackupServiceTests()
+ {
+ _fileSystem = Substitute.For();
+ _sut = new BackupService(_fileSystem);
+ }
+
+ [Fact]
+ public async Task CreateBackupAsync_CreatesTimestampedBackup()
+ {
+ // Arrange
+ var sourcePath = "/config/appsettings.json";
+ _fileSystem.FileExists(sourcePath).Returns(true);
+ _fileSystem.GetDirectoryName(sourcePath).Returns("/config");
+ _fileSystem.GetFileNameWithoutExtension(sourcePath).Returns("appsettings");
+ _fileSystem.Combine(Arg.Any()).Returns(callInfo =>
+ {
+ var paths = callInfo.Arg();
+ return string.Join("/", paths);
+ });
+
+ // Act
+ var backupPath = await _sut.CreateBackupAsync(sourcePath);
+
+ // Assert
+ backupPath.ShouldStartWith("/config/appsettings.");
+ backupPath.ShouldEndWith(".bak");
+ await _fileSystem.Received(1).CopyFileAsync(sourcePath, backupPath, Arg.Any());
+ }
+
+ [Fact]
+ public async Task CleanupOldBackupsAsync_KeepsOnlySpecifiedCount()
+ {
+ // Arrange
+ var filePath = "/config/appsettings.json";
+ _fileSystem.GetDirectoryName(filePath).Returns("/config");
+ _fileSystem.GetFileNameWithoutExtension(filePath).Returns("appsettings");
+
+ var backups = Enumerable.Range(1, 15)
+ .Select(i => $"/config/appsettings.2026-01-{i:D2}_120000.bak")
+ .ToArray();
+ _fileSystem.GetFilesAsync("/config", "appsettings.*.bak", Arg.Any())
+ .Returns(Task.FromResult(backups));
+
+ // Mock GetFileNameWithoutExtension for each backup file
+ foreach (var backup in backups)
+ {
+ var fileName = backup.Split('/').Last().Replace(".bak", "");
+ _fileSystem.GetFileNameWithoutExtension(backup).Returns(fileName);
+ }
+
+ // Act
+ await _sut.CleanupOldBackupsAsync(filePath, keepCount: 10);
+
+ // Assert - should delete 5 oldest backups
+ await _fileSystem.Received(5).DeleteFileAsync(Arg.Any(), Arg.Any());
+ }
+}