Skip to content

fix(launcher): robust binary download/upgrade (resume, rate-limit, UX)#10575

Merged
mudler merged 2 commits into
masterfrom
fix/launcher-download-resume-retry
Jun 28, 2026
Merged

fix(launcher): robust binary download/upgrade (resume, rate-limit, UX)#10575
mudler merged 2 commits into
masterfrom
fix/launcher-download-resume-retry

Conversation

@localai-bot

@localai-bot localai-bot commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

What

Makes the launcher's LocalAI binary download/upgrade flow robust on flaky networks and shared IPs, and cleans up its UX.

1. First-start 403 from GitHub API (rate limit)

On a fresh Linux start with no LocalAI installed, the download failed with failed to fetch latest release: status 403. Root cause: the unauthenticated api.github.com rate limit is 60 requests/hour per IP, exhausted almost instantly on shared/NAT/CGNAT/cloud addresses (confirmed: x-ratelimit-remaining: 0).

Fix: resolve the latest version by following the github.com/.../releases/latest redirect (final URL is .../releases/tag/<tag>), which is not subject to the API rate limit. Only the version is ever consumed by callers, so the tag is sufficient. The JSON API is kept as a fallback, now honoring GITHUB_TOKEN and reporting rate-limit 403/429 with a clear message instead of an opaque status.

2. Resume + manual Retry (flaky downloads)

A dropped download had no recourse and always restarted from byte 0. Now the binary streams to local-ai.part and resumes via Range: bytes=N- (handling 206/200/416), renaming to the final path only after checksum verification; on checksum failure the file is discarded so the next attempt starts clean. A Retry button appears on failure and resumes from the partial file.

3. Redundant percentage

The status label printed Downloading... N% next to a progress bar already showing the percent. It now shows a human-readable byte readout (Downloading… 12.3 MB / 45.6 MB).

4. Blank space below "View Release Notes"

Progress/install dialogs were hardcoded to oversized dimensions. A new resizeToContent helper sizes each window to its content with a sane minimum width.

Cleanup

Unified the three near-identical download-progress popups into one Launcher.showDownloadProgressWindow helper (and removed a dead unused copy in ui.go). The progress callback now carries (downloaded, total int64) byte counts.

Testing

  • httptest-backed Ginkgo unit tests in release_manager_test.go cover: latest-version-via-redirect, resume (206), fresh-start (200), stale-partial restart (416), checksum-failure cleanup, and byte-progress. Each verified red-then-green by reverting the fix in isolation.
  • The fyne UI files can't be compiled in my sandbox (no X11/GL headers); they are verified by review and rely on CI to build.

Assisted-by: Claude:claude-opus-4-8 [Claude Code]

…alogs

The binary upgrade/download flow had three rough edges:

- The status label printed "Downloading... N%" right next to a progress
  bar already showing the percent. Replace it with a human-readable byte
  readout ("Downloading... 12.3 MB / 45.6 MB").
- A failed download (GitHub releases are flaky) had no recourse and always
  restarted from byte 0. Stream to "<dest>.part" and resume via a
  "Range: bytes=N-" request (handling 206/200/416), renaming to the final
  path only after checksum verification; on checksum failure the file is
  discarded so the next attempt starts clean. Add a Retry button that
  appears on failure and resumes from the partial file.
- Progress/install dialogs were hardcoded to oversized dimensions, leaving
  a blank gap below "View Release Notes". Size each window to its content
  with a sane minimum width.

Also unify the three near-identical download-progress popups into one
Launcher.showDownloadProgressWindow helper (and delete a dead unused copy
in ui.go) so the behaviour stays consistent across every entry point.

The progress callback now reports (downloaded, total) byte counts instead
of a single fraction. Resume/retry behaviour is covered by httptest-backed
unit tests in release_manager_test.go.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
out, err := os.Create(filepath)
var out *os.File
if offset > 0 {
out, err = os.OpenFile(partPath, os.O_WRONLY|os.O_APPEND, 0644)
out, err := os.Create(filepath)
var out *os.File
if offset > 0 {
out, err = os.OpenFile(partPath, os.O_WRONLY|os.O_APPEND, 0644)
if offset > 0 {
out, err = os.OpenFile(partPath, os.O_WRONLY|os.O_APPEND, 0644)
} else {
out, err = os.Create(partPath)
if err := rm.VerifyChecksum(localPath, checksumPath, binaryName); err != nil {
// Discard the corrupt binary (and any leftover partial) so the next
// retry starts from a clean slate rather than resuming corruption.
os.Remove(localPath)
// Discard the corrupt binary (and any leftover partial) so the next
// retry starts from a clean slate rather than resuming corruption.
os.Remove(localPath)
os.Remove(localPath + ".part")
// Resume: append to the existing partial file.
case http.StatusRequestedRangeNotSatisfiable:
// Stale or already-complete partial: discard and restart fresh.
resp.Body.Close()
case http.StatusRequestedRangeNotSatisfiable:
// Stale or already-complete partial: discard and restart fresh.
resp.Body.Close()
os.Remove(partPath)
…I 403

On a fresh Linux start with no LocalAI installed, the download failed with
"failed to fetch latest release: status 403". The cause is the unauthenticated
api.github.com rate limit (60 requests/hour, per IP): on shared/NAT/CGNAT/cloud
addresses it is exhausted almost immediately and every request 403s.

Resolve the latest version by following the github.com "releases/latest"
redirect instead, reading the tag from the final ".../releases/tag/<tag>" URL.
That endpoint is not subject to the API rate limit. Only the version is ever
consumed by callers, so the tag is sufficient. The JSON API is kept as a
fallback, now honoring GITHUB_TOKEN and reporting rate-limit 403/429 clearly
instead of an opaque status code.

Covered by an httptest-backed unit test that asserts the redirect path is used.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
@localai-bot localai-bot changed the title fix(launcher): resume flaky downloads, drop redundant percent, fit dialogs fix(launcher): robust binary download/upgrade (resume, rate-limit, UX) Jun 28, 2026
@mudler
mudler merged commit 923c470 into master Jun 28, 2026
58 of 59 checks passed
@mudler
mudler deleted the fix/launcher-download-resume-retry branch June 28, 2026 10:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants