From 389d1f05a59dc72fe6a9699b4c9bfcfe89561b95 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 16 Mar 2026 11:58:53 -0400 Subject: [PATCH] fix(install): add wget fallback, timeouts on all operations, reorder strategies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Debian Bookworm SSL/TLS stack (both OpenSSL for curl and GnuTLS for git) frequently fails or hangs against GitHub. This overhaul: - Adds wget as fallback downloader (different TLS codepath) - Wraps all git/network ops with `timeout` to prevent hangs - Reorders strategies: binary → tarball → shallow clone → pipx → pip (tarball now before git-based methods since it needs no git protocol) - Each download retries 3x with both curl and wget before giving up - Installs wget as a system dep on apt-based distros - Final error message gives manual download instructions Co-Authored-By: Claude Opus 4.6 --- install.sh | 276 +++++++++++++++++++++++++++++------------------------ 1 file changed, 149 insertions(+), 127 deletions(-) diff --git a/install.sh b/install.sh index 74dc949..e6f966d 100755 --- a/install.sh +++ b/install.sh @@ -9,12 +9,10 @@ BRANCH="main" # Kill git operations that stall below 1 KB/s for 30 seconds export GIT_HTTP_LOW_SPEED_LIMIT=1000 export GIT_HTTP_LOW_SPEED_TIME=30 -# Force TLS 1.2 to avoid GnuTLS negotiation bugs on older distros -export GIT_SSL_VERSION=tlsv1.2 -# Timeout for curl operations (seconds) -CURL_TIMEOUT=120 -CURL_RETRY=3 -CURL_RETRY_DELAY=5 +# Disable partial clones — they are fragile on flaky connections +export GIT_CLONE_PROTECTION_ACTIVE=false +# Hard timeout (seconds) for any single network operation +NET_TIMEOUT=90 command_exists() { command -v "$1" >/dev/null 2>&1 @@ -53,51 +51,77 @@ die() { echo -e "\e[31m$*\e[0m" >&2; exit 1; } install_pkg() { case "$PM" in - apt) - $SUDO apt-get install -y "$@" - ;; - dnf) - $SUDO dnf install -y "$@" - ;; - yum) - $SUDO yum install -y "$@" - ;; - pacman) - $SUDO pacman -S --noconfirm "$@" - ;; - brew) - brew install "$@" - ;; - apk) - $SUDO apk add --no-cache "$@" - ;; + apt) $SUDO apt-get install -y "$@" ;; + dnf) $SUDO dnf install -y "$@" ;; + yum) $SUDO yum install -y "$@" ;; + pacman) $SUDO pacman -S --noconfirm "$@" ;; + brew) brew install "$@" ;; + apk) $SUDO apk add --no-cache "$@" ;; esac } -# Resilient curl wrapper with retries and timeout -safe_curl() { - curl --max-time "$CURL_TIMEOUT" \ - --retry "$CURL_RETRY" \ - --retry-delay "$CURL_RETRY_DELAY" \ - --retry-connrefused \ - --tlsv1.2 \ - "$@" +# --- Resilient download: tries curl then wget, with retries and timeout --- +download() { + local url="$1" dest="$2" + # Try curl first + if command_exists curl; then + local attempt + for attempt in 1 2 3; do + if timeout "$NET_TIMEOUT" curl -fsSL \ + --retry 2 --retry-delay 3 --retry-connrefused \ + --max-time "$NET_TIMEOUT" \ + "$url" -o "$dest" 2>/dev/null; then + return 0 + fi + warn "curl attempt $attempt failed for $(basename "$url")" + sleep 2 + done + fi + # Try wget as fallback (different TLS stack — often works when curl doesn't) + if command_exists wget; then + local attempt + for attempt in 1 2 3; do + if timeout "$NET_TIMEOUT" wget -q --timeout=60 --tries=1 \ + "$url" -O "$dest" 2>/dev/null; then + return 0 + fi + warn "wget attempt $attempt failed for $(basename "$url")" + sleep 2 + done + fi + return 1 } -# Ensure core dependencies are present (always run to ensure they are up to date) +# --- Resilient HTTP status check (for release tag redirect) --- +get_latest_tag() { + local tag="" + if command_exists curl; then + tag=$(timeout 30 curl -fsSLI -o /dev/null -w '%{url_effective}' \ + "${REPO_URL}/releases/latest" 2>/dev/null | grep -oE '[^/]+$') || true + fi + if [ -z "$tag" ] && command_exists wget; then + tag=$(timeout 30 wget --max-redirect=5 -q -S --spider \ + "${REPO_URL}/releases/latest" 2>&1 | grep -i 'Location:' | tail -1 | grep -oE '[^/]+$') || true + fi + echo "$tag" +} + +# Ensure core dependencies are present msg "Checking and updating system dependencies..." if [ "$PM" = "apt" ]; then $SUDO apt-get update - SYSTEM_PKGS=(tor curl git python3-pip python3-venv) + SYSTEM_PKGS=(tor curl wget git python3-pip python3-venv) [ -n "$(command -v pipx)" ] || SYSTEM_PKGS+=(pipx) install_pkg "${SYSTEM_PKGS[@]}" elif [ "$PM" = "brew" ]; then - brew install tor onionshare curl git + brew install tor onionshare curl wget git else install_pkg tor curl git + # Best-effort install wget if not present + install_pkg wget 2>/dev/null || true fi -# Onionshare-cli - ensure it's installed and working (always attempt update if using pipx) +# Onionshare-cli msg "Ensuring onionshare-cli is installed and up-to-date..." if command_exists pipx && (pipx list | grep -q onionshare || ! command_exists onionshare-cli); then msg "Installing/Updating onionshare-cli via pipx..." @@ -121,8 +145,7 @@ else msg "onionshare-cli found (system package), skipping pipx install. Run 'pipx install --force ...' if you want the latest git version." fi -# Clean up stale user-local packages from prior pip --user installs of voxvera. -# These shadow system packages (e.g. Werkzeug) and break onionshare-cli. +# Clean up stale user-local packages msg "Cleaning up stale user-local packages that may conflict with system onionshare-cli..." for pydir in "$HOME"/.local/lib/python3.*/site-packages; do [ -d "$pydir" ] || continue @@ -160,58 +183,30 @@ install_from_local() { return 1 } -# --- Helper: download and install from GitHub tarball (no git needed) --- -install_from_tarball() { - msg "Downloading VoxVera source tarball..." - local tmp_tar tmp_dir - tmp_tar=$(mktemp /tmp/voxvera-XXXXXX.tar.gz) - tmp_dir=$(mktemp -d /tmp/voxvera-src-XXXXXX) - - if safe_curl -fsSL "${REPO_URL}/archive/refs/heads/${BRANCH}.tar.gz" -o "$tmp_tar"; then - tar -xzf "$tmp_tar" -C "$tmp_dir" --strip-components=1 - if install_from_local "$tmp_dir"; then - rm -rf "$tmp_tar" "$tmp_dir" - return 0 - fi - else - warn "Tarball download failed." +# --- Helper: remove stale standalone binary so it doesn't shadow pipx/pip entrypoints --- +cleanup_stale_binary() { + local bin="$HOME/.local/bin/voxvera" + [ -f "$bin" ] || return 0 + if file "$bin" 2>/dev/null | grep -qiE 'ELF|executable|Mach-O'; then + warn "Removing stale standalone binary at $bin (would shadow pipx/pip entrypoint)" + rm -f "$bin" fi - rm -rf "$tmp_tar" "$tmp_dir" - return 1 -} - -# --- Helper: shallow clone and install --- -install_from_shallow_clone() { - msg "Attempting shallow git clone (--depth 1)..." - local tmp_dir - tmp_dir=$(mktemp -d /tmp/voxvera-clone-XXXXXX) - - if git clone --depth 1 --single-branch --branch "$BRANCH" "${REPO_URL}.git" "$tmp_dir"; then - if install_from_local "$tmp_dir"; then - rm -rf "$tmp_dir" - return 0 - fi - else - warn "Shallow clone failed." - fi - rm -rf "$tmp_dir" - return 1 } # ============================================================ -# Strategy 1: Pre-built binary (fastest, no compilation needed) +# Strategy 1: Pre-built binary (fastest) # ============================================================ install_binary() { - command_exists curl || return 1 - msg "Attempting to download latest VoxVera binary..." + msg "Strategy 1/5: Attempting to download pre-built binary..." local latest - latest=$(safe_curl -fsSLI -o /dev/null -w '%{url_effective}' "${REPO_URL}/releases/latest" | grep -oE '[^/]+$') || return 1 + latest=$(get_latest_tag) + [ -n "$latest" ] || { warn "Could not determine latest release tag."; return 1; } local install_dir="$HOME/.local/bin" mkdir -p "$install_dir" local dest="$install_dir/voxvera" - local os_name arch binary url + local os_name arch binary os_name="$(uname -s | tr '[:upper:]' '[:lower:]')" arch=$(uname -m) @@ -221,50 +216,89 @@ install_binary() { *) binary="voxvera-linux" ;; esac - url="${REPO_URL}/releases/download/${latest}/${binary}-${arch}" - msg "Downloading from: $url" + local url="${REPO_URL}/releases/download/${latest}/${binary}-${arch}" + msg "Downloading: $url" - set +e - local status - status=$(safe_curl -w "%{http_code}" -fsSL "$url" -o "$dest") - local curl_exit=$? - set -e - - if [ $curl_exit -eq 0 ] && [ "$status" = "200" ]; then + if download "$url" "$dest"; then chmod +x "$dest" - msg "VoxVera binary (${arch}) installed to $dest" - [[ ":$PATH:" != *":$HOME/.local/bin:"* ]] && warn "Add \$HOME/.local/bin to your PATH." - return 0 + # Verify it's actually an executable, not an HTML error page + if file "$dest" | grep -qiE 'ELF|executable|Mach-O'; then + msg "VoxVera binary (${arch}) installed to $dest" + [[ ":$PATH:" != *":$HOME/.local/bin:"* ]] && warn "Add \$HOME/.local/bin to your PATH." + return 0 + fi + warn "Downloaded file is not a valid binary." fi - warn "Binary download failed (status: ${status:-?}, exit: $curl_exit). Trying next method..." [ -f "$dest" ] && rm -f "$dest" + warn "Binary download failed. Trying next method..." return 1 } -# --- Helper: remove stale standalone binary so it doesn't shadow pipx/pip entrypoints --- -cleanup_stale_binary() { - local bin="$HOME/.local/bin/voxvera" - [ -f "$bin" ] || return 0 - # If the file is a real binary (not a pipx/pip script), remove it - if file "$bin" 2>/dev/null | grep -qiE 'ELF|executable|Mach-O'; then - warn "Removing stale standalone binary at $bin (would shadow pipx/pip entrypoint)" - rm -f "$bin" +# ============================================================ +# Strategy 2: GitHub tarball — no git needed, most network-resilient +# ============================================================ +install_tarball() { + msg "Strategy 2/5: Downloading source tarball (no git required)..." + local tmp_tar tmp_dir + tmp_tar=$(mktemp /tmp/voxvera-XXXXXX.tar.gz) + tmp_dir=$(mktemp -d /tmp/voxvera-src-XXXXXX) + + if download "${REPO_URL}/archive/refs/heads/${BRANCH}.tar.gz" "$tmp_tar"; then + if tar -xzf "$tmp_tar" -C "$tmp_dir" --strip-components=1 2>/dev/null; then + if install_from_local "$tmp_dir"; then + cleanup_stale_binary + rm -rf "$tmp_tar" "$tmp_dir" + return 0 + fi + else + warn "Tarball extraction failed." + fi + else + warn "Tarball download failed." fi + + rm -rf "$tmp_tar" "$tmp_dir" + warn "Tarball install failed. Trying next method..." + return 1 } # ============================================================ -# Strategy 2: pipx from git (isolated venv) +# Strategy 3: Shallow git clone + local pip install +# ============================================================ +install_shallow() { + msg "Strategy 3/5: Attempting shallow git clone (--depth 1)..." + local tmp_dir + tmp_dir=$(mktemp -d /tmp/voxvera-clone-XXXXXX) + + if timeout "$NET_TIMEOUT" git clone --depth 1 --single-branch --branch "$BRANCH" \ + "${REPO_URL}.git" "$tmp_dir" 2>&1; then + if install_from_local "$tmp_dir"; then + cleanup_stale_binary + rm -rf "$tmp_dir" + return 0 + fi + else + warn "Shallow clone failed or timed out after ${NET_TIMEOUT}s." + fi + + rm -rf "$tmp_dir" + warn "Shallow clone install failed. Trying next method..." + return 1 +} + +# ============================================================ +# Strategy 4: pipx from git (isolated venv) # ============================================================ install_pipx() { command_exists pipx || return 1 - msg "Attempting pipx installation..." - if pipx install --force "voxvera@git+${REPO_URL}.git@${BRANCH}"; then + msg "Strategy 4/5: Attempting pipx installation..." + if timeout 180 pipx install --force "voxvera@git+${REPO_URL}.git@${BRANCH}"; then pipx inject voxvera setuptools 2>/dev/null || true pipx ensurepath --force cleanup_stale_binary msg "\nVoxVera installed/updated successfully via pipx." - msg "IMPORTANT: Please restart your terminal or run 'source ~/.bashrc' (or your shell config) to use 'voxvera'." + msg "IMPORTANT: Please restart your terminal or run 'source ~/.bashrc' to use 'voxvera'." return 0 fi warn "pipx install failed. Trying next method..." @@ -272,47 +306,35 @@ install_pipx() { } # ============================================================ -# Strategy 3: pip from git +# Strategy 5: pip from git (last resort) # ============================================================ install_pip_git() { - msg "Attempting pip install from git..." + msg "Strategy 5/5: Attempting pip install from git..." local flags flags=$(pip_flags) - if pip install $flags "voxvera@git+${REPO_URL}.git@${BRANCH}"; then + if timeout 180 pip install $flags "voxvera@git+${REPO_URL}.git@${BRANCH}"; then cleanup_stale_binary msg "VoxVera installed via pip." return 0 fi - warn "pip git install failed. Trying next method..." - return 1 -} - -# ============================================================ -# Strategy 4: GitHub tarball (no git clone needed — most resilient) -# ============================================================ -install_tarball() { - if install_from_tarball; then cleanup_stale_binary; return 0; fi - warn "Tarball install failed. Trying next method..." - return 1 -} - -# ============================================================ -# Strategy 5: Shallow clone + local pip install -# ============================================================ -install_shallow() { - if install_from_shallow_clone; then cleanup_stale_binary; return 0; fi - warn "Shallow clone install failed." + warn "pip git install failed." return 1 } # ============================================================ # Run strategies in order — first success wins # ============================================================ -for strategy in install_binary install_pipx install_pip_git install_tarball install_shallow; do +# Order rationale: +# 1. Binary — fastest, single download +# 2. Tarball — no git needed, single download, most resilient on flaky TLS +# 3. Shallow clone — small git transfer +# 4. pipx — git clone internally, but isolated venv +# 5. pip git — git clone internally, last resort +for strategy in install_binary install_tarball install_shallow install_pipx install_pip_git; do if $strategy; then msg "\nVoxVera installed/updated successfully. Run 'voxvera check' to verify." exit 0 fi done -die "All installation methods failed. Check your network connection and try again, or clone manually:\n git clone --depth 1 ${REPO_URL}.git /tmp/voxvera && pip install --user /tmp/voxvera" +die "All installation methods failed. Your network may be blocking or corrupting HTTPS traffic to GitHub.\nTry downloading manually from a working machine:\n ${REPO_URL}/archive/refs/heads/${BRANCH}.tar.gz\nThen copy to this machine and run:\n tar xzf VoxVera-main.tar.gz && pip install --user ./VoxVera-main"