fix(install): add wget fallback, timeouts on all operations, reorder strategies

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 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.6 committed 2026-03-16 11:58:53 -04:00
1 parent ba3c63749d
commit 389d1f05a5
1 file changed
+149 -127
+149 -127
View File
@@ -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"