using System;
using System.IO;
using System.Threading.Tasks;
using NewLife;
using NewLife.Log;
using Rainbow.Entity;
namespace Rainbow.Services;
/// <summary>配置文件备份器。备份/回滚/清理</summary>
public class ConfigBackup
{
private readonly String _backupRoot;
/// <summary>默认备份的配置文件列表</summary>
public static readonly String[] DefaultConfigFiles = [
"/etc/dnsmasq.conf",
"/etc/dnsmasq.d/",
"/etc/iptables/",
"/etc/ppp/",
"/etc/network/interfaces",
"/etc/netplan/"
];
/// <summary>创建备份器</summary>
/// <param name="backupRoot">备份根目录,默认 Data/Backup</param>
public ConfigBackup(String? backupRoot = null)
{
_backupRoot = (backupRoot ?? "Data/Backup").GetFullPath();
}
/// <summary>备份单个文件到备份目录,创建 ConfigSnapshot 记录</summary>
/// <param name="filePath">源文件路径</param>
/// <returns>备份文件路径,文件不存在时返回 null</returns>
public async Task<String?> BackupAsync(String filePath)
{
if (filePath.IsNullOrEmpty()) throw new ArgumentNullException(nameof(filePath));
var sourceFile = filePath.GetFullPath();
if (!File.Exists(sourceFile))
{
XTrace.WriteLine("ConfigBackup: 源文件不存在 {0}", sourceFile);
return null;
}
// 备份目录:BackupRoot/yyyyMMdd/
var dateDir = Path.Combine(_backupRoot, DateTime.Now.ToString("yyyyMMdd"));
if (!Directory.Exists(dateDir)) Directory.CreateDirectory(dateDir);
// 备份文件名:原文件名_yyyyMMddHHmmss.bak
var fileName = Path.GetFileName(sourceFile);
var backupName = $"{fileName}_{DateTime.Now:yyyyMMddHHmmss}.bak";
var backupPath = Path.Combine(dateDir, backupName);
// 异步复制文件
using (var sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
using (var destStream = new FileStream(backupPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
{
await sourceStream.CopyToAsync(destStream);
}
var fileInfo = new FileInfo(sourceFile);
// 创建 ConfigSnapshot 记录
var snapshot = new ConfigSnapshot
{
FileName = fileName,
BackupPath = backupPath,
SourcePath = sourceFile,
FileSize = fileInfo.Length,
Enable = true
};
snapshot.Insert();
XTrace.WriteLine("ConfigBackup: 备份完成 {0} → {1}", sourceFile, backupPath);
return backupPath;
}
/// <summary>从备份恢复配置文件</summary>
/// <param name="backupPath">备份文件路径</param>
public async Task RollbackAsync(String backupPath)
{
if (backupPath.IsNullOrEmpty()) throw new ArgumentNullException(nameof(backupPath));
var backupFile = backupPath.GetFullPath();
if (!File.Exists(backupFile))
throw new FileNotFoundException("备份文件不存在", backupFile);
// 查找对应的 ConfigSnapshot 记录
var snapshot = ConfigSnapshot.Find(ConfigSnapshot._.BackupPath == backupFile);
if (snapshot == null)
throw new InvalidOperationException($"未找到备份记录: {backupFile}");
var sourcePath = snapshot.SourcePath;
if (sourcePath.IsNullOrEmpty())
throw new InvalidOperationException("备份记录中源文件路径为空");
// 确保源目录存在
var sourceDir = Path.GetDirectoryName(sourcePath);
if (!sourceDir.IsNullOrEmpty() && !Directory.Exists(sourceDir))
Directory.CreateDirectory(sourceDir);
// 复制备份到源位置
using (var sourceStream = new FileStream(backupFile, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
using (var destStream = new FileStream(sourcePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
{
await sourceStream.CopyToAsync(destStream);
}
XTrace.WriteLine("ConfigBackup: 回滚完成 {0} → {1}", backupFile, sourcePath);
}
/// <summary>清理 N 天前的旧备份(同时删除文件和数据库记录)</summary>
/// <param name="days">保留天数,默认 7</param>
public void CleanOldBackups(Int32 days = 7)
{
var cutoff = DateTime.Now.AddDays(-days);
var oldSnapshots = ConfigSnapshot.FindAll(
ConfigSnapshot._.CreateTime < cutoff & ConfigSnapshot._.Enable == true);
var deleted = 0;
foreach (var snapshot in oldSnapshots)
{
try
{
// 删除备份文件
var backupPath = snapshot.BackupPath;
if (!backupPath.IsNullOrEmpty() && File.Exists(backupPath))
File.Delete(backupPath);
// 删除数据库记录
snapshot.Delete();
deleted++;
}
catch (Exception ex)
{
XTrace.WriteException(ex);
}
}
// 清理空的日期目录
if (Directory.Exists(_backupRoot))
{
foreach (var dir in Directory.GetDirectories(_backupRoot))
{
try
{
if (Directory.GetFileSystemEntries(dir).Length == 0)
Directory.Delete(dir);
}
catch
{
// 忽略目录删除失败
}
}
}
if (deleted > 0)
XTrace.WriteLine("ConfigBackup: 清理完成,删除 {0} 条旧备份", deleted);
}
/// <summary>备份所有默认配置文件</summary>
/// <returns>成功备份的文件数</returns>
public async Task<Int32> BackupAllAsync()
{
var count = 0;
foreach (var pattern in DefaultConfigFiles)
{
try
{
if (Directory.Exists(pattern))
{
foreach (var file in Directory.GetFiles(pattern, "*", SearchOption.AllDirectories))
{
if (await BackupAsync(file) != null)
count++;
}
}
else if (File.Exists(pattern))
{
if (await BackupAsync(pattern) != null)
count++;
}
}
catch (Exception ex)
{
XTrace.WriteException(ex);
}
}
return count;
}
/// <summary>导出指定日期范围内的备份文件路径列表</summary>
/// <param name="days">最近 N 天</param>
/// <returns>备份文件路径列表</returns>
public IEnumerable<String> GetBackupFiles(Int32 days = 7)
{
var cutoff = DateTime.Now.AddDays(-days);
var snapshots = ConfigSnapshot.FindAll(
ConfigSnapshot._.CreateTime >= cutoff & ConfigSnapshot._.Enable == true);
foreach (var snapshot in snapshots)
{
if (!snapshot.BackupPath.IsNullOrEmpty() && File.Exists(snapshot.BackupPath))
yield return snapshot.BackupPath;
}
}
}
|