Skip to content

Commit 755b32f

Browse files
fix(install): retry GitHub downloads with exponential backoff
The installer served at https://get.livekit.io/cli makes three unguarded curl calls to GitHub (the releases API, the archive, and checksums.txt). Any transient failure aborts the whole install, which makes CI runners flaky. Route all three through a `fetch` helper that retries connection errors, timeouts, 5xx, 408, 429, and the 403 GitHub returns when rate limiting, with exponential backoff (5 attempts, 2s doubling). Permanent 4xx responses still fail immediately. Attempt count and base delay are overridable via LK_INSTALL_MAX_ATTEMPTS / LK_INSTALL_RETRY_DELAY. Also adds the missing `jq` prerequisite check — the script already depended on jq but only reported it as an unhelpful version parse error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 241824e commit 755b32f

1 file changed

Lines changed: 57 additions & 7 deletions

File tree

install-cli.sh

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,60 @@ FISH_COMPLETION_PATH="/usr/share/fish/vendor_completions.d"
3131
log() { printf "%b\n" "$*"; }
3232
abort() { printf "%s\n" "$@" >&2; exit 1; }
3333

34+
# GitHub is frequently flaky from CI runners, so every download is retried with
35+
# exponential backoff. Overridable for testing / impatient callers.
36+
MAX_ATTEMPTS="${LK_INSTALL_MAX_ATTEMPTS:-5}"
37+
RETRY_DELAY="${LK_INSTALL_RETRY_DELAY:-2}"
38+
39+
# fetch <url> <output-file>
40+
#
41+
# Downloads a URL, retrying transient failures (connection errors, timeouts,
42+
# 5xx, 408, 429, and the 403 GitHub returns when rate limiting) with
43+
# exponential backoff. Permanent 4xx responses fail immediately, since no
44+
# amount of retrying will turn a 404 into a release archive.
45+
fetch() {
46+
local url="$1" out="$2"
47+
local attempt=1 delay="$RETRY_DELAY" status rc reason
48+
49+
while :; do
50+
rc=0
51+
status=$(curl -sSL \
52+
--connect-timeout 10 \
53+
--max-time 300 \
54+
--output "$out" \
55+
--write-out '%{http_code}' \
56+
"$url") || rc=$?
57+
58+
if [ "$rc" -eq 0 ] && [ "$status" -ge 200 ] && [ "$status" -lt 300 ]; then
59+
return 0
60+
fi
61+
62+
if [ "$rc" -eq 0 ]; then
63+
reason="HTTP $status"
64+
case "$status" in
65+
408|429|403) ;;
66+
4??) abort "Failed to download $url ($reason)" ;;
67+
esac
68+
else
69+
reason="curl exit $rc"
70+
fi
71+
72+
if [ "$attempt" -ge "$MAX_ATTEMPTS" ]; then
73+
abort "Failed to download $url after $MAX_ATTEMPTS attempts ($reason)"
74+
fi
75+
76+
log "Download of $url failed ($reason), retrying in ${delay}s (attempt $((attempt + 1))/$MAX_ATTEMPTS)..."
77+
sleep "$delay"
78+
attempt=$((attempt + 1))
79+
delay=$((delay * 2))
80+
done
81+
}
82+
3483
[ -n "${BASH_VERSION:-}" ] || abort "This script requires bash"
3584
[ -d "$INSTALL_PATH" ] || abort "Could not install, $INSTALL_PATH doesn't exist"
3685
command -v curl >/dev/null || abort "cURL is required and is not found"
3786
command -v sha256sum >/dev/null || abort "sha256sum is required and is not found"
87+
command -v jq >/dev/null || abort "jq is required and is not found"
3888

3989
OS="$(uname)"
4090
case "$OS" in
@@ -55,8 +105,11 @@ if [ ! -w "$INSTALL_PATH" ]; then
55105
log "sudo is required to install to $INSTALL_PATH"
56106
fi
57107

58-
VERSION=$(curl -fsSL https://api.github.com/repos/livekit/$REPO/releases/latest \
59-
| jq -r '.tag_name' | sed 's/^v//')
108+
TEMP_DIR=$(mktemp -d)
109+
trap 'rm -rf "$TEMP_DIR"' EXIT
110+
111+
fetch "https://api.github.com/repos/livekit/$REPO/releases/latest" "$TEMP_DIR/latest.json"
112+
VERSION=$(jq -r '.tag_name' "$TEMP_DIR/latest.json" | sed 's/^v//')
60113

61114
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || abort "Invalid version: $VERSION"
62115

@@ -67,11 +120,8 @@ CHECKSUMS_URL="https://github.com/livekit/$REPO/releases/download/v${VERSION}/ch
67120
log "Installing $REPO $VERSION"
68121
log "Downloading from $ARCHIVE_URL..."
69122

70-
TEMP_DIR=$(mktemp -d)
71-
trap 'rm -rf "$TEMP_DIR"' EXIT
72-
73-
curl -fsSL "$ARCHIVE_URL" -o "$TEMP_DIR/$ARCHIVE_NAME"
74-
curl -fsSL "$CHECKSUMS_URL" -o "$TEMP_DIR/checksums.txt"
123+
fetch "$ARCHIVE_URL" "$TEMP_DIR/$ARCHIVE_NAME"
124+
fetch "$CHECKSUMS_URL" "$TEMP_DIR/checksums.txt"
75125

76126
# Verify the archive against the release's checksums.txt before extracting. The checksums
77127
# file is fetched from the same release over HTTPS, so this guards against corrupted/partial

0 commit comments

Comments
 (0)