diff --git a/Moonlight/App/Helpers/BackupHelper.cs b/Moonlight/App/Helpers/BackupHelper.cs new file mode 100644 index 0000000..635bcfd --- /dev/null +++ b/Moonlight/App/Helpers/BackupHelper.cs @@ -0,0 +1,119 @@ +using System.Diagnostics; +using System.IO.Compression; +using Microsoft.EntityFrameworkCore; +using Moonlight.App.Database; +using Moonlight.App.Services; +using MySql.Data.MySqlClient; + +namespace Moonlight.App.Helpers; + +public class BackupHelper +{ + public async Task CreateBackup(string path) + { + Logger.Info("Started moonlight backup creation"); + Logger.Info($"This backup will be saved to '{path}'"); + + var stopWatch = new Stopwatch(); + stopWatch.Start(); + + var cachePath = PathBuilder.Dir("storage", "backups", "cache"); + + Directory.CreateDirectory(cachePath); + + // + // Exporting database + // + + Logger.Info("Exporting database"); + + var configService = new ConfigService(new()); + var dataContext = new DataContext(configService); + + await using MySqlConnection conn = new MySqlConnection(dataContext.Database.GetConnectionString()); + await using MySqlCommand cmd = new MySqlCommand(); + using MySqlBackup mb = new MySqlBackup(cmd); + + cmd.Connection = conn; + await conn.OpenAsync(); + mb.ExportToFile(PathBuilder.File(cachePath, "database.sql")); + await conn.CloseAsync(); + + // + // Saving config + // + + Logger.Info("Saving configuration"); + File.Copy( + PathBuilder.File("storage", "configs", "config.json"), + PathBuilder.File(cachePath, "config.json")); + + // + // Saving all storage items needed to restore the panel + // + + Logger.Info("Saving resources"); + CopyDirectory( + PathBuilder.Dir("storage", "resources"), + PathBuilder.Dir(cachePath, "resources")); + + Logger.Info("Saving logs"); + CopyDirectory( + PathBuilder.Dir("storage", "logs"), + PathBuilder.Dir(cachePath, "logs")); + + Logger.Info("Saving uploads"); + CopyDirectory( + PathBuilder.Dir("storage", "uploads"), + PathBuilder.Dir(cachePath, "uploads")); + + // + // Compressing the backup to a single file + // + + Logger.Info("Compressing"); + ZipFile.CreateFromDirectory(cachePath, + path, + CompressionLevel.Fastest, + false); + + Directory.Delete(cachePath, true); + + stopWatch.Stop(); + Logger.Info($"Backup successfully created. Took {stopWatch.Elapsed.TotalSeconds} seconds"); + } + + private void CopyDirectory(string sourceDirName, string destDirName, bool copySubDirs = true) + { + DirectoryInfo dir = new DirectoryInfo(sourceDirName); + + if (!dir.Exists) + { + throw new DirectoryNotFoundException($"Source directory does not exist or could not be found: {sourceDirName}"); + } + + if (!Directory.Exists(destDirName)) + { + Directory.CreateDirectory(destDirName); + } + + FileInfo[] files = dir.GetFiles(); + + foreach (FileInfo file in files) + { + string tempPath = Path.Combine(destDirName, file.Name); + file.CopyTo(tempPath, false); + } + + if (copySubDirs) + { + DirectoryInfo[] dirs = dir.GetDirectories(); + + foreach (DirectoryInfo subdir in dirs) + { + string tempPath = Path.Combine(destDirName, subdir.Name); + CopyDirectory(subdir.FullName, tempPath, copySubDirs); + } + } + } +} \ No newline at end of file diff --git a/Moonlight/App/Helpers/DatabaseCheckupService.cs b/Moonlight/App/Helpers/DatabaseCheckupService.cs index dbb7ba3..bfb2fc3 100644 --- a/Moonlight/App/Helpers/DatabaseCheckupService.cs +++ b/Moonlight/App/Helpers/DatabaseCheckupService.cs @@ -44,8 +44,10 @@ public class DatabaseCheckupService if (migrations.Any()) { Logger.Info($"{migrations.Length} migrations pending. Updating now"); - - await BackupDatabase(); + + var backupHelper = new BackupHelper(); + await backupHelper.CreateBackup( + PathBuilder.File("storage", "backups", $"{new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds()}.zip")); Logger.Info("Applying migrations"); @@ -58,53 +60,4 @@ public class DatabaseCheckupService Logger.Info("Database is up-to-date. No migrations have been performed"); } } - - public async Task BackupDatabase() - { - Logger.Info("Creating backup from database"); - - var configService = new ConfigService(new StorageService()); - var dateTimeService = new DateTimeService(); - - var config = configService.Get().Moonlight.Database; - - var connectionString = $"host={config.Host};" + - $"port={config.Port};" + - $"database={config.Database};" + - $"uid={config.Username};" + - $"pwd={config.Password}"; - - string file = PathBuilder.File("storage", "backups", $"{dateTimeService.GetCurrentUnix()}-mysql.sql"); - - Logger.Info($"Saving it to: {file}"); - Logger.Info("Starting backup..."); - - try - { - var sw = new Stopwatch(); - sw.Start(); - - await using MySqlConnection conn = new MySqlConnection(connectionString); - await using MySqlCommand cmd = new MySqlCommand(); - using MySqlBackup mb = new MySqlBackup(cmd); - - cmd.Connection = conn; - await conn.OpenAsync(); - mb.ExportToFile(file); - await conn.CloseAsync(); - - sw.Stop(); - Logger.Info($"Done. {sw.Elapsed.TotalSeconds}s"); - } - catch (Exception e) - { - Logger.Fatal("-----------------------------------------------"); - Logger.Fatal("Unable to create backup for moonlight database"); - Logger.Fatal("Moonlight will start anyways in 30 seconds"); - Logger.Fatal("-----------------------------------------------"); - Logger.Fatal(e); - - Thread.Sleep(TimeSpan.FromSeconds(30)); - } - } } \ No newline at end of file diff --git a/Moonlight/Program.cs b/Moonlight/Program.cs index 94fc142..03b0a31 100644 --- a/Moonlight/Program.cs +++ b/Moonlight/Program.cs @@ -109,6 +109,10 @@ namespace Moonlight await databaseCheckupService.Perform(); + var backupHelper = new BackupHelper(); + await backupHelper.CreateBackup(PathBuilder.File("storage", "backups", + $"{new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds()}.zip")); + var builder = WebApplication.CreateBuilder(args); var pluginService = new PluginService();