Package management and system utilities for Proxmox VE containers
The tools.func library provides unified helper functions for robust package installation and repository management across Debian/Ubuntu OS upgrades. It includes automatic retry logic, keyring cleanup, legacy installation cleanup, and OS-upgrade-safe repository preparation.
install_packages_with_retry() { local packages=("$@") local max_retries=3 local retry=0 # Pre-check: ensure dpkg is not in a broken state if dpkg --audit 2>&1 | grep -q .; then $STD dpkg --configure -a 2>/dev/null || true fi while [[ $retry -le $max_retries ]]; do if DEBIAN_FRONTEND=noninteractive $STD apt install -y \ -o Dpkg::Options::="--force-confdef" \ -o Dpkg::Options::="--force-confold" \ "${packages[@]}" 2>/dev/null; then return 0 fi retry=$((retry + 1)) if [[ $retry -le $max_retries ]]; then msg_warn "Package installation failed, retrying ($retry/$max_retries)..." # Progressive recovery steps case $retry in 1) # First retry: fix dpkg and update $STD dpkg --configure -a 2>/dev/null || true $STD apt update 2>/dev/null || true ;; 2) # Second retry: fix broken dependencies $STD apt --fix-broken install -y 2>/dev/null || true $STD apt update 2>/dev/null || true ;; 3) # Third retry: install packages one by one local failed=() for pkg in "${packages[@]}"; do if ! $STD apt install -y "$pkg" 2>/dev/null; then if ! $STD apt install -y --fix-missing "$pkg" 2>/dev/null; then failed+=("$pkg") fi fi done # Partial success handling if [[ ${#failed[@]} -lt ${#packages[@]} ]]; then if [[ ${#failed[@]} -gt 0 ]]; then msg_warn "Partially installed. Failed packages: ${failed[*]}" fi return 0 fi ;; esac sleep $((retry * 2)) fi done msg_error "Failed to install packages after $((max_retries + 1)) attempts: ${packages[*]}" return 1}
CURL_TIMEOUT: Max time per attempt in seconds (default: 60)
CURL_CONNECT_TO: Connection timeout in seconds (default: 10)
tools.func:68-123
curl_with_retry() { local url="$1" local output="${2:--}" local extra_opts="${3:-}" local retries="${CURL_RETRIES:-3}" local timeout="${CURL_TIMEOUT:-60}" local connect_timeout="${CURL_CONNECT_TO:-10}" local attempt=1 local success=false local backoff=1 # DNS pre-check - fail fast if host is unresolvable local host host=$(echo "$url" | sed -E 's|^https?://([^/:]+).*|\1|') if ! getent hosts "$host" &>/dev/null; then debug_log "DNS resolution failed for $host" return 1 fi while [[ $attempt -le $retries ]]; do debug_log "curl attempt $attempt/$retries: $url" local curl_cmd="curl -fsSL --connect-timeout $connect_timeout --max-time $timeout" [[ -n "$extra_opts" ]] && curl_cmd="$curl_cmd $extra_opts" if [[ "$output" == "-" ]]; then if $curl_cmd "$url"; then success=true break fi else if $curl_cmd -o "$output" "$url"; then success=true break fi fi debug_log "curl attempt $attempt failed, waiting ${backoff}s before retry..." sleep "$backoff" # Exponential backoff: 1, 2, 4, 8... capped at 30s backoff=$((backoff * 2)) ((backoff > 30)) && backoff=30 ((attempt++)) done if [[ "$success" == "true" ]]; then debug_log "curl successful: $url" return 0 else debug_log "curl FAILED after $retries attempts: $url" return 1 fi}
prepare_repository_setup() { local repo_names=("$@") # Clean up all old repository files for repo in "${repo_names[@]}"; do cleanup_old_repo_files "$repo" done # Clean up all keyrings cleanup_tool_keyrings "${repo_names[@]}" # Ensure APT is in working state ensure_apt_working || return 1 return 0}
cleanup_tool_keyrings() { local tool_patterns=("$@") for pattern in "${tool_patterns[@]}"; do rm -f /usr/share/keyrings/${pattern}*.gpg \ /etc/apt/keyrings/${pattern}*.gpg \ /etc/apt/trusted.gpg.d/${pattern}*.gpg 2>/dev/null || true done}
is_tool_installed() { local tool_name="$1" local required_version="${2:-}" local installed_version="" case "$tool_name" in mariadb) if command -v mariadb >/dev/null 2>&1; then installed_version=$(mariadb --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) fi ;; mysql) if command -v mysql >/dev/null 2>&1; then installed_version=$(mysql --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) fi ;; mongodb | mongod) if command -v mongod >/dev/null 2>&1; then installed_version=$(mongod --version 2>/dev/null | awk '/db version/{print $3}' | cut -d. -f1,2) fi ;; node | nodejs) if command -v node >/dev/null 2>&1; then installed_version=$(node -v 2>/dev/null | grep -oP '^v\K[0-9]+') fi ;; php) if command -v php >/dev/null 2>&1; then installed_version=$(php -v 2>/dev/null | awk '/^PHP/{print $2}' | cut -d. -f1,2) fi ;; postgresql) if command -v psql >/dev/null 2>&1; then installed_version=$(psql --version 2>/dev/null | awk '{print $3}' | cut -d. -f1) fi ;; esac if [[ -z "$installed_version" ]]; then return 1 # Not installed fi if [[ -n "$required_version" && "$installed_version" != "$required_version" ]]; then echo "$installed_version" return 1 # Version mismatch fi echo "$installed_version" return 0 # Installed and version matches}
verify_tool_version() { local tool_name="$1" local expected_version="$2" local installed_version="$3" # Extract major version for comparison local expected_major="${expected_version%%.*}" local installed_major="${installed_version%%.*}" if [[ "$installed_major" != "$expected_major" ]]; then msg_warn "$tool_name version mismatch: expected $expected_version, got $installed_version" return 1 fi return 0}
GITHUB_TOKEN: GitHub personal access token (optional, increases rate limit)
tools.func:1085-1154
github_api_call() { local url="$1" local output_file="${2:-/dev/stdout}" local max_retries=3 local retry_delay=2 local header_args=() [[ -n "${GITHUB_TOKEN:-}" ]] && header_args=(-H "Authorization: Bearer $GITHUB_TOKEN") for attempt in $(seq 1 $max_retries); do local http_code http_code=$(curl -sSL -w "%{http_code}" -o "$output_file" \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "${header_args[@]}" \ "$url" 2>/dev/null) || true case "$http_code" in 200) return 0 ;; 401) msg_error "GitHub API authentication failed (HTTP 401)." if [[ -n "${GITHUB_TOKEN:-}" ]]; then msg_error "Your GITHUB_TOKEN appears to be invalid or expired." else msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\"" fi return 1 ;; 403) # Rate limit - check if we can retry if [[ $attempt -lt $max_retries ]]; then msg_warn "GitHub API rate limit, waiting ${retry_delay}s... (attempt $attempt/$max_retries)" sleep "$retry_delay" retry_delay=$((retry_delay * 2)) continue fi msg_error "GitHub API rate limit exceeded (HTTP 403)." msg_error "To increase the limit, export a GitHub token before running the script:" msg_error " export GITHUB_TOKEN=\"ghp_your_token_here\"" return 1 ;; 404) msg_error "GitHub repository or release not found (HTTP 404): $url" return 1 ;; esac done msg_error "GitHub API call failed after ${max_retries} attempts: ${url}" return 1}