From bd7d5940a22fb242f26751437b8fd9f6b7168fdf Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Wed, 15 Apr 2026 08:03:40 +0200 Subject: [PATCH] docs(installer): add download-mode + Gitea Releases design spec Design for rewriting the installer to fetch prebuilt binaries from Gitea Releases on git.kuns.dev instead of building from source. Covers the Actions workflow, release artifact layout, install.json marker file, Install/Update/Manage mode detection, and the new DownloadAndExtractStep. Co-Authored-By: Claude Opus 4.6 (1M context) --- ...26-04-15-installer-download-mode-design.md | 274 ++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-15-installer-download-mode-design.md diff --git a/docs/superpowers/specs/2026-04-15-installer-download-mode-design.md b/docs/superpowers/specs/2026-04-15-installer-download-mode-design.md new file mode 100644 index 0000000..5b82c9a --- /dev/null +++ b/docs/superpowers/specs/2026-04-15-installer-download-mode-design.md @@ -0,0 +1,274 @@ +# 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-.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 will +be reused for the Manage-mode branch described 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/{owner}/ClaudeDo`. + +**2) An installer rewrite** that replaces the three publish/deploy steps with +a single `DownloadAndExtractStep`, detects existing installs via a marker +file, and branches into Install / Update / Manage modes. + +## Release Artifacts + +Each `v*` tag produces a Gitea Release with three assets: + +``` +ClaudeDo--win-x64.zip # contains /app and /worker subdirs +ClaudeDo.Installer-.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**: framework-dependent (`--self-contained false`), same as + today. This assumes the target machine has the .NET 8 Desktop Runtime. + *Open decision — see "Decisions to revisit".* +- **Checksums file** — plain text, one line per asset (` `), + verified by installer before extract. + +The "latest installer exe" URL is stable: + +``` +https://git.kuns.dev/{owner}/ClaudeDo/releases/latest/download/ClaudeDo.Installer-.exe +``` + +(Gitea also exposes `/releases/download//` 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 false /p:Version=$VERSION -o out/app` + 5. `dotnet publish src/ClaudeDo.Worker -c Release -r win-x64 --self-contained false /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--win-x64.zip` with + `app/` and `worker/` as top-level dirs + 8. Copy `out/installer/ClaudeDo.Installer.exe` to + `ClaudeDo.Installer-.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 secrets** — `gitea.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`: + +```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. + +### Mode detection (`InstallModeDetector`) + +| `install.json` state | Mode | Wizard flow | +|-------------------------------------------|---------------------|-----------------------------------------------------| +| absent | **Install** | Welcome → Paths → UiSettings → Service → Install | +| present, version < installer's version | **Update** | Welcome (shows old→new) → Install (download + swap) | +| present, version == installer's version | **Manage** | Welcome → choose: Repair · Edit Config · Uninstall | +| present, version > installer's version | **Downgrade warn** | Welcome explains; user can cancel or force Install | + +The installer's own version comes from its assembly (`AssemblyInformational +Version`), set by the workflow's `/p:Version=$VERSION`. + +### New step: `DownloadAndExtractStep` + +Replaces `PublishAppStep`, `PublishWorkerStep`, `DeployBinariesStep`. + +``` +1. GET https://git.kuns.dev/api/v1/repos/{owner}/ClaudeDo/releases/latest + Parse tag_name and asset URLs for: + - ClaudeDo--win-x64.zip + - checksums.txt +2. Download both to %TEMP%\ClaudeDo-install-\ +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 — 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) + +### Manage mode — branches + +- **Repair:** re-download + extract (same as Update), re-create shortcuts, + re-register service. Leaves config/DB alone. +- **Edit Config:** opens Paths / UiSettings / Service pages prefilled from + existing `~/.todo-app/*.json`. Save writes the JSON files and offers + "Restart service" if worker config changed. No download. +- **Uninstall:** stops + unregisters service, removes shortcuts, deletes + `InstallDir`. Asks separately whether to delete `~/.todo-app` (config + DB). + +### 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. **Framework-dependent vs self-contained App/Worker.** Framework-dependent + keeps the zip small (~10 MB) but requires the user to install the .NET 8 + Desktop Runtime separately. Self-contained is ~80 MB per process but + zero-dependency. Current plan: framework-dependent, matches today's + behavior. If install friction becomes a problem, flip the flag in the + workflow and installer messaging. + +2. **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. + +3. **Signed installer.** Out of scope for v1. Users will see a SmartScreen + warning the first time. Note this in the README. + +4. **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 and no .NET SDK: + 1. Download `ClaudeDo.Installer-.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 shows Manage mode. +- Publishing a new tag, then running the installer on the existing install, + performs an update without touching `~/.todo-app/todo.db` or the config + JSONs. +- The entire release pipeline runs on `git.kuns.dev` with no manual steps + beyond `git tag vX.Y.Z && git push --tags`.