- 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>
317 lines
13 KiB
Markdown
317 lines
13 KiB
Markdown
# 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 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.
|
|
|
|
### 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`.
|