VPS confirmed the releases/ org is world-readable without auth; the ClaudeDo source already lives at git.kuns.dev/releases/ClaudeDo, so the workflow uses the built-in gitea.token (no cross-org PAT needed). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
279 lines
12 KiB
Markdown
279 lines
12 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 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/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 branches into Install / Update / Manage modes.
|
|
|
|
## 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**: 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 (`<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 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-<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.
|
|
|
|
### 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/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)
|
|
|
|
### 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-<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 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`.
|