From 9dd3d214c0ee9a824e353996d11fe76869dd2791 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 12 Nov 2025 13:11:11 +0000 Subject: [PATCH] Fix install.sh malformed download URL when latest release is draft (related to #669) The installer was constructing malformed download URLs like: https://github.com/.../download/location: https://github.com/.../pulse-location: ... This occurred when the latest GitHub release is a draft: 1. /releases/latest API returns nothing (drafts don't count as "latest") 2. Fallback redirect scraper gets "location: .../releases" (no /tag/) 3. sed regex fails to match but echoes the entire header line 4. That malformed string becomes LATEST_RELEASE, breaking the download URL Fixed by: 1. Switch both stable and RC channels to use /releases endpoint 2. Filter JSON to get first non-draft (and non-prerelease for stable) 3. Harden redirect scraper to only match when /tag/ is actually present 4. Fall through to v4.5.1 hardcoded fallback if both methods fail This ensures the installer works correctly when latest release is draft, during DNS issues, and when GitHub API is unavailable. --- install.sh | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/install.sh b/install.sh index 69e77fb..4525c22 100755 --- a/install.sh +++ b/install.sh @@ -1625,21 +1625,30 @@ download_pulse() { fi # Get appropriate release based on channel (with timeout) - if [[ "$UPDATE_CHANNEL" == "rc" ]]; then - # Get all releases and find the latest (including pre-releases) - # Use timeout command for extra protection against hanging - if command -v timeout >/dev/null 2>&1; then - LATEST_RELEASE=$(timeout 15 curl -s --connect-timeout 10 --max-time 30 https://api.github.com/repos/$GITHUB_REPO/releases 2>/dev/null | grep '"tag_name":' | head -1 | sed -E 's/.*"([^"]+)".*/\1/' || true) - else - LATEST_RELEASE=$(curl -s --connect-timeout 10 --max-time 30 https://api.github.com/repos/$GITHUB_REPO/releases 2>/dev/null | grep '"tag_name":' | head -1 | sed -E 's/.*"([^"]+)".*/\1/' || true) - fi + # Both stable and RC channels now use /releases endpoint to handle draft releases + if command -v timeout >/dev/null 2>&1; then + RELEASES_JSON=$(timeout 15 curl -s --connect-timeout 10 --max-time 30 https://api.github.com/repos/$GITHUB_REPO/releases 2>/dev/null || true) else - # Get latest stable release only - # Use timeout command for extra protection against hanging - if command -v timeout >/dev/null 2>&1; then - LATEST_RELEASE=$(timeout 15 curl -s --connect-timeout 10 --max-time 30 https://api.github.com/repos/$GITHUB_REPO/releases/latest 2>/dev/null | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' || true) + RELEASES_JSON=$(curl -s --connect-timeout 10 --max-time 30 https://api.github.com/repos/$GITHUB_REPO/releases 2>/dev/null || true) + fi + + if [[ -n "$RELEASES_JSON" ]]; then + if [[ "$UPDATE_CHANNEL" == "rc" ]]; then + # RC channel: Get latest release (including pre-releases, but skip drafts) + if command -v jq >/dev/null 2>&1; then + LATEST_RELEASE=$(echo "$RELEASES_JSON" | jq -r '[.[] | select(.draft == false)][0].tag_name' 2>/dev/null || true) + else + # Fallback without jq: grep for first non-draft tag_name + LATEST_RELEASE=$(echo "$RELEASES_JSON" | grep -v '"draft": true' | grep '"tag_name":' | head -1 | sed -E 's/.*"([^"]+)".*/\1/' || true) + fi else - LATEST_RELEASE=$(curl -s --connect-timeout 10 --max-time 30 https://api.github.com/repos/$GITHUB_REPO/releases/latest 2>/dev/null | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' || true) + # Stable channel: Get latest non-draft, non-prerelease + if command -v jq >/dev/null 2>&1; then + LATEST_RELEASE=$(echo "$RELEASES_JSON" | jq -r '[.[] | select(.draft == false and .prerelease == false)][0].tag_name' 2>/dev/null || true) + else + # Fallback without jq: filter out both draft and prerelease + LATEST_RELEASE=$(echo "$RELEASES_JSON" | awk '/"draft": true/,/"tag_name":/ {next} /"prerelease": true/,/"tag_name":/ {next} /"tag_name":/ {print; exit}' | sed -E 's/.*"([^"]+)".*/\1/' || true) + fi fi fi @@ -1647,9 +1656,9 @@ download_pulse() { if [[ -z "$LATEST_RELEASE" ]]; then print_info "GitHub API unavailable, trying alternative method..." if command -v timeout >/dev/null 2>&1; then - LATEST_RELEASE=$(timeout 10 curl -sI --connect-timeout 5 --max-time 10 https://github.com/$GITHUB_REPO/releases/latest 2>/dev/null | grep -i '^location:' | sed -E 's|.*tag/([^[:space:]]+).*|\1|' | tr -d '\r' || true) + LATEST_RELEASE=$(timeout 10 curl -sI --connect-timeout 5 --max-time 10 https://github.com/$GITHUB_REPO/releases/latest 2>/dev/null | grep -i '^location:.*\/tag\/' | sed -E 's|.*tag/([^[:space:]]+).*|\1|' | tr -d '\r' || true) else - LATEST_RELEASE=$(curl -sI --connect-timeout 5 --max-time 10 https://github.com/$GITHUB_REPO/releases/latest 2>/dev/null | grep -i '^location:' | sed -E 's|.*tag/([^[:space:]]+).*|\1|' | tr -d '\r' || true) + LATEST_RELEASE=$(curl -sI --connect-timeout 5 --max-time 10 https://github.com/$GITHUB_REPO/releases/latest 2>/dev/null | grep -i '^location:.*\/tag\/' | sed -E 's|.*tag/([^[:space:]]+).*|\1|' | tr -d '\r' || true) fi fi