namespace Sonex.BaseBackup; using System; using System.Diagnostics; using System.Globalization; using System.IO; using System.Text; using System.Threading.Tasks; internal static class Program { public static async Task Main(string[] args) { try { EnsureDefaultConfig(); var pgBaseBackupPath = Config.GetStringValue("PgBaseBackupPath", ""); var host = Config.GetStringValue("Host", "localhost"); var port = Config.GetIntValue("Port", 5432); var username = Config.GetStringValue("Username", "postgres"); var password = Config.GetStringValue("Password", ""); var backupRootDirectory = Config.GetStringValue( "BackupRootDirectory", Path.Combine(AppContext.BaseDirectory, "backups")); if (string.IsNullOrWhiteSpace(pgBaseBackupPath)) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("PgBaseBackupPath is missing in config.cfg"); Console.ResetColor(); return 1; } if (!File.Exists(pgBaseBackupPath)) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"File not found: {pgBaseBackupPath}"); Console.ResetColor(); return 1; } if (string.IsNullOrWhiteSpace(password)) { Console.Write("Enter PostgreSQL password: "); password = ReadPassword(); Console.WriteLine(); } Directory.CreateDirectory(backupRootDirectory); var timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss", CultureInfo.InvariantCulture); var backupDirectory = Path.Combine(backupRootDirectory, $"pg_backup_{timestamp}"); var logFilePath = Path.Combine(backupRootDirectory, $"pg_backup_{timestamp}.log"); Log(logFilePath, "=== PostgreSQL Full Backup ==="); Log(logFilePath, $"Start: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); Log(logFilePath, $"Host: {host}"); Log(logFilePath, $"Port: {port}"); Log(logFilePath, $"Username: {username}"); Log(logFilePath, $"Backup root directory: {backupRootDirectory}"); Log(logFilePath, $"Backup directory: {backupDirectory}"); Log(logFilePath, "Format: tar"); Log(logFilePath, "Compression: zstd:3"); Log(logFilePath, "Wal method: fetch"); Log(logFilePath, "Checkpoint: fast"); Log(logFilePath, ""); var arguments = BuildArguments(host, port, username, backupDirectory); Log(logFilePath, $"Command: {pgBaseBackupPath} {arguments}"); Log(logFilePath, ""); var startInfo = new ProcessStartInfo { FileName = pgBaseBackupPath, Arguments = arguments, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; startInfo.Environment["PGPASSWORD"] = password; using var process = new Process { StartInfo = startInfo }; process.OutputDataReceived += (_, e) => { if (e.Data is null) return; Console.WriteLine(e.Data); Log(logFilePath, e.Data); }; process.ErrorDataReceived += (_, e) => { if (e.Data is null) return; Console.Error.WriteLine(e.Data); Log(logFilePath, "[ERR] " + e.Data); }; Console.WriteLine("Starting backup..."); Console.WriteLine(); if (!process.Start()) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Failed to start process."); Console.ResetColor(); Log(logFilePath, "Failed to start process."); return 1; } process.BeginOutputReadLine(); process.BeginErrorReadLine(); await process.WaitForExitAsync(); Log(logFilePath, ""); Log(logFilePath, $"Exit code: {process.ExitCode}"); Log(logFilePath, $"End: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); Console.WriteLine(); Console.WriteLine($"Exit code: {process.ExitCode}"); if (process.ExitCode != 0) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Backup failed."); Console.ResetColor(); return process.ExitCode; } Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Backup completed successfully."); Console.ResetColor(); return 0; } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Critical error:"); Console.WriteLine(ex); Console.ResetColor(); return 1; } } private static void EnsureDefaultConfig() { Config.GetStringValue("PgBaseBackupPath", ""); Config.GetStringValue("Host", "localhost"); Config.GetIntValue("Port", 5432); Config.GetStringValue("Username", "postgres"); Config.GetStringValue("Password", ""); Config.GetStringValue("BackupRootDirectory", Path.Combine(AppContext.BaseDirectory, "backups")); } private static string BuildArguments(string host, int port, string username, string backupDirectory) { return $"-h \"{host}\" " + $"-p {port} " + $"-U \"{username}\" " + $"-D \"{backupDirectory}\" " + "-Ft " + "-Xfetch " + "--checkpoint=fast " + "-P " + "-v " + "-Z \"zstd:3\""; } private static void Log(string logFilePath, string message) { var line = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}"; File.AppendAllText(logFilePath, line + Environment.NewLine, Encoding.UTF8); } private static string ReadPassword() { var sb = new StringBuilder(); while (true) { var key = Console.ReadKey(intercept: true); if (key.Key == ConsoleKey.Enter) break; if (key.Key == ConsoleKey.Backspace) { if (sb.Length > 0) sb.Length--; continue; } if (!char.IsControl(key.KeyChar)) sb.Append(key.KeyChar); } return sb.ToString(); } }