mirror of
https://github.com/RedWizardsLab/EchoHub.git
synced 2026-09-04 08:36:11 +02:00
158 lines
4.8 KiB
C#
158 lines
4.8 KiB
C#
using EchoHub.Client;
|
|
using EchoHub.Client.Config;
|
|
using EchoHub.Client.Services;
|
|
using EchoHub.Client.Themes;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Serilog;
|
|
using Serilog.Settings.Configuration;
|
|
using Terminal.Gui.App;
|
|
|
|
// == CLI rollback: works without TUI, before anything else ================
|
|
if (args.Contains("--rollback"))
|
|
{
|
|
if (UpdateBackupService.BackupExists())
|
|
{
|
|
var info = UpdateBackupService.GetBackupInfo();
|
|
Console.WriteLine($"Rolling back to version {info?.Version ?? "unknown"}...");
|
|
try
|
|
{
|
|
UpdateBackupService.RestoreBackup();
|
|
// RestoreBackup calls Environment.Exit(0)
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"Rollback failed: {ex.Message}");
|
|
Environment.Exit(1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Error.WriteLine("No backup available to restore.");
|
|
Environment.Exit(1);
|
|
}
|
|
}
|
|
|
|
// == Unix permission self-check (defense-in-depth after auto-update) ==
|
|
if (!OperatingSystem.IsWindows())
|
|
{
|
|
var exePath = Environment.ProcessPath;
|
|
if (!string.IsNullOrEmpty(exePath))
|
|
{
|
|
try
|
|
{
|
|
var mode = File.GetUnixFileMode(exePath);
|
|
if ((mode & UnixFileMode.UserExecute) == 0)
|
|
{
|
|
File.SetUnixFileMode(exePath, mode | UnixFileMode.UserExecute);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Best-effort; if we're running, we already have execute permission
|
|
}
|
|
}
|
|
}
|
|
|
|
// == Normal startup ==
|
|
var appSettingsPath = Path.Combine(AppContext.BaseDirectory, "appsettings.json");
|
|
if (!File.Exists(appSettingsPath))
|
|
{
|
|
using var stream = typeof(AppOrchestrator).Assembly
|
|
.GetManifestResourceStream("EchoHub.Client.appsettings.example.json");
|
|
|
|
if (stream is not null)
|
|
{
|
|
using var file = File.Create(appSettingsPath);
|
|
stream.CopyTo(file);
|
|
}
|
|
}
|
|
|
|
var configuration = new ConfigurationBuilder()
|
|
.SetBasePath(AppContext.BaseDirectory)
|
|
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
|
|
.Build();
|
|
|
|
// Explicit sink-assembly reference is required under PublishSingleFile — the default
|
|
// AssemblyFinder scans for Serilog.Sinks.*.dll on disk, which don't exist in a bundled exe.
|
|
var serilogOptions = new ConfigurationReaderOptions(typeof(FileLoggerConfigurationExtensions).Assembly);
|
|
Log.Logger = new LoggerConfiguration()
|
|
.ReadFrom.Configuration(configuration, serilogOptions)
|
|
.CreateLogger();
|
|
|
|
Log.Information("EchoHub client starting");
|
|
|
|
// == Ensure echohub is on PATH for convenient terminal access ============
|
|
PathSetup.EnsureOnPath();
|
|
|
|
// == Post-update detection: stale backup cleanup or flag for rollback menu ================
|
|
if (UpdateBackupService.BackupExists())
|
|
{
|
|
var backupInfo = UpdateBackupService.GetBackupInfo();
|
|
if (backupInfo is not null && DateTimeOffset.UtcNow - backupInfo.CreatedAt > TimeSpan.FromDays(7))
|
|
{
|
|
Log.Information("Deleting stale update backup from {Date}", backupInfo.CreatedAt);
|
|
UpdateBackupService.DeleteBackup();
|
|
}
|
|
else
|
|
{
|
|
Log.Information("Post-update: backup of v{OldVersion} available for rollback",
|
|
backupInfo?.Version ?? "unknown");
|
|
UpdateBackupService.IsPostUpdate = true;
|
|
}
|
|
}
|
|
|
|
// == Windows: clean up .old executable left by rollback restore ===========
|
|
if (OperatingSystem.IsWindows())
|
|
{
|
|
var currentExe = Environment.ProcessPath;
|
|
if (!string.IsNullOrEmpty(currentExe))
|
|
{
|
|
var oldExe = currentExe + ".old";
|
|
if (File.Exists(oldExe))
|
|
{
|
|
try { File.Delete(oldExe); }
|
|
catch { /* locked or permission issue — will be cleaned next launch */ }
|
|
}
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
var config = ConfigManager.Load();
|
|
Log.Information("Configuration loaded, active theme: {Theme}", config.ActiveTheme);
|
|
|
|
var app = Application.Create().Init();
|
|
|
|
var theme = ThemeManager.GetTheme(config.ActiveTheme);
|
|
ThemeManager.ApplyTheme(theme);
|
|
|
|
var orchestrator = new AppOrchestrator(app, config);
|
|
|
|
app.Run(orchestrator.MainWindow);
|
|
|
|
// Capture any confirmed update before tearing anything down, then restore the console.
|
|
var pendingUpdate = orchestrator.PendingUpdate;
|
|
app.Dispose();
|
|
|
|
// Apply the update on a clean console: the TUI has released it, so the updater can extract
|
|
// and restart the process without deadlocking against the alternate-screen buffer. This call
|
|
// ends by starting the new version and calling Environment.Exit, so it does not return.
|
|
if (pendingUpdate is not null)
|
|
{
|
|
Log.Information("Applying confirmed update after shutdown");
|
|
await pendingUpdate();
|
|
}
|
|
|
|
orchestrator.Dispose();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Fatal(ex, "EchoHub client crashed");
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
Log.Information("EchoHub client shutting down");
|
|
Log.CloseAndFlush();
|
|
}
|