diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 2b615f9..1532f16 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -145,18 +145,19 @@ jobs: ZIP_NAME="ClaudeDo-${VERSION}-win-x64.zip" ( cd bundle && zip -r -q "../assets/${ZIP_NAME}" app worker ) - # 2) Installer single-file exe (renamed) + # 2) Installer single-file exe — STABLE name (no version) so the download URL + # (…/releases/latest/download/ClaudeDo.Installer.exe) stays permanent. INSTALLER_EXE=$(ls out/installer/*.exe | head -n 1) if [ -z "$INSTALLER_EXE" ]; then echo "::error::No .exe produced by installer publish" >&2 exit 1 fi - cp "$INSTALLER_EXE" "assets/ClaudeDo.Installer-${VERSION}.exe" + cp "$INSTALLER_EXE" "assets/ClaudeDo.Installer.exe" # 3) Checksums (sha256, relative filenames) ( cd assets && sha256sum \ "ClaudeDo-${VERSION}-win-x64.zip" \ - "ClaudeDo.Installer-${VERSION}.exe" \ + "ClaudeDo.Installer.exe" \ > checksums.txt ) echo "--- assets ---" @@ -200,7 +201,7 @@ jobs: cd "$WORK/src/assets" for f in \ "ClaudeDo-${VERSION}-win-x64.zip" \ - "ClaudeDo.Installer-${VERSION}.exe" \ + "ClaudeDo.Installer.exe" \ "checksums.txt" do echo "Uploading: $f" diff --git a/src/ClaudeDo.Installer/App.xaml.cs b/src/ClaudeDo.Installer/App.xaml.cs index 6f54349..a37d80f 100644 --- a/src/ClaudeDo.Installer/App.xaml.cs +++ b/src/ClaudeDo.Installer/App.xaml.cs @@ -38,104 +38,6 @@ public partial class App : Application var localizer = new Localizer(localeStore, initialLang); TrExtension.Localizer = localizer; - // --- Self-update pre-flight --- - // Resolve current exe path. Assembly.Location may point to a .dll for apphost-based - // .NET apps; swap to the .exe companion when that happens. - var currentExePath = Assembly.GetEntryAssembly()!.Location; - if (currentExePath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) - { - currentExePath = System.IO.Path.ChangeExtension(currentExePath, ".exe"); - } - - // Arg form: --replace-self "" - var replaceSelfIndex = Array.FindIndex(e.Args, a => a.Equals("--replace-self", StringComparison.OrdinalIgnoreCase)); - if (replaceSelfIndex >= 0 && replaceSelfIndex + 1 < e.Args.Length) - { - var oldPath = e.Args[replaceSelfIndex + 1]; - var relaunched = await SelfUpdater.HandleReplaceSelfAsync( - oldPath: oldPath, - currentExePath: currentExePath, - launchProcess: path => - { - try - { - System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true }); - return true; - } - catch { return false; } - }); - if (relaunched) - { - Shutdown(0); - return; - } - // Replacement failed — fall through to normal wizard from the temp location. - } - else - { - // Normal launch: check for a newer installer. - using var selfUpdateHttp = new HttpClient { Timeout = TimeSpan.FromSeconds(10) }; - var selfUpdateReleases = new ReleaseClient(selfUpdateHttp); - var currentVersion = GetInstallerVersion(); - - var decision = await SelfUpdater.DecideUpdateAsync(selfUpdateReleases, currentVersion, CancellationToken.None); - if (decision.Kind == SelfUpdateDecisionKind.UpdateAvailable) - { - var prompt = new SelfUpdatePromptWindow(currentVersion, decision.LatestVersion!); - DarkTitleBar.Apply(prompt); - var ok = prompt.ShowDialog() == true; - if (!ok) - { - Shutdown(0); - return; - } - if (prompt.Choice == SelfUpdateChoice.Update) - { - prompt.ShowProgress("Downloading..."); - var tempDir = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "ClaudeDo.Installer.Update"); - var verifiedPath = await SelfUpdater.DownloadAndVerifyAsync( - selfUpdateReleases, - decision.InstallerAsset!, - decision.ChecksumsAsset!, - tempDir, - new Progress(_ => { }), - CancellationToken.None); - - if (verifiedPath is null) - { - MessageBox.Show(prompt, - "Update download or verification failed. Continuing with current installer.", - "ClaudeDo Installer", MessageBoxButton.OK, MessageBoxImage.Warning); - } - else - { - try - { - var psi = new System.Diagnostics.ProcessStartInfo(verifiedPath) - { - UseShellExecute = true, - }; - psi.ArgumentList.Add("--replace-self"); - psi.ArgumentList.Add(currentExePath); - System.Diagnostics.Process.Start(psi); - Shutdown(0); - return; - } - catch (Exception ex) - { - MessageBox.Show(prompt, - "Failed to launch updated installer: " + ex.Message + "\nContinuing with current installer.", - "ClaudeDo Installer", MessageBoxButton.OK, MessageBoxImage.Warning); - } - } - } - // SelfUpdateChoice.Continue — fall through to normal wizard. - } - // No-update or check failed — fall through to normal wizard. - } - - // --- Existing wizard start-up unchanged below this line --- - _services = BuildServices(localizer); var context = _services.GetRequiredService(); diff --git a/src/ClaudeDo.Installer/CLAUDE.md b/src/ClaudeDo.Installer/CLAUDE.md index e0667b0..2b4181e 100644 --- a/src/ClaudeDo.Installer/CLAUDE.md +++ b/src/ClaudeDo.Installer/CLAUDE.md @@ -12,14 +12,22 @@ Note: this is the one project where `System.Windows` is correct (WPF, not Avalon - Entry point: `App.xaml` / `App.xaml.cs` (no `Program.cs`) - References: `ClaudeDo.Data`, `ClaudeDo.Releases`, `ClaudeDo.Localization` - Manifests: `app.manifest` (requireAdministrator, Release) / `app.debug.manifest` (asInvoker, Debug) -- Only CLI arg: `--replace-self ` (self-update handoff) +- No CLI args — mode is detected from `install.json` + the Gitea API ## Startup Sequence (`App.OnStartup`) 1. Load locale -2. Self-update preflight — `SelfUpdater.DecideUpdateAsync` checks Gitea API; if a newer installer exists, download + checksum verify + relaunch with `--replace-self ` -3. Detect mode — `InstallModeDetector` reads `install.json` + Gitea API -4. Open `WizardWindow` (FreshInstall / Update) or `SettingsWindow` (Config) +2. Detect mode — `InstallModeDetector` reads `install.json` + Gitea API +3. Open `WizardWindow` (FreshInstall / Update) or `SettingsWindow` (Config) + +The installer does **not** self-update. Each release ships a stable-named +`ClaudeDo.Installer.exe` asset (permanent URL +`…/releases/latest/download/ClaudeDo.Installer.exe`); the installer never checks for or +replaces itself on launch. The in-app "Update" button relaunches the on-disk installer to +run the app update — the installer binary itself only changes when the user downloads a +fresh copy. App-update detection is unaffected: `WriteInstallManifestStep` records +`ctx.InstalledVersion` (the release tag from `DownloadAndExtractStep`), which +`InstallModeDetector` compares against the latest tag. ## Modes (`Core/InstallerMode.cs`) @@ -56,8 +64,7 @@ Installer/ Interfaces/ — IInstallStep + StepResult/StepStatus/StepProgress, IInstallerPage Pages/ — WelcomePage, PathsPage, ServicePage, UiSettingsPage, InstallPage (each: ViewModel + View.xaml) - Views/ — WizardWindow(+WizardViewModel), SettingsWindow(+SettingsViewModel), - SelfUpdatePromptWindow + Views/ — WizardWindow(+WizardViewModel), SettingsWindow(+SettingsViewModel) ``` ## Key Step Behaviors diff --git a/src/ClaudeDo.Installer/Steps/WriteUninstallRegistryStep.cs b/src/ClaudeDo.Installer/Steps/WriteUninstallRegistryStep.cs index 69db9ea..90d9627 100644 --- a/src/ClaudeDo.Installer/Steps/WriteUninstallRegistryStep.cs +++ b/src/ClaudeDo.Installer/Steps/WriteUninstallRegistryStep.cs @@ -26,9 +26,9 @@ public sealed class WriteUninstallRegistryStep : IInstallStep // the single-file temp extract is gone once this process exits. var sourceExe = Environment.ProcessPath ?? throw new InvalidOperationException("Cannot resolve running installer path."); - // In the self-update path the installer already runs from uninstaller/ (the - // --replace-self handoff put it there), so source == target and the copy would - // throw. Skip it; the binary is already in place. + // When relaunched from the installed copy (e.g. the Apps & Features "Rerun + // Installer" entry points at uninstaller/ClaudeDo.Installer.exe), source == target + // and the copy would throw. Skip it; the binary is already in place. var alreadyInPlace = string.Equals( Path.GetFullPath(sourceExe), Path.GetFullPath(targetExe), StringComparison.OrdinalIgnoreCase); if (!alreadyInPlace) diff --git a/src/ClaudeDo.Installer/Views/SelfUpdatePromptWindow.xaml b/src/ClaudeDo.Installer/Views/SelfUpdatePromptWindow.xaml deleted file mode 100644 index 1eb9c06..0000000 --- a/src/ClaudeDo.Installer/Views/SelfUpdatePromptWindow.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - -