feat(desktop): self-update for the lets-chat-desktop binary #103

Merged
David merged 1 commit from feat/desktop-self-update into main 2026-05-15 02:07:44 +02:00
Owner

Summary

Adds a self-update path to the lets-chat-desktop Tao+Wry wrapper for Linux x86_64 and Windows x86_64. The previous state had only --version / /version reporting and zero update logic.

Client (desktop/src/update.rs)

Fetches ${LETS_CHAT_UPDATE_URL}/latest/latest.json, parses the manifest below, and on --update downloads the platform-matching binary into a temp file, marks it +x on Unix, then calls self_replace::self_replace(...). The self-replace crate 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):

{
  "version": "v0.2.0",
  "linux_x86_64":   { "url": "https://dev.a8n.run/api/packages/a8n-tools/generic/lets-chat/v0.2.0/lets-chat-desktop-linux-x86_64" },
  "windows_x86_64": { "url": "https://dev.a8n.run/api/packages/a8n-tools/generic/lets-chat/v0.2.0/lets-chat-desktop-windows-x86_64.exe" }
}

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-update prints availability and exits.
  • lets-chat-desktop --update downloads + replaces; idempotent (no-op + exit 0 when already at the latest).
  • lets-chat-desktop (default GUI launch) spawns one background check() 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 existing ci-build/Dockerfile.desktop-{linux,windows} images, generates latest.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-effort DELETE so the manifest at latest/ can be overwritten release-over-release.

Config

LETS_CHAT_UPDATE_URL (default https://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-desktop are clean (the pre-existing build-script uninlined_format_args warnings 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 MB lets-chat-desktop.exe produced).
  • Linux smoke test against a python3 -m http.server fixture serving a fake manifest at /latest/latest.json and a sentinel "binary" at /v99.0.0/lets-chat-desktop-linux-x86_64:
    • --check-update against the fixture prints update available: v99.0.0 (current: 0.1.0) and exits 0.
    • --update downloads, replaces the binary in place (post-swap content equals the sentinel), and exits 0.
    • --check-update / --update against a manifest at the same version exits 0 with already at latest version: 0.1.0.
    • Pointing at an unreachable URL exits 1 with the network error on stderr.
  • On Forgejo CI: tag a release, confirm publish-release.yml uploads three files to Generic Packages and that the manifest URL responds with valid JSON.
  • On a Windows host: run lets-chat-desktop.exe --update and 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).
## Summary Adds a self-update path to the `lets-chat-desktop` Tao+Wry wrapper for Linux x86_64 and Windows x86_64. The previous state had only `--version` / `/version` reporting and zero update logic. ### Client (desktop/src/update.rs) Fetches `${LETS_CHAT_UPDATE_URL}/latest/latest.json`, parses the manifest below, and on `--update` downloads the platform-matching binary into a temp file, marks it +x on Unix, then calls `self_replace::self_replace(...)`. The `self-replace` crate 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): ```json { "version": "v0.2.0", "linux_x86_64": { "url": "https://dev.a8n.run/api/packages/a8n-tools/generic/lets-chat/v0.2.0/lets-chat-desktop-linux-x86_64" }, "windows_x86_64": { "url": "https://dev.a8n.run/api/packages/a8n-tools/generic/lets-chat/v0.2.0/lets-chat-desktop-windows-x86_64.exe" } } ``` 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-update` prints availability and exits. - `lets-chat-desktop --update` downloads + replaces; idempotent (no-op + exit 0 when already at the latest). - `lets-chat-desktop` (default GUI launch) spawns one background `check()` 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 existing `ci-build/Dockerfile.desktop-{linux,windows}` images, generates `latest.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-effort `DELETE` so the manifest at `latest/` can be overwritten release-over-release. ### Config `LETS_CHAT_UPDATE_URL` (default `https://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 - [x] `just check-desktop`, `just check-fmt`, and `./dev/cargo-desktop clippy -p lets-chat-desktop` are clean (the pre-existing build-script `uninlined_format_args` warnings are not in the new code). - [x] `docker buildx build -f ci-build/Dockerfile.desktop-windows .` succeeds end-to-end with the new deps (3.7 MB `lets-chat-desktop.exe` produced). - [x] Linux smoke test against a `python3 -m http.server` fixture serving a fake manifest at `/latest/latest.json` and a sentinel "binary" at `/v99.0.0/lets-chat-desktop-linux-x86_64`: - [x] `--check-update` against the fixture prints `update available: v99.0.0 (current: 0.1.0)` and exits 0. - [x] `--update` downloads, replaces the binary in place (post-swap content equals the sentinel), and exits 0. - [x] `--check-update` / `--update` against a manifest at the same version exits 0 with `already at latest version: 0.1.0`. - [x] Pointing at an unreachable URL exits 1 with the network error on stderr. - [ ] On Forgejo CI: tag a release, confirm `publish-release.yml` uploads three files to Generic Packages and that the manifest URL responds with valid JSON. - [ ] On a Windows host: run `lets-chat-desktop.exe --update` and 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).
feat(desktop): self-update for the lets-chat-desktop binary
Some checks failed
Check / clippy + fmt + tests (pull_request) Failing after 26s
c64692782b
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>
David merged commit 3ba793e37d into main 2026-05-15 02:07:44 +02:00
David deleted branch feat/desktop-self-update 2026-05-15 02:07:44 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!103
No description provided.