diff --git a/.config/mise/install.sh b/.config/mise/install.sh index d12a4f62876..c53c483804a 100755 --- a/.config/mise/install.sh +++ b/.config/mise/install.sh @@ -18,9 +18,69 @@ case "$(uname -m)" in esac tarball="mise-v${MISE_VERSION}-linux-${arch}.tar.gz" +base_url="https://github.com/jdx/mise/releases/download/v${MISE_VERSION}" + +# curl needs the header passed explicitly, and plain `-L` drops it on a cross-host redirect. +curl_auth=() +github_token="${MISE_GITHUB_TOKEN:-${GITHUB_TOKEN:-}}" +if [[ -n ${github_token} ]]; then + curl_auth=(--header "Authorization: Bearer ${github_token}") +fi + +# Downloads $1 to $2. `--fail` is deliberately omitted so that the response body +# survives to be printed: a bare `curl: (22)` explains nothing about why a build broke. +# `--retry-max-time` bounds the series: curl prefers a server `Retry-After`, and GitHub sends 60. +fetch() { + local url=$1 output=$2 + local headers="${output}.headers" + local status=000 curl_status=0 + + status=$( + curl -sS -L \ + --retry 5 --retry-all-errors --retry-delay 2 --retry-max-time 60 \ + --connect-timeout 10 --max-time 300 \ + ${curl_auth[@]+"${curl_auth[@]}"} \ + --dump-header "${headers}" \ + --write-out '%{http_code}' \ + --output "${output}" \ + "${url}" + ) || curl_status=$? + + if [[ ${curl_status} -ne 0 || ${status} -lt 200 || ${status} -ge 300 ]]; then + { + echo "error: failed to download ${url}" + echo " curl exit code: ${curl_status}" + echo " http status: ${status}" + # `--dump-header` appends, so keep only the headers following the last status line. + awk ' + /^[Hh][Tt][Tt][Pp]\// { count = 0; next } + tolower($0) ~ /^(retry-after|x-ratelimit-[a-z-]+):/ { header[++count] = $0 } + END { for (i = 1; i <= count; i++) print header[i] } + ' "${headers}" 2> /dev/null | tr -d '\r' | sed 's/^/ /' || true + echo " response body (first 2000 bytes):" + head -c 2000 "${output}" 2> /dev/null | tr -d '\r' | tr -c '[:print:]\n\t' '.' | sed 's/^/ /' + echo + } >&2 + return 1 + fi +} cd "$(mktemp -d)" -curl -fsSL -O "https://github.com/jdx/mise/releases/download/v${MISE_VERSION}/${tarball}" -curl -fsSL "https://github.com/jdx/mise/releases/download/v${MISE_VERSION}/SHASUMS256.txt" | grep " \./${tarball}$" | sha256sum -c - + +fetch "${base_url}/${tarball}" "${tarball}" +fetch "${base_url}/SHASUMS256.txt" SHASUMS256.txt + +# Extract the entry first, so a bad response is reported as such and not as a parse failure. +if ! grep -E "^[0-9a-f]{64} (\./)?${tarball}$" SHASUMS256.txt > "${tarball}.sha256"; then + { + echo "error: no checksum entry for ${tarball} in ${base_url}/SHASUMS256.txt" + echo " received (first 2000 bytes):" + head -c 2000 SHASUMS256.txt | tr -d '\r' | tr -c '[:print:]\n\t' '.' | sed 's/^/ /' + echo + } >&2 + exit 1 +fi + +sha256sum -c "${tarball}.sha256" tar --no-same-owner --strip-components=2 -C /usr/local/bin -xzf "${tarball}" mise/bin/mise rm "${tarball}" diff --git a/.github/actions/install-tools/action.yml b/.github/actions/install-tools/action.yml index 9eb84f79382..b327e84d5cd 100644 --- a/.github/actions/install-tools/action.yml +++ b/.github/actions/install-tools/action.yml @@ -22,8 +22,12 @@ runs: using: composite steps: + # `mise install` builds the Rust toolchain by shelling out to `rustup`, so mise's own + # `http_retries` does not cover that download. Non-fatal here, retried below. - name: Run `mise install` with `ci` environment + id: mise-install uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3.5.1 + continue-on-error: true with: # renovate: datasource=github-releases depName=jdx/mise version: 2026.7.14 @@ -32,6 +36,29 @@ runs: MISE_VERBOSE: 1 GITHUB_TOKEN: ${{ inputs.token }} + # Without `mise` on `PATH` there is no install to retry, only five minutes of `not found`. + - name: Fail fast if `mise` itself is missing + if: ${{ steps.mise-install.outcome != 'success' }} + shell: bash + run: | + command -v mise > /dev/null || { + echo "::error::mise is not on PATH: jdx/mise-action failed during its own setup, so there is no install to retry" + exit 1 + } + + # Skipped on the happy path; `!= 'success'` so an unresolved `outcome` retries too. + - name: Retry `mise install` if it failed + if: ${{ steps.mise-install.outcome != 'success' }} + uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2 + with: + timeout_minutes: 20 + max_attempts: 5 + retry_wait_seconds: 60 + command: mise install --env ci --jobs 1 --locked + env: + MISE_VERBOSE: 1 + GITHUB_TOKEN: ${{ inputs.token }} + - name: Install package manager via corepack uses: $/.github/actions/install-corepack