From 3c437de4b349a89f35fad54b53495805248b70d8 Mon Sep 17 00:00:00 2001 From: Chicken Date: Thu, 25 Jun 2026 20:42:52 +0300 Subject: [PATCH] fix: resilient GitHub downloads and version lookups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Downloads: add download_with_fallback() in common/utils.sh — retries each source up to 3x and falls back through GitHub mirror proxies (ghproxy.net, gh-proxy.com), failing only when all sources are exhausted. All GitHub file downloads are routed through it: hiddify_installer.sh, downgrade.sh, the GeoLite DBs in hiddify-panel/install.sh, and the package_manager.sh binary downloader (xray/singbox/warp/wgcf/dnstm). The download.sh and docker-installer.sh bootstraps keep a minimal inline copy since they run before utils.sh is on disk. Version/API lookups (get_release_version, get_pre_release_version, get_commit_version) previously used curl with no timeout, so a stalled TLS handshake could hang forever. They now use --connect-timeout + --retry --retry-all-errors to bound the handshake and retry both timeouts and connection resets. No mirror fallback here: the proxies don't serve api.github.com. --- common/docker-installer.sh | 13 +++++++-- common/downgrade.sh | 2 +- common/download.sh | 28 +++++++++++++++++-- common/hiddify_installer.sh | 2 +- common/package_manager.sh | 5 ++-- common/utils.sh | 55 +++++++++++++++++++++++++++++++++---- hiddify-panel/install.sh | 4 +-- 7 files changed, 92 insertions(+), 17 deletions(-) diff --git a/common/docker-installer.sh b/common/docker-installer.sh index 298620da6..6ad501525 100644 --- a/common/docker-installer.sh +++ b/common/docker-installer.sh @@ -25,7 +25,16 @@ else # Create the 'hiddify-manager' directory mkdir hiddify-manager cd hiddify-manager - wget https://raw.githubusercontent.com/hiddify/Hiddify-Manager/refs/heads/main/docker-compose.yml + _url="https://raw.githubusercontent.com/hiddify/Hiddify-Manager/refs/heads/main/docker-compose.yml" + _mirrors=("" "https://ghproxy.net/" "https://gh-proxy.com/") + # mirror+retry fetch (canonical: download_with_fallback in common/utils.sh); + for _m in "${_mirrors[@]}"; do + for _a in 1 2 3; do + curl -fL --connect-timeout 15 -o docker-compose.yml "${_m}${_url}" && [[ -s docker-compose.yml ]] && break 2 + rm -f docker-compose.yml; sleep 3 + done + done + [[ -s docker-compose.yml ]] || { echo "ERROR: failed to download docker-compose.yml"; exit 1; } fi # Generate random passwords for MySQL and Redis @@ -42,4 +51,4 @@ docker compose pull docker compose up -d # Follow the logs from the containers -docker compose logs -f \ No newline at end of file +docker compose logs -f diff --git a/common/downgrade.sh b/common/downgrade.sh index d3b04e36c..a72953047 100644 --- a/common/downgrade.sh +++ b/common/downgrade.sh @@ -11,7 +11,7 @@ cd .. pip install hiddifypanel==$(get_release_version hiddify-panel) -curl -L -s -o hiddify-manager.zip https://github.com/hiddify/hiddify-manager/releases/latest/download/hiddify-manager.zip +download_with_fallback hiddify-manager.zip https://github.com/hiddify/hiddify-manager/releases/latest/download/hiddify-manager.zip || exit 1 unzip -o hiddify-manager.zip rm hiddify-manager.zip ln -s /opt/hiddify-manager /opt/hiddify-config diff --git a/common/download.sh b/common/download.sh index 4f662a160..108b73e7a 100755 --- a/common/download.sh +++ b/common/download.sh @@ -44,8 +44,32 @@ else # Otherwise, use the input as a branch name base_url="https://raw.githubusercontent.com/hiddify/Hiddify-Manager/refs/heads/main/" fi -curl -sL -o /tmp/hiddify/hiddify_installer.sh $base_url/common/hiddify_installer.sh -curl -sL -o /tmp/hiddify/utils.sh $base_url/common/utils.sh + +function _bootstrap_download() { + local file_name=$1 + local url=$2 + local mirrors=( + "" + "https://ghproxy.net/" + "https://gh-proxy.com/" + ) + for mirror in "${mirrors[@]}"; do + for attempt in 1 2 3; do + echo "Fetching $file_name (mirror='${mirror:-direct}', attempt=$attempt)..." + if curl -fL --connect-timeout 15 --retry 2 -o "$file_name" "${mirror}${url}"; then + [[ -s "$file_name" ]] && return 0 + echo "Empty file, retrying..." + fi + rm -f "$file_name" + sleep 3 + done + done + echo "ERROR: Failed to download $file_name" + return 1 +} + +_bootstrap_download /tmp/hiddify/hiddify_installer.sh "${base_url}common/hiddify_installer.sh" || exit 1 +_bootstrap_download /tmp/hiddify/utils.sh "${base_url}common/utils.sh" || exit 1 chmod 700 /tmp/hiddify/* /tmp/hiddify/hiddify_installer.sh $@ diff --git a/common/hiddify_installer.sh b/common/hiddify_installer.sh index 95db78228..80efafd2d 100755 --- a/common/hiddify_installer.sh +++ b/common/hiddify_installer.sh @@ -287,7 +287,7 @@ function update_from_github() { local file_type=${file_name##*.} mkdir -p /opt/hiddify-manager cd /opt/hiddify-manager - curl -sL -o "$file_name" "$url" + download_with_fallback "$file_name" "$url" || return 1 if [[ "$file_type" == "zip" ]]; then install_package unzip diff --git a/common/package_manager.sh b/common/package_manager.sh index bc9d2d0ef..50566cd16 100755 --- a/common/package_manager.sh +++ b/common/package_manager.sh @@ -111,10 +111,9 @@ download_package() { # Download the file echo "Downloading package $package_name version $requested_version for $arch... current version is $existing_version" local tmp_file=$(mktemp) - curl -sL -o "$tmp_file" "$url" - if [[ $? -ne 0 ]]; then + if ! download_with_fallback "$tmp_file" "$url"; then error "Error downloading file: $url" - rm "$tmp_file" + rm -f "$tmp_file" return 3 fi mv "$tmp_file" "$output_file" diff --git a/common/utils.sh b/common/utils.sh index 596a8973d..41f86ff3d 100644 --- a/common/utils.sh +++ b/common/utils.sh @@ -1,7 +1,9 @@ export venv_path="/opt/hiddify-manager/.venv313" +GH_META_CURL_OPTS="--connect-timeout 10 --retry 3 --retry-all-errors" + function get_commit_version() { - json_data=$(curl -sL -H "Accept: application/json" "https://github.com/hiddify/$1/commits/main.atom") + json_data=$(curl $GH_META_CURL_OPTS -sL -H "Accept: application/json" "https://github.com/hiddify/$1/commits/main.atom") latest_commit_date=$(echo "$json_data" | jq -r '.payload.commitGroups[0].commits[0].committedDate') # xml_data=$(curl -sl "https://github.com/hiddify/$1/commits/main.atom") # latest_commit_date=$(echo "$xml_data" | grep -m 1 '' | awk -F'>|<' '{print $3}') @@ -12,19 +14,19 @@ function get_commit_version() { function get_pre_release_version() { # lastversion "$1" --pre --at github - VERSION=$(curl -sL "https://api.github.com/repos/hiddify/$1/releases" | jq -r 'map(select(.prerelease == true or .draft == true)) | sort_by(.created_at) | last | .tag_name') + VERSION=$(curl $GH_META_CURL_OPTS -sL "https://api.github.com/repos/hiddify/$1/releases" | jq -r 'map(select(.prerelease == true or .draft == true)) | sort_by(.created_at) | last | .tag_name') VERSION=${VERSION/#v/} echo $VERSION } function get_release_version() { - VERSION=$(curl -sL "https://api.github.com/repos/hiddify/$1/releases" | jq -r 'map(select(.prerelease == false)) | sort_by(.created_at) | last | .tag_name') + VERSION=$(curl $GH_META_CURL_OPTS -sL "https://api.github.com/repos/hiddify/$1/releases" | jq -r 'map(select(.prerelease == false)) | sort_by(.created_at) | last | .tag_name') if [ -z $VERSION ]; then # COMMIT_URL=https://api.github.com/repos/hiddify/$1/releases/latest # VERSION=$(curl -s --connect-timeout 1 $COMMIT_URL | jq -r .tag_name) - location=$(curl -sI "https://github.com/hiddify/$1/releases/latest" | grep -i location | awk -F' ' '{print $2}' | tr -d '\r') + location=$(curl $GH_META_CURL_OPTS -sI "https://github.com/hiddify/$1/releases/latest" | grep -i location | awk -F' ' '{print $2}' | tr -d '\r') if [[ $location == *"latest"* ]]; then - location=$(curl -sI "$location" | grep -i location | awk -F' ' '{print $2}' | tr -d '\r') + location=$(curl $GH_META_CURL_OPTS -sI "$location" | grep -i location | awk -F' ' '{print $2}' | tr -d '\r') fi VERSION=$(echo $location | rev | awk -F/ '{print $1}' | rev) @@ -682,4 +684,45 @@ set_files_in_folder_readable_to_hiddify_common_group() { chown :hiddify-common "$parent" # Change ownership to the group parent=$(dirname "$parent") # Move to the next parent directory done -} \ No newline at end of file +} + +# NOTE: download.sh and docker-installer.sh keep minimal inline copies of this +# logic because they run before utils.sh is on disk. +function download_with_fallback() { + local file_name=$1 + local url=$2 + local max_retries=3 + local retry_delay=3 + + local mirrors=( + "" + "https://ghproxy.net/" + "https://gh-proxy.com/" + ) + + # Download to a temp file next to the target and replace it only on success, + # so a failed download never destroys an existing file. + local tmp_file + tmp_file=$(mktemp "${file_name}.XXXXXX") || return 1 + + for mirror in "${mirrors[@]}"; do + local try_url="${mirror}${url}" + for attempt in $(seq 1 $max_retries); do + echo "Downloading $file_name (mirror='${mirror:-direct}', attempt=$attempt)..." + if curl -fL --connect-timeout 15 --retry 2 -o "$tmp_file" "$try_url"; then + if [[ -s "$tmp_file" ]]; then + mv -f "$tmp_file" "$file_name" + echo "Downloaded $file_name successfully." + return 0 + fi + echo "Downloaded file is empty, retrying..." + fi + rm -f "$tmp_file" + sleep $retry_delay + done + done + + rm -f "$tmp_file" + echo "ERROR: Failed to download $file_name from all sources (tried direct + ${#mirrors[@]} mirrors)." + return 1 +} diff --git a/hiddify-panel/install.sh b/hiddify-panel/install.sh index 11854ec10..09b2c1a1a 100755 --- a/hiddify-panel/install.sh +++ b/hiddify-panel/install.sh @@ -41,10 +41,10 @@ service cron reload >/dev/null 2>&1 ##### download videos if [[ ! -e "GeoLite2-ASN.mmdb" || $(find "GeoLite2-ASN.mmdb" -mtime +1) ]]; then - curl --connect-timeout 10 -sL -o GeoLite2-ASN.mmdb1 https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-ASN.mmdb && mv GeoLite2-ASN.mmdb1 GeoLite2-ASN.mmdb + download_with_fallback GeoLite2-ASN.mmdb https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-ASN.mmdb fi if [[ ! -e "GeoLite2-Country.mmdb" || $(find "GeoLite2-Country.mmdb" -mtime +1) ]]; then - curl --connect-timeout 10 -sL -o GeoLite2-Country.mmdb1 https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb && mv GeoLite2-Country.mmdb1 GeoLite2-Country.mmdb + download_with_fallback GeoLite2-Country.mmdb https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb fi # bash download_yt.sh &