/
akuzn.1
/
backupper
Обзор
Документация
Войти
/
akuzn.1
/
backupper
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Backupper/Program.cs
232 строки
10 KB
aleksandr kuznetsov
Начальный коммит. До этого приложение создавалось на gitlab, перенёс заготовку оттуда.
05 янв 2025, 19:14
05 янв 2025, 19:14
824f648
Код
Авторство
О чём код?
using Backupper.Notifications; using Microsoft.Extensions.Configuration; using Serilog; using System.IO.Compression; class Program { class BackupProfile { public string Name { get; set; } = ""; public string SourceDirectory { get; set; } = ""; public string BackupDirectory { get; set; } = ""; public int FullBackupIntervalDays { get; set; } = 7; public RetentionSettings RetentionSettings { get; set; } = new(); public ExcludeSettings Exclude { get; set; } = new(); } class ExcludeSettings { public string[] Directories { get; set; } = []; public string[] FilePatterns { get; set; } = []; } class RetentionSettings { public int FullBackupRetentionDays { get; set; } = 90; public int IncrementalBackupRetentionDays { get; set; } = 30; public int MinimumFullBackupsToKeep { get; set; } = 3; } static async Task Main(string[] args) { Log.Logger = new LoggerConfiguration() .WriteTo.Console() .WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs/backup.log"), rollingInterval: RollingInterval.Day) .CreateLogger(); try { var configuration = new ConfigurationBuilder() .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) .AddJsonFile("appsettings.json") .Build(); var notificationManager = new NotificationManager(configuration); var profiles = configuration.GetSection("BackupSettings:BackupProfiles") .Get<BackupProfile[]>(); if (profiles == null || profiles.Length == 0) { Log.Error("Не найдено профилей для резервного копирования в конфигурации"); return; } foreach (var profile in profiles) { try { Log.Information("Начало создания резервной копии для профиля {Profile}", profile.Name); await CreateBackupAsync(profile, notificationManager); } catch (Exception ex) { Log.Error(ex, "Ошибка при создании резервной копии для профиля {Profile}", profile.Name); await notificationManager.NotifyErrorAsync(profile.Name, ex.Message); } } } catch (Exception ex) { Log.Error(ex, "Произошла ошибка при выполнении программы"); } finally { Log.CloseAndFlush(); } } static async Task CreateBackupAsync(BackupProfile profile, NotificationManager notificationManager) { if (string.IsNullOrEmpty(profile.SourceDirectory) || string.IsNullOrEmpty(profile.BackupDirectory)) { Log.Error("Ошибка в конфигурации профиля {Profile}", profile.Name); return; } var history = BackupHistory.Load(); var profileHistory = history.Profiles.GetValueOrDefault(profile.Name, new BackupHistory.ProfileHistory()); var daysSinceLastFullBackup = (DateTime.Now - profileHistory.LastFullBackupDate).Days; var isFullBackup = daysSinceLastFullBackup >= profile.FullBackupIntervalDays; Log.Information("Исходная директория: {SourceDir}", profile.SourceDirectory); Log.Information("Директория для бэкапов: {BackupDir}", profile.BackupDirectory); Log.Information("Тип бэкапа: {BackupType}", isFullBackup ? "Полный" : "Инкрементальный"); if (profile.Exclude.Directories.Any()) Log.Information("Исключаемые папки: {Dirs}", string.Join(", ", profile.Exclude.Directories)); if (profile.Exclude.FilePatterns.Any()) Log.Information("Исключаемые файлы: {Patterns}", string.Join(", ", profile.Exclude.FilePatterns)); Directory.CreateDirectory(profile.BackupDirectory); var backupFileName = $"{profile.Name}_{(isFullBackup ? "full" : "incremental")}_backup_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.zip"; var backupPath = Path.Combine(profile.BackupDirectory, backupFileName); var files = Directory.GetFiles(profile.SourceDirectory, "*.*", SearchOption.AllDirectories) .Where(file => { var relativePath = Path.GetRelativePath(profile.SourceDirectory, file); if (profile.Exclude.Directories.Any(dir => relativePath.StartsWith(dir + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) || relativePath.Equals(dir, StringComparison.OrdinalIgnoreCase))) { Log.Debug("Пропущен файл в исключаемой папке: {File}", relativePath); return false; } if (profile.Exclude.FilePatterns.Any(pattern => pattern.StartsWith("*") ? Path.GetFileName(file).EndsWith(pattern[1..], StringComparison.OrdinalIgnoreCase) : Path.GetFileName(file).Equals(pattern, StringComparison.OrdinalIgnoreCase))) { Log.Debug("Пропущен файл по маске: {File}", relativePath); return false; } if (!isFullBackup) { var fileInfo = new FileInfo(file); if (fileInfo.LastWriteTime <= profileHistory.LastBackupDate) { Log.Debug("Пропущен неизмененный файл: {File}", relativePath); return false; } } return true; }) .ToArray(); Log.Information("Найдено файлов для архивации: {Count}", files.Length); using (var archive = ZipFile.Open(backupPath, ZipArchiveMode.Create)) { foreach (var file in files) { var relativePath = Path.GetRelativePath(profile.SourceDirectory, file); archive.CreateEntryFromFile(file, relativePath); Log.Information("Добавлен файл: {File}", relativePath); } } history.UpdateHistory(profile.Name, isFullBackup); Log.Information("Резервная копия успешно создана: {BackupPath}", backupPath); await notificationManager.NotifySuccessAsync(profile.Name, backupPath, files.Length, isFullBackup); var (fullDeleted, incrementalDeleted) = RotateBackups(profile); if (fullDeleted > 0 || incrementalDeleted > 0) { await notificationManager.NotifyRotationAsync(profile.Name, fullDeleted, incrementalDeleted); } } static (int FullDeleted, int IncrementalDeleted) RotateBackups(BackupProfile profile) { var backupFiles = Directory.GetFiles(profile.BackupDirectory, $"{profile.Name}_*_backup_*.zip") .Select(path => new FileInfo(path)) .ToList(); var now = DateTime.Now; var fullBackups = new List<FileInfo>(); var incrementalBackups = new List<FileInfo>(); foreach (var file in backupFiles) { if (file.Name.Contains("_full_")) fullBackups.Add(file); else incrementalBackups.Add(file); } fullBackups = fullBackups.OrderByDescending(f => f.CreationTime).ToList(); incrementalBackups = incrementalBackups.OrderByDescending(f => f.CreationTime).ToList(); var fullBackupsToDelete = fullBackups .Skip(profile.RetentionSettings.MinimumFullBackupsToKeep) .Where(f => (now - f.CreationTime).TotalDays > profile.RetentionSettings.FullBackupRetentionDays) .ToList(); var incrementalBackupsToDelete = incrementalBackups .Where(f => (now - f.CreationTime).TotalDays > profile.RetentionSettings.IncrementalBackupRetentionDays) .ToList(); foreach (var file in fullBackupsToDelete) { try { file.Delete(); Log.Information("Удален старый полный бэкап: {File}", file.Name); } catch (Exception ex) { Log.Error(ex, "Ошибка при удалении старого полного бэкапа: {File}", file.Name); } } foreach (var file in incrementalBackupsToDelete) { try { file.Delete(); Log.Information("Удален старый инкрементальный бэкап: {File}", file.Name); } catch (Exception ex) { Log.Error(ex, "Ошибка при удалении старого инкрементального бэкапа: {File}", file.Name); } } Log.Information( "Ротация бэкапов завершена. Удалено полных бэкапов: {FullCount}, инкрементальных: {IncrementalCount}", fullBackupsToDelete.Count, incrementalBackupsToDelete.Count ); return (fullBackupsToDelete.Count, incrementalBackupsToDelete.Count); } }