Files
ClaudeDo/docs/superpowers/specs/2026-04-15-installer-download-mode-design.md
Mika Kuns 0498fbae47 docs(installer): finalize decisions — self-contained, auto-check, full uninstall
- App + Worker now self-contained (zero .NET runtime dep on target)
- Collapse Manage mode into "update check -> Config view" on every
  subsequent launch; Repair + Uninstall become buttons in Config
- Uninstall removes {InstallDir} and ~/.todo-app in full (no prompt
  to keep data) — matches user's stated intent

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 08:21:12 +02:00

13 KiB

Installer: Download-Mode + Gitea Releases

Date: 2026-04-15 Status: Design — awaiting implementation plan

Goal

Turn ClaudeDo.Installer into a self-contained tool that any user can run on any Windows machine to install, update, reconfigure, repair, or uninstall ClaudeDo. The installer pulls prebuilt binaries from a Gitea release on git.kuns.dev instead of building from source.

End-user experience:

  1. Download ClaudeDo.Installer-<version>.exe from the releases page.
  2. Run it.
  3. Done — no .NET SDK, no source checkout, no manual steps.

Non-Goals

  • Code signing the installer or the app binaries (future concern).
  • Cross-platform installs (Windows-only, same as today).
  • In-app update notifications (the installer handles updates when run; the app does not self-update).
  • Arbitrary-version selection UI. Installer always targets "latest" release.
  • A package-manager listing (winget/Chocolatey/Scoop). Future, separate spec.

Current State (2026-04-15)

The existing installer (src/ClaudeDo.Installer/) is a WPF wizard that only works from inside a source checkout on a machine with the .NET SDK installed:

  • PublishAppStep runs dotnet publish src/ClaudeDo.App/...
  • PublishWorkerStep runs dotnet publish src/ClaudeDo.Worker/...
  • DeployBinariesStep copies bin/Release/.../publish into the install dir
  • Subsequent steps (WriteConfigStep, InitDatabaseStep, CreateShortcutsStep, RegisterServiceStep) are fine to keep.

The installer also contains a partial "Settings" window (Views/SettingsWindow.xaml, Views/SettingsViewModel.cs) — that wiring is reused for the Config view shown on subsequent launches (see Mode detection below).

High-Level Design

Two pieces, each small:

1) A Gitea Actions workflow that, on every v* tag push, builds the App, Worker, and Installer; packages them; and creates a Gitea Release on the public repo at git.kuns.dev/releases/ClaudeDo.

The releases/ org on the Gitea instance is world-readable without auth; private work (including the source repo, if you want) lives under kuns/* which is never public. The installer only needs to hit releases/ClaudeDo.

2) An installer rewrite that replaces the three publish/deploy steps with a single DownloadAndExtractStep, detects existing installs via a marker file, and on subsequent launches checks the Gitea API for updates before deciding whether to show the Update flow or jump straight to the Config view.

Release Artifacts

Each v* tag produces a Gitea Release with three assets:

ClaudeDo-<version>-win-x64.zip      # contains /app and /worker subdirs
ClaudeDo.Installer-<version>.exe    # self-contained installer (no .NET needed)
checksums.txt                       # SHA256 of the above

Decisions:

  • One combined app+worker zip (not two separate). Reasons: one download, one extract, guaranteed version-locked pair.
  • Self-contained installer exe — user does not need .NET installed.
  • App + Worker: self-contained (--self-contained true, -r win-x64). Zero runtime dependency on the target machine, at the cost of a larger download (~100 MB combined zip). Decision: acceptable trade-off — the installer is one-click, not per-user-problem-to-debug.
  • Checksums file — plain text, one line per asset (<sha256> <filename>), verified by installer before extract.

The "latest installer exe" URL is stable:

https://git.kuns.dev/releases/ClaudeDo/releases/latest/download/ClaudeDo.Installer-<version>.exe

(Gitea also exposes /releases/download/<tag>/<filename> for specific versions.)

Gitea Actions Workflow

File: .gitea/workflows/release.yml

  • Trigger: push on tags matching v*
  • Runner: Linux container with .NET 8 SDK (dotnet publish -r win-x64 works cross-platform). The installer itself requires Windows to run, but dotnet publish can target win-x64 from Linux.
  • Steps:
    1. Checkout
    2. Setup .NET 8 SDK
    3. Derive version from tag (${{ gitea.ref_name }} without the v prefix)
    4. dotnet publish src/ClaudeDo.App -c Release -r win-x64 --self-contained true /p:Version=$VERSION -o out/app
    5. dotnet publish src/ClaudeDo.Worker -c Release -r win-x64 --self-contained true /p:Version=$VERSION -o out/worker
    6. dotnet publish src/ClaudeDo.Installer -c Release -r win-x64 --self-contained true /p:Version=$VERSION /p:PublishSingleFile=true -o out/installer
    7. Zip out/app + out/worker as ClaudeDo-<version>-win-x64.zip with app/ and worker/ as top-level dirs
    8. Copy out/installer/ClaudeDo.Installer.exe to ClaudeDo.Installer-<version>.exe
    9. Generate checksums.txt (sha256sum both files)
    10. Create release via Gitea API using the built-in ${{ gitea.token }} (this token has repo write scope automatically on Actions runs). Release name = tag name. Release notes = git log summary between previous tag and this one (nice-to-have).

The workflow needs no custom secretsgitea.token is sufficient for creating releases on its own repo.

Installer Changes

New: install.json marker file

Written at the end of every successful install or update to {InstallDir}/install.json:

{
  "version": "0.2.0",
  "installDir": "C:\\Program Files\\ClaudeDo",
  "workerDir": "C:\\Program Files\\ClaudeDo\\worker",
  "installedAt": "2026-04-15T12:34:56Z"
}

The installer reads this on startup (from the default install dir, or a path supplied via CLI arg) to decide which mode to run in.

Launch flow (InstallModeDetector)

On every launch, the installer checks for install.json first:

install.json absent?
  -> Install mode: Welcome -> Paths -> UiSettings -> Service -> Install
                  (writes install.json at the end)

install.json present?
  -> Query https://git.kuns.dev/api/v1/repos/releases/ClaudeDo/releases/latest
     (short timeout; if offline, treat as "no update available")

     latest.tag_name > installed.version
       -> Update mode: Welcome ("Update v0.1.0 -> v0.2.0, Update / Later")
          If user accepts -> Install steps (download + swap service)
          If user declines -> fall through to Config view
     latest.tag_name <= installed.version  (or API unreachable)
       -> Config view: directly open Paths/UiSettings/Service tabs,
          prefilled from existing ~/.todo-app/*.json.
          Action buttons: Save · Repair · Uninstall.

Key properties:

  • First run = wizard, as today — no behavior change for new users.
  • Every subsequent run = update check first, then either offer update or drop straight into Config. No "Manage page" with a menu of actions — the Config view is the default, and Repair/Uninstall are buttons on it.
  • Offline / API error = not fatal: if the release endpoint can't be reached, the installer silently skips the update check and opens Config. The user is never blocked from managing an existing install by a network issue.
  • Downgrade (installed version > latest) is treated the same as "no update available" — we don't ever offer a downgrade.

The installer's own version (shown for reference in Config) comes from its assembly (AssemblyInformationalVersion), set by the workflow's /p:Version=$VERSION. The installed version comes from install.json.

New step: DownloadAndExtractStep

Replaces PublishAppStep, PublishWorkerStep, DeployBinariesStep.

1. GET https://git.kuns.dev/api/v1/repos/releases/ClaudeDo/releases/latest
   Parse tag_name and asset URLs for:
     - ClaudeDo-<ver>-win-x64.zip
     - checksums.txt
2. Download both to %TEMP%\ClaudeDo-install-<guid>\
3. Parse checksums.txt, verify SHA256 of the zip. Fail hard if mismatch.
4. (Update mode only) Stop Worker service via sc.exe stop ClaudeDoWorker.
   Wait up to 30s for it to actually stop. If it won't stop, fail.
5. (Update mode only) Delete contents of {InstallDir}/app and
   {InstallDir}/worker, but leave the directories and install.json in place.
6. Extract zip: /app -> {InstallDir}/app, /worker -> {InstallDir}/worker.
7. (Update mode only) Start service again via sc.exe start ClaudeDoWorker.
8. Progress is reported via IProgress<string> — the UI already shows it.

Config files (~/.todo-app/*.json) and DB (~/.todo-app/todo.db) live outside InstallDir and are never touched by this step — updates are naturally non-destructive.

Update mode — which steps run

  • Yes: DownloadAndExtractStep
  • No: WriteConfigStep (user already has config — we don't overwrite)
  • No: InitDatabaseStep (DB exists)
  • No: CreateShortcutsStep (already there; Repair can re-run this)
  • Conditional: RegisterServiceStep only if service is not currently registered (covers someone who unregistered it manually)

Config view — actions

  • Save (primary): writes the Paths / UiSettings / Service fields to ~/.todo-app/*.json. If worker config changed, prompts "Restart service?" and calls sc stop / sc start if accepted. No download.

  • Repair: re-download + extract (same as Update flow), re-create shortcuts, re-register service. Leaves config/DB alone. Confirmation dialog before starting.

  • Uninstall: confirmation dialog ("This removes ClaudeDo and all of your tasks, config, and database. Type UNINSTALL to confirm."). On confirm:

    1. Stop + unregister service (sc stop, sc delete ClaudeDoWorker)
    2. Remove Start Menu / Desktop shortcuts
    3. Delete {InstallDir} (including install.json)
    4. Delete ~/.todo-app in full (config + DB + logs)
    5. Exit

    Everything is removed. No "keep my data" option — that was explicitly declined in the design discussion.

Files to add

src/ClaudeDo.Installer/Core/InstallModeDetector.cs
src/ClaudeDo.Installer/Core/ReleaseClient.cs        // Gitea API + downloads
src/ClaudeDo.Installer/Core/ChecksumVerifier.cs
src/ClaudeDo.Installer/Core/InstallManifest.cs      // read/write install.json
src/ClaudeDo.Installer/Steps/DownloadAndExtractStep.cs
src/ClaudeDo.Installer/Steps/WriteInstallManifestStep.cs
src/ClaudeDo.Installer/Steps/StopServiceStep.cs     // used in Update+Uninstall
src/ClaudeDo.Installer/Steps/StartServiceStep.cs    // used in Update+Repair
.gitea/workflows/release.yml

Files to remove

src/ClaudeDo.Installer/Steps/PublishAppStep.cs
src/ClaudeDo.Installer/Steps/PublishWorkerStep.cs
src/ClaudeDo.Installer/Steps/DeployBinariesStep.cs

Files to update

  • Core/InstallerService.cs — mode-aware step list
  • Core/InstallContext.cs — add Version, Mode, IsFirstInstall fields
  • Pages/WelcomePage/* — content + buttons depend on mode
  • Views/WizardViewModel.cs — route pages based on mode
  • Core/PageResolver.cs — register new/renamed pages
  • ClaudeDo.Installer.csproj — add PublishSingleFile, SelfContained properties (only active when published)

Failure Modes & Recovery

Failure Behavior
No network / Gitea unreachable Step fails with clear message + retry button
API returns no releases yet "No release available — publish a tag first"
Checksum mismatch Step fails, temp files deleted, user prompted retry
Zip extraction fails mid-way (update) InstallDir is left partially empty — user re-runs
Service won't stop Fail before extract; nothing destructive has happened
User cancels mid-download Temp dir cleaned up; install state unchanged

For safety, the DownloadAndExtractStep always downloads + verifies before it deletes the old binaries. An aborted download cannot leave an install in a half-deleted state.

Security

  • All downloads over HTTPS from a pinned host (git.kuns.dev).
  • SHA256 verification before extract (protects against partial downloads and tampered caches on the wire; not a substitute for code signing).
  • No tokens shipped in the installer — repo is public.
  • Worker service runs under the same account as today (no change).

Decisions to Revisit

  1. Release notes content. Auto-generated git log summary vs manual notes in the tag message vs empty. Start empty; revisit when there are enough releases to care.

  2. Signed installer. Out of scope for v1. Users will see a SmartScreen warning the first time. Note this in the README.

  3. Installer distribution page. A simple README.md badge or a pinned "Latest release" link on the Gitea repo home is enough for v1.

Success Criteria

  • On a fresh Windows VM with no source checkout, no .NET runtime, and no .NET SDK:
    1. Download ClaudeDo.Installer-<ver>.exe.
    2. Run it.
    3. Complete the wizard.
    4. ClaudeDo App launches, Worker service is running, a task can be created and picked up.
  • Running the same installer a second time, with no new release published, opens directly in the Config view after a quick update check.
  • Publishing a new tag, then running the installer on the existing install, offers the update; accepting performs it without touching ~/.todo-app/todo.db or the config JSONs.
  • Uninstall leaves no trace: {InstallDir} gone, ~/.todo-app gone, service unregistered, shortcuts removed.
  • The entire release pipeline runs on git.kuns.dev with no manual steps beyond git tag vX.Y.Z && git push --tags.