diff --git a/src/ClaudeDo.Installer/Steps/DownloadAndExtractStep.cs b/src/ClaudeDo.Installer/Steps/DownloadAndExtractStep.cs index 0400e95..aa97f78 100644 --- a/src/ClaudeDo.Installer/Steps/DownloadAndExtractStep.cs +++ b/src/ClaudeDo.Installer/Steps/DownloadAndExtractStep.cs @@ -70,11 +70,16 @@ public sealed class DownloadAndExtractStep : IInstallStep return StepResult.Fail("Checksum mismatch — the downloaded zip may be corrupt or tampered with."); // Only after verification do we touch the install directory. - progress.Report("Clearing previous app/worker binaries..."); + progress.Report("Stashing previous app/worker binaries..."); var appDest = Path.Combine(ctx.InstallDirectory, "app"); var workerDest = Path.Combine(ctx.InstallDirectory, "worker"); - if (Directory.Exists(appDest)) Directory.Delete(appDest, recursive: true); - if (Directory.Exists(workerDest)) Directory.Delete(workerDest, recursive: true); + var appBak = appDest + ".bak"; + var workerBak = workerDest + ".bak"; + + if (Directory.Exists(appBak)) Directory.Delete(appBak, recursive: true); + if (Directory.Exists(workerBak)) Directory.Delete(workerBak, recursive: true); + if (Directory.Exists(appDest)) Directory.Move(appDest, appBak); + if (Directory.Exists(workerDest)) Directory.Move(workerDest, workerBak); progress.Report("Extracting..."); Directory.CreateDirectory(ctx.InstallDirectory); @@ -84,11 +89,19 @@ public sealed class DownloadAndExtractStep : IInstallStep } catch (Exception ex) { + // Roll back to previous binaries. + if (Directory.Exists(appDest)) Directory.Delete(appDest, recursive: true); + if (Directory.Exists(workerDest)) Directory.Delete(workerDest, recursive: true); + if (Directory.Exists(appBak)) Directory.Move(appBak, appDest); + if (Directory.Exists(workerBak)) Directory.Move(workerBak, workerDest); return StepResult.Fail( - $"Extraction failed after old binaries were removed: {ex.Message}. " + - "Your install directory may be incomplete. Re-run the installer to retry."); + $"Extraction failed; previous binaries have been restored: {ex.Message}."); } + // Success — drop stash. + if (Directory.Exists(appBak)) Directory.Delete(appBak, recursive: true); + if (Directory.Exists(workerBak)) Directory.Delete(workerBak, recursive: true); + ctx.InstalledVersion = release.TagName.TrimStart('v', 'V'); return StepResult.Ok(); }