diff --git a/contabo/lib/common.sh b/contabo/lib/common.sh index 0f2fcc7ad..7072c6631 100644 --- a/contabo/lib/common.sh +++ b/contabo/lib/common.sh @@ -35,12 +35,22 @@ get_contabo_access_token() { -d "grant_type=password" \ "${CONTABO_AUTH_URL}" 2>&1) || { log_error "Failed to obtain Contabo OAuth token" - log_error "Response: $response" + log_error "" + log_error "How to fix:" + log_error " 1. Verify your credentials at: https://my.contabo.com/api/details" + log_error " 2. Check that CONTABO_CLIENT_ID, CONTABO_CLIENT_SECRET, CONTABO_API_USER," + log_error " and CONTABO_API_PASSWORD are all set correctly" + log_error " 3. The API password is separate from your Contabo login password" return 1 } if echo "$response" | grep -q '"error"'; then - log_error "OAuth authentication failed: $response" + log_error "Contabo OAuth authentication failed" + log_error "" + log_error "How to fix:" + log_error " 1. Verify credentials at: https://my.contabo.com/api/details" + log_error " 2. Ensure your API user has not been deactivated" + log_error " 3. Re-run to enter new credentials" return 1 fi @@ -48,7 +58,9 @@ get_contabo_access_token() { token=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('access_token',''))" 2>/dev/null) if [[ -z "$token" ]]; then - log_error "Failed to extract access token from response" + log_error "Failed to extract access token from Contabo OAuth response" + log_error "The API returned an unexpected response format." + log_error "Try again, or check Contabo's API status page." return 1 fi diff --git a/exoscale/lib/common.sh b/exoscale/lib/common.sh index 73e68c25d..25e84b3a9 100644 --- a/exoscale/lib/common.sh +++ b/exoscale/lib/common.sh @@ -60,7 +60,9 @@ ensure_exo_cli() { local temp_dir temp_dir=$(mktemp -d) if ! curl -fsSL "$exo_url" -o "${temp_dir}/exo.tar.gz"; then - log_error "Failed to download exo CLI" + log_error "Failed to download exo CLI from: $exo_url" + log_error "Check your internet connection and try again." + log_error "You can also install manually: https://community.exoscale.com/documentation/tools/exoscale-command-line-interface/" rm -rf "$temp_dir" return 1 fi @@ -77,6 +79,7 @@ ensure_exo_cli() { test_exoscale_creds() { if ! exo config list &>/dev/null; then log_error "Exoscale credentials not configured or invalid" + log_error "Get your API credentials at: https://portal.exoscale.com/iam/api-keys" return 1 fi @@ -128,6 +131,11 @@ ensure_exoscale_creds() { return 0 else log_error "Failed to configure Exoscale credentials" + log_error "" + log_error "How to fix:" + log_error " 1. Verify your API Key and Secret at: https://portal.exoscale.com/iam/api-keys" + log_error " 2. Ensure the API key has the required permissions (compute, SSH keys)" + log_error " 3. Re-run the command to enter new credentials" return 1 fi } diff --git a/netcup/lib/common.sh b/netcup/lib/common.sh index fbe377cf3..5bfa63767 100644 --- a/netcup/lib/common.sh +++ b/netcup/lib/common.sh @@ -72,6 +72,7 @@ netcup_get_session() { -H "Content-Type: application/json" \ -d "$body" 2>&1) || { log_error "Failed to connect to Netcup API" + log_error "Check your internet connection and try again." return 1 } diff --git a/scaleway/lib/common.sh b/scaleway/lib/common.sh index 6282cce0c..3741ced4a 100644 --- a/scaleway/lib/common.sh +++ b/scaleway/lib/common.sh @@ -88,7 +88,11 @@ get_scaleway_project_id() { if [[ -z "$project_id" ]]; then log_error "Failed to get Scaleway project ID" - log_warn "Set SCW_DEFAULT_PROJECT_ID environment variable or check API permissions" + log_error "" + log_error "How to fix:" + log_error " 1. Set SCW_DEFAULT_PROJECT_ID in your environment" + log_error " 2. Find your Project ID at: https://console.scaleway.com/project/settings" + log_error " 3. Ensure your API key has permission to list projects" return 1 fi diff --git a/shared/common.sh b/shared/common.sh index 26cccfeba..69f38be97 100644 --- a/shared/common.sh +++ b/shared/common.sh @@ -122,24 +122,33 @@ ensure_jq() { if [[ "$OSTYPE" == "darwin"* ]]; then if command -v brew &>/dev/null; then - brew install jq || { log_error "Failed to install jq via Homebrew"; return 1; } + brew install jq || { log_error "Failed to install jq via Homebrew. Run 'brew install jq' manually."; return 1; } else - log_error "Install jq: brew install jq (or https://jqlang.github.io/jq/download/)" + log_error "jq is required but not installed" + log_error "Install it with: brew install jq" + log_error "If Homebrew is not available: https://jqlang.github.io/jq/download/" return 1 fi elif command -v apt-get &>/dev/null; then - sudo apt-get update -qq && sudo apt-get install -y jq || { log_error "Failed to install jq via apt"; return 1; } + sudo apt-get update -qq && sudo apt-get install -y jq || { log_error "Failed to install jq via apt. Run 'sudo apt-get install -y jq' manually."; return 1; } elif command -v dnf &>/dev/null; then - sudo dnf install -y jq || { log_error "Failed to install jq via dnf"; return 1; } + sudo dnf install -y jq || { log_error "Failed to install jq via dnf. Run 'sudo dnf install -y jq' manually."; return 1; } elif command -v apk &>/dev/null; then - sudo apk add jq || { log_error "Failed to install jq via apk"; return 1; } + sudo apk add jq || { log_error "Failed to install jq via apk. Run 'sudo apk add jq' manually."; return 1; } else - log_error "jq is required but not installed. Install from https://jqlang.github.io/jq/download/" + log_error "jq is required but not installed" + log_error "" + log_error "Install jq for your system:" + log_error " Ubuntu/Debian: sudo apt-get install -y jq" + log_error " Fedora/RHEL: sudo dnf install -y jq" + log_error " macOS: brew install jq" + log_error " Other: https://jqlang.github.io/jq/download/" return 1 fi if ! command -v jq &>/dev/null; then - log_error "jq not found in PATH after installation" + log_error "jq was installed but is not found in PATH" + log_error "Try opening a new terminal or run: hash -r" return 1 fi @@ -444,7 +453,12 @@ get_openrouter_api_key_manual() { while [[ -z "${api_key}" ]]; do attempts=$((attempts + 1)) if [[ ${attempts} -gt ${max_attempts} ]]; then - log_error "Too many failed attempts. Set OPENROUTER_API_KEY environment variable and try again." + log_error "Too many failed attempts." + log_error "" + log_error "How to fix:" + log_error " 1. Get your key from: https://openrouter.ai/settings/keys" + log_error " 2. Set it before running spawn: export OPENROUTER_API_KEY=sk-or-v1-..." + log_error " 3. Then re-run: spawn " return 1 fi api_key=$(safe_read "Enter your OpenRouter API key: ") || return 1 @@ -1033,9 +1047,21 @@ generate_ssh_key_if_missing() { if [[ -f "${key_path}" ]]; then return 0 fi - log_step "Generating SSH key..." - mkdir -p "$(dirname "${key_path}")" - ssh-keygen -t ed25519 -f "${key_path}" -N "" -q + log_step "Generating SSH key at ${key_path}..." + mkdir -p "$(dirname "${key_path}")" || { + log_error "Failed to create SSH key directory: $(dirname "${key_path}")" + log_error "Check that you have write permissions to this directory." + return 1 + } + ssh-keygen -t ed25519 -f "${key_path}" -N "" -q || { + log_error "Failed to generate SSH key at ${key_path}" + log_error "" + log_error "How to fix:" + log_error " 1. Check disk space: df -h $(dirname "${key_path}")" + log_error " 2. Check permissions: ls -la $(dirname "${key_path}")" + log_error " 3. Generate manually: ssh-keygen -t ed25519 -f ${key_path}" + return 1 + } log_info "SSH key generated at ${key_path}" } @@ -1043,7 +1069,21 @@ generate_ssh_key_if_missing() { # Usage: get_ssh_fingerprint PUB_KEY_PATH get_ssh_fingerprint() { local pub_path="${1}" - ssh-keygen -lf "${pub_path}" -E md5 2>/dev/null | awk '{print $2}' | sed 's/MD5://' + if [[ ! -f "${pub_path}" ]]; then + log_error "SSH public key not found: ${pub_path}" + log_error "Expected a public key file alongside your private key." + log_error "Regenerate with: ssh-keygen -t ed25519 -f ${pub_path%.pub}" + return 1 + fi + local fingerprint + fingerprint=$(ssh-keygen -lf "${pub_path}" -E md5 2>/dev/null | awk '{print $2}' | sed 's/MD5://') + if [[ -z "${fingerprint}" ]]; then + log_error "Failed to read SSH public key fingerprint from ${pub_path}" + log_error "The key file may be corrupted or in an unsupported format." + log_error "Regenerate with: ssh-keygen -t ed25519 -f ${pub_path%.pub}" + return 1 + fi + echo "${fingerprint}" } # JSON-escape a string (for embedding in JSON bodies) @@ -1255,7 +1295,13 @@ _report_api_failure() { local max_retries="${2}" log_error "${retry_reason} after ${max_retries} attempts" if [[ "${retry_reason}" == "Cloud API network error" ]]; then - log_warn "Check your internet connection and verify the provider's API is reachable." + log_warn "Could not reach the cloud provider's API." + log_warn "" + log_warn "How to fix:" + log_warn " 1. Check your internet connection: curl -s https://httpbin.org/ip" + log_warn " 2. Check DNS resolution: nslookup the provider's API hostname" + log_warn " 3. If behind a proxy or firewall, ensure HTTPS traffic is allowed" + log_warn " 4. Try again in a few moments (the API may be temporarily down)" else log_warn "This is usually caused by rate limiting or temporary provider issues." log_warn "Wait a minute and try again, or check the provider's status page." @@ -1468,8 +1514,15 @@ generic_ssh_wait() { attempt=$((attempt + 1)) done - log_error "${description} timed out after ${elapsed_time}s" - log_warn "The server at ${ip} may still be booting. You can try again or check its status in your cloud provider dashboard." + log_error "${description} timed out after ${elapsed_time}s (server: ${ip})" + log_warn "" + log_warn "The server may still be booting or the connection may be blocked." + log_warn "" + log_warn "How to fix:" + log_warn " 1. Re-run the command to try again (the server may need more time)" + log_warn " 2. Check your cloud provider dashboard to verify the server is running" + log_warn " 3. Test SSH manually: ssh ${username}@${ip}" + log_warn " 4. Check that port 22 is open in the server's firewall/security group" return 1 }