257 lines
9.1 KiB
C#
257 lines
9.1 KiB
C#
using MySql.Data.MySqlClient;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Xml;
|
|
using System.Xml.Serialization;
|
|
|
|
namespace BackupManager
|
|
{
|
|
public static class Program {
|
|
public readonly static Stopwatch sw = new();
|
|
|
|
private const string CONFIG_NAME = @"FlashiiBackupManager.v1.xml";
|
|
|
|
public static bool IsWindows
|
|
=> RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
|
|
|
public readonly static DateTimeOffset Startup = DateTimeOffset.UtcNow;
|
|
|
|
public static string Basename
|
|
=> $@"{Environment.MachineName} {Startup.Year:0000}-{Startup.Month:00}-{Startup.Day:00} {Startup.Hour:00}{Startup.Minute:00}{Startup.Second:00}";
|
|
public static string BackupName
|
|
=> $@"{Basename}.zip";
|
|
|
|
private static Config Config;
|
|
private readonly static string ConfigPath = Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
|
|
CONFIG_NAME
|
|
);
|
|
|
|
public static string BackupStore => string.IsNullOrWhiteSpace(Config.FileSystemPathV2)
|
|
? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), @"Backups")
|
|
: Config.FileSystemPathV2;
|
|
|
|
private static bool Headless;
|
|
|
|
public static string WindowsToUnixPath(this string path) {
|
|
return IsWindows ? path.Replace('\\', '/') : path;
|
|
}
|
|
|
|
public static Stream ToXml(this object obj, bool pretty = false) {
|
|
MemoryStream ms = new();
|
|
XmlSerializer xs = new(obj.GetType());
|
|
|
|
using(XmlWriter xw = XmlWriter.Create(ms, new XmlWriterSettings { Indent = pretty }))
|
|
xs.Serialize(xw, obj);
|
|
|
|
ms.Seek(0, SeekOrigin.Begin);
|
|
return ms;
|
|
}
|
|
|
|
public static T FromXml<T>(Stream xml) {
|
|
if(xml.CanSeek)
|
|
xml.Seek(0, SeekOrigin.Begin);
|
|
|
|
XmlSerializer xs = new(typeof(T));
|
|
return (T)xs.Deserialize(xml);
|
|
}
|
|
|
|
public static void SaveConfig() {
|
|
Log(@"Saving configuration...");
|
|
using FileStream fs = new(ConfigPath, FileMode.Create, FileAccess.Write);
|
|
using Stream cs = Config.ToXml(true);
|
|
cs.CopyTo(fs);
|
|
}
|
|
|
|
public static void LoadConfig() {
|
|
Log(@"Loading configuration...");
|
|
using FileStream fs = File.OpenRead(ConfigPath);
|
|
Config = FromXml<Config>(fs);
|
|
}
|
|
|
|
public static void Main(string[] args) {
|
|
Headless = args.Contains(@"-cron") || args.Contains(@"-headless");
|
|
|
|
Log(@"Flashii Backup Manager");
|
|
sw.Start();
|
|
|
|
if(!File.Exists(ConfigPath)) {
|
|
Config = new Config();
|
|
SaveConfig();
|
|
Error(@"No configuration file exists, created a blank one. Be sure to fill it out properly.");
|
|
}
|
|
|
|
LoadConfig();
|
|
|
|
if(!Directory.Exists(BackupStore))
|
|
Directory.CreateDirectory(BackupStore);
|
|
|
|
Log(@"Fetching database list...");
|
|
|
|
MySqlConnectionStringBuilder connStr = new() {
|
|
Server = Config.MySqlHost,
|
|
UserID = Config.MySqlUser,
|
|
Password = Config.MySqlPass,
|
|
CharacterSet = @"utf8mb4",
|
|
SslMode = MySqlSslMode.None,
|
|
};
|
|
|
|
List<string> databases = new();
|
|
string[] exclude = Config.MySqlExcludeDatabases.Split(' ');
|
|
|
|
using(MySqlConnection conn = new(connStr.ToString())) {
|
|
conn.Open();
|
|
|
|
using(MySqlCommand comm = new(@"SET NAMES 'utf8mb4';", conn))
|
|
comm.ExecuteNonQuery();
|
|
|
|
using(MySqlCommand comm = new(@"SHOW DATABASES;", conn))
|
|
using(MySqlDataReader read = comm.ExecuteReader()) {
|
|
while(read.Read()) {
|
|
string database = read.GetString(0);
|
|
|
|
if(string.IsNullOrEmpty(database) || exclude.Contains(database))
|
|
continue;
|
|
|
|
databases.Add(database);
|
|
}
|
|
}
|
|
}
|
|
|
|
Log(@"Creating backup archive...");
|
|
|
|
string archivePath = Path.GetTempFileName();
|
|
|
|
using(FileStream fs = File.OpenWrite(archivePath))
|
|
using(ZipArchive archive = new(fs, ZipArchiveMode.Create)) {
|
|
Log(@"Database backup...");
|
|
|
|
string sqldefaults = Path.GetTempFileName();
|
|
|
|
using(FileStream sqlConfFs = File.Open(sqldefaults, FileMode.Open, FileAccess.ReadWrite))
|
|
using(StreamWriter sw = new(sqlConfFs)) {
|
|
sw.WriteLine(@"[client]");
|
|
sw.WriteLine($@"user={Config.MySqlUser}");
|
|
sw.WriteLine($@"password={Config.MySqlPass}");
|
|
sw.WriteLine(@"default-character-set=utf8mb4");
|
|
}
|
|
|
|
foreach(string database in databases)
|
|
CreateDbDump(archive, sqldefaults, database);
|
|
|
|
Log($@"MariaDB dump done.");
|
|
File.Delete(sqldefaults);
|
|
|
|
if(!string.IsNullOrWhiteSpace(Config.MisuzuPath)
|
|
&& Directory.Exists(Config.MisuzuPath)) {
|
|
Log(@"Filesystem backup...");
|
|
string mszConfig = Path.Combine(Config.MisuzuPath, @"config/config.ini");
|
|
|
|
if(!File.Exists(mszConfig))
|
|
Error(@"Could not find Misuzu config.");
|
|
|
|
string mszStore = Path.Combine(Config.MisuzuPath, @"store");
|
|
|
|
if(!Directory.Exists(mszStore))
|
|
Error(@"Could not find Misuzu storage directory.");
|
|
|
|
CreateMisuzuDataBackup(archive, mszConfig, mszStore);
|
|
}
|
|
}
|
|
|
|
string targetPath = Path.Combine(BackupStore, BackupName);
|
|
File.Move(archivePath, targetPath);
|
|
Log($@"Moved backup archive to {targetPath}");
|
|
|
|
SaveConfig();
|
|
sw.Stop();
|
|
Log($@"Done! Took {sw.Elapsed}.");
|
|
|
|
#if DEBUG
|
|
Console.ReadLine();
|
|
#endif
|
|
}
|
|
|
|
public static void Log(object line) {
|
|
if(!Headless) {
|
|
if(sw?.IsRunning == true) {
|
|
ConsoleColor fg = Console.ForegroundColor;
|
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
Console.Write(sw.ElapsedMilliseconds.ToString().PadRight(10));
|
|
Console.ForegroundColor = fg;
|
|
}
|
|
|
|
Console.WriteLine(line);
|
|
}
|
|
}
|
|
|
|
public static void Error(object line, int exit = 0x00DEAD00) {
|
|
if(!Headless) {
|
|
Console.ForegroundColor = ConsoleColor.Red;
|
|
Log(line);
|
|
Console.ResetColor();
|
|
}
|
|
|
|
#if DEBUG
|
|
Console.ReadLine();
|
|
#endif
|
|
|
|
Environment.Exit(exit);
|
|
}
|
|
|
|
public static void CreateMisuzuDataBackup(ZipArchive archive, string configPath, string storePath) {
|
|
Log(@"Storing non-volatile Misuzu data...");
|
|
|
|
archive.CreateEntryFromFile(configPath, @"misuzu/config/config.ini", CompressionLevel.Optimal);
|
|
|
|
string[] storeFiles = Directory.GetFiles(storePath, @"*", SearchOption.AllDirectories);
|
|
|
|
foreach(string file in storeFiles)
|
|
archive.CreateEntryFromFile(
|
|
file,
|
|
@"misuzu/store/" + file.Replace(storePath, string.Empty).WindowsToUnixPath().Trim('/'),
|
|
CompressionLevel.Optimal
|
|
);
|
|
}
|
|
|
|
public static void CreateDbDump(ZipArchive archive, string defaults, string database) {
|
|
Log($@"Dumping {database}...");
|
|
|
|
string sqldump = Path.GetTempFileName();
|
|
|
|
StringBuilder mysqldumpArgs = new();
|
|
mysqldumpArgs.AppendFormat(@"--defaults-file={0} ", defaults);
|
|
mysqldumpArgs.Append(@"--single-transaction ");
|
|
mysqldumpArgs.Append(@"--tz-utc --triggers ");
|
|
mysqldumpArgs.Append(@"--routines --hex-blob ");
|
|
mysqldumpArgs.Append(@"--add-locks --order-by-primary ");
|
|
mysqldumpArgs.Append(@"--skip-lock-tables "); // might regret this, we'll find out someday
|
|
mysqldumpArgs.AppendFormat(@"--result-file={0} ", sqldump);
|
|
mysqldumpArgs.Append(@"-Q -q -B "); // lock, quote names, quick, database list
|
|
mysqldumpArgs.Append(database);
|
|
|
|
#if DEBUG
|
|
Log($@"mysqldump args: {mysqldumpArgs}");
|
|
#endif
|
|
|
|
Process p = Process.Start(new ProcessStartInfo {
|
|
FileName = IsWindows ? Config.MySqlDumpPathWindows : Config.MySqlDumpPath,
|
|
Arguments = mysqldumpArgs.ToString(),
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
});
|
|
|
|
p.WaitForExit();
|
|
|
|
archive.CreateEntryFromFile(sqldump, $@"mariadb/{database}.sql", CompressionLevel.Optimal);
|
|
|
|
File.Delete(sqldump);
|
|
}
|
|
}
|
|
}
|