diff --git a/docs/design-notes.md b/docs/design-notes.md index 4c82c3f..bebd336 100644 --- a/docs/design-notes.md +++ b/docs/design-notes.md @@ -1174,3 +1174,11 @@ the fix had to cover Read as well as Copy, not just the obviously-serial half. Linux keeps the atotto backend: no local model inflates RSS there, so the fork is cheap and a second native backend would not pay for itself. + +## install.sh resolves the release tag from a redirect, not the API (2026-08-17) + +A user's install failed with `download failed: .../releases/download/https://api.github.com/...` — the "version" was the release's API URL (`.../releases/361182589`, the release *id*). Cause: version resolution parsed the `releases/latest` API JSON with `awk -F'"' '/"tag_name"/ {print $4; exit}'`, which assumes GitHub's pretty-printed one-field-per-line layout. Their machine (personal, likely TLS-inspecting security software) received the same JSON minified onto one line; the awk then matched the whole object and `$4` was the value of its *first* quoted field, `"url"`. Reproduced locally by piping the API response through `jq -c` — output matched the user's screenshot exactly. + +Fix: resolve the tag from the `https://github.com//releases/latest` redirect (`curl -w '%{redirect_url}'`, take the basename). Rejected alternative: a shape-tolerant JSON grep (`grep -o '"tag_name" *: *"[^"]*"'`) — still a hand-rolled JSON parser, and it keeps the api.github.com dependency with its 60 req/hour unauthenticated rate limit, which the old error message already had to apologize for. The redirect removes both failure modes. The Go-side check (`update/check.go`) was never affected — `encoding/json` is whitespace-immune. + +Audit of every other download: model `manifest.txt` (TSV) and `checksums.txt` are line-oriented by design; ggufs/DMG/update-zip are SHA256-gated. The awk was the only shape-dependent parse in the product. diff --git a/install.sh b/install.sh index cefac03..ce3ec43 100755 --- a/install.sh +++ b/install.sh @@ -143,9 +143,15 @@ if [[ -n "$DMG_PATH" ]]; then else if [[ -z "$VERSION" ]]; then log "Resolving latest release..." - VERSION="$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \ - | awk -F'"' '/"tag_name"/ {print $4; exit}')" - [[ -n "$VERSION" ]] || err "could not resolve latest version (GitHub API rate limit?). Set VERSION=vX.Y.Z and retry." + # Resolve the tag from the /releases/latest redirect, not the GitHub API: + # parsing the API JSON with awk assumed pretty-printed one-field-per-line + # output and broke when a TLS-inspecting middlebox re-served the response + # minified (see design-notes). A Location header has no shape to mangle, + # and skipping api.github.com also removes its unauthenticated rate limit. + VERSION="$(curl -fsS -o /dev/null -w '%{redirect_url}' \ + "https://github.com/${REPO}/releases/latest")" + VERSION="${VERSION##*/}" + [[ "$VERSION" == v* ]] || err "could not resolve latest version. Set VERSION=vX.Y.Z and retry." fi log "Installing Zee ${VERSION}" fi