feat(desktop): self-update for the lets-chat-desktop binary #103
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/desktop-self-update"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a self-update path to the
lets-chat-desktopTao+Wry wrapper for Linux x86_64 and Windows x86_64. The previous state had only--version//versionreporting and zero update logic.Client (desktop/src/update.rs)
Fetches
${LETS_CHAT_UPDATE_URL}/latest/latest.json, parses the manifest below, and on--updatedownloads the platform-matching binary into a temp file, marks it +x on Unix, then callsself_replace::self_replace(...). Theself-replacecrate handles the Windows "can't delete a running .exe" restriction by renaming the live binary aside before writing the new one, so the same code path works on both platforms.Manifest shape (small on purpose):
Dependencies added:
ureq(default rustls TLS + webpki-roots, no native-TLS build deps),serde/serde_json(workspace),self-replace.CLI surface
lets-chat-desktop --check-updateprints availability and exits.lets-chat-desktop --updatedownloads + replaces; idempotent (no-op + exit 0 when already at the latest).lets-chat-desktop(default GUI launch) spawns one backgroundcheck()and prints a single stderr line if an update is available; never auto-installs.CI (.forgejo/workflows/publish-release.yml)
Triggers on
v*tag pushes, cross-builds both desktop binaries from the existingci-build/Dockerfile.desktop-{linux,windows}images, generateslatest.json, and uploads to the Forgejo Generic Packages API at/api/packages/a8n-tools/generic/lets-chat/{version}/{filename}plus/api/packages/.../lets-chat/latest/latest.json. Each upload is preceded by a best-effortDELETEso the manifest atlatest/can be overwritten release-over-release.Config
LETS_CHAT_UPDATE_URL(defaulthttps://dev.a8n.run/api/packages/a8n-tools/generic/lets-chat) overrides the manifest root - useful for forks, staging mirrors, and the local smoke fixture below. Documented in CLAUDE.md's env-var table.Test plan
just check-desktop,just check-fmt, and./dev/cargo-desktop clippy -p lets-chat-desktopare clean (the pre-existing build-scriptuninlined_format_argswarnings are not in the new code).docker buildx build -f ci-build/Dockerfile.desktop-windows .succeeds end-to-end with the new deps (3.7 MBlets-chat-desktop.exeproduced).python3 -m http.serverfixture serving a fake manifest at/latest/latest.jsonand a sentinel "binary" at/v99.0.0/lets-chat-desktop-linux-x86_64:--check-updateagainst the fixture printsupdate available: v99.0.0 (current: 0.1.0)and exits 0.--updatedownloads, replaces the binary in place (post-swap content equals the sentinel), and exits 0.--check-update/--updateagainst a manifest at the same version exits 0 withalready at latest version: 0.1.0.publish-release.ymluploads three files to Generic Packages and that the manifest URL responds with valid JSON.lets-chat-desktop.exe --updateand confirm the in-place swap works (this needs manual verification by a Windows user; cross-compile + the self-replace crate's Windows path are validated only at the type/link level here).Adds a small self-update path to the desktop crate so the Tao+Wry wrapper can refresh itself in place on both Linux x86_64 and Windows x86_64. Client side. A new desktop/src/update.rs module fetches a JSON manifest from `${LETS_CHAT_UPDATE_URL}/latest/latest.json` (default points at the Forgejo Generic Packages root for a8n-tools/lets-chat), compares the manifest's version to the compile-time CARGO_PKG_VERSION with a loose semver-ish parse, downloads the matching platform binary, sets +x on Unix, and atomically swaps the running executable using the `self-replace` crate (which handles the Windows-cant-delete-running-exe restriction by renaming the live binary aside before writing the new one). The dependency footprint is intentionally small: `ureq` with default rustls TLS + webpki-roots for HTTPS, `serde` + `serde_json` from the workspace for the manifest, and `self-replace` for the swap. CLI surface. `lets-chat-desktop --check-update` prints whether a newer version is available and exits without touching anything. `lets-chat-desktop --update` performs the swap and exits with the message "updated to V; restart lets-chat-desktop to use the new binary"; if already on the latest version it is a no-op that exits 0 with a confirmation message. The default GUI path spawns a background thread that runs `check()` once at startup and prints a single stderr line if an update is available, but never auto-installs. Release plumbing. A new .forgejo/workflows/publish-release.yml fires on `v*` tag pushes, cross-builds the Linux and Windows desktop binaries from the existing ci-build Dockerfiles, generates `latest.json` referencing `${GITHUB_SERVER_URL}/api/packages/a8n-tools/generic/lets-chat/{version}/{filename}` for each platform, and uploads the binaries + the manifest to the Forgejo Generic Packages API. The manifest path `latest/latest.json` is overwritten every release; each upload is preceded by a best-effort DELETE so the PUT does not collide. Configuration. `LETS_CHAT_UPDATE_URL` (documented in CLAUDE.md's env-var table) overrides the default Generic Packages root and is used both for forks/staging mirrors and for the local fixture in the smoke test described below. Smoke test. Built the release binary, copied it out of the cargo target volume, stood up a python http.server fixture serving a fake manifest at /latest/latest.json plus a sentinel "binary" at /v99.0.0/lets-chat-desktop-linux-x86_64, and confirmed the four paths: `--check-update` reports `update available: v99.0.0`; `--update` downloads and replaces the binary on disk (post-swap content matches the sentinel); `--update` against a manifest at the same version emits `already at latest version: 0.1.0` and exits 0; pointing at an unreachable endpoint surfaces the network error and exits 1. Cross-compile for x86_64-pc-windows-gnu via ci-build/Dockerfile.desktop-windows also succeeds end-to-end with the new dependencies (ureq, self-replace, serde, serde_json). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>