diff --git a/hetzner/lib/common.sh b/hetzner/lib/common.sh index 57e93bce..55f27d3e 100755 --- a/hetzner/lib/common.sh +++ b/hetzner/lib/common.sh @@ -2,242 +2,24 @@ # Common bash functions for Hetzner Cloud spawn scripts # ============================================================ -# Provider-agnostic functions (shared with sprite/lib/common.sh) +# Provider-agnostic functions # ============================================================ -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -# Print colored message (to stderr so they don't pollute command substitution output) -log_info() { - echo -e "${GREEN}$1${NC}" >&2 +# Source shared provider-agnostic functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../../shared/common.sh" || { + echo "ERROR: Failed to load shared/common.sh" >&2 + exit 1 } -log_warn() { - echo -e "${YELLOW}$1${NC}" >&2 -} - -log_error() { - echo -e "${RED}$1${NC}" >&2 -} - -# Safe read function that works in both interactive and non-interactive modes -safe_read() { - local prompt="$1" - local result="" - - if [[ -t 0 ]]; then - # stdin is a terminal - read directly - read -p "$prompt" result - elif echo -n "" > /dev/tty 2>/dev/null; then - # /dev/tty is functional - use it - read -p "$prompt" result < /dev/tty - else - # No interactive input available - log_error "Cannot read input: no TTY available" - return 1 - fi - - echo "$result" -} - -# Listen on a port with netcat (handles busybox/Termux nc requiring -p flag) -nc_listen() { - local port=$1 - shift - # Detect if nc requires -p flag (busybox nc on Termux) - if nc --help 2>&1 | grep -q "BusyBox\|busybox" || nc --help 2>&1 | grep -q "\-p "; then - nc -l -p "$port" "$@" - else - nc -l "$port" "$@" - fi -} - -# Open browser to URL (supports macOS, Linux, Termux) -open_browser() { - local url=$1 - if command -v termux-open-url &> /dev/null; then - termux-open-url "$url" /dev/null; then - open "$url" /dev/null; then - xdg-open "$url" /dev/null; then - log_warn "netcat (nc) not found - OAuth server unavailable" - return 1 - fi - - local callback_url="http://localhost:${callback_port}/callback" - local auth_url="https://openrouter.ai/auth?callback_url=${callback_url}" - - # Create a temporary directory for the OAuth flow - local oauth_dir=$(mktemp -d) - local code_file="$oauth_dir/code" - - log_warn "Starting local OAuth server on port ${callback_port}..." - - # Use a simpler nc approach - pipe response while capturing request - ( - local success_response='HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n

Authentication Successful!

Redirecting back to terminal...

This tab will close automatically

' - - while true; do - # Listen and capture just the first line of the request, then respond - local response_file=$(mktemp) - echo -e "$success_response" > "$response_file" - - local request=$(nc_listen "$callback_port" < "$response_file" 2>/dev/null | head -1) - local nc_status=$? - rm -f "$response_file" - - # If nc failed, exit the loop - if [[ $nc_status -ne 0 ]]; then - break - fi - - if [[ "$request" == *"/callback?code="* ]]; then - local code=$(echo "$request" | sed -n 's/.*code=\([^ &]*\).*/\1/p') - echo "$code" > "$code_file" - break - fi - done - ) /dev/null; then - log_warn "Failed to start OAuth server (port may be in use)" - rm -rf "$oauth_dir" - return 1 - fi - - # Open browser - log_warn "Opening browser to authenticate with OpenRouter..." - open_browser "$auth_url" - - # Wait for the code file to be created (timeout after 2 minutes) - local timeout=120 - local elapsed=0 - while [[ ! -f "$code_file" ]] && [[ $elapsed -lt $timeout ]]; do - sleep 1 - ((elapsed++)) - done - - # Kill the background server process - kill $server_pid 2>/dev/null || true - wait $server_pid 2>/dev/null || true - - if [[ ! -f "$code_file" ]]; then - log_warn "OAuth timeout - no response received" - rm -rf "$oauth_dir" - return 1 - fi - - local oauth_code=$(cat "$code_file") - rm -rf "$oauth_dir" - - # Exchange the code for an API key - log_warn "Exchanging OAuth code for API key..." - local key_response=$(curl -s -X POST "https://openrouter.ai/api/v1/auth/keys" \ - -H "Content-Type: application/json" \ - -d "{\"code\": \"$oauth_code\"}") - - local api_key=$(echo "$key_response" | grep -o '"key":"[^"]*"' | sed 's/"key":"//;s/"$//') - - if [[ -z "$api_key" ]]; then - log_error "Failed to exchange OAuth code: ${key_response}" - return 1 - fi - - log_info "Successfully obtained OpenRouter API key via OAuth!" - echo "$api_key" -} - -# Main function: Try OAuth, fallback to manual entry -get_openrouter_api_key_oauth() { - local callback_port=${1:-5180} - - # Try OAuth flow first - local api_key=$(try_oauth_flow "$callback_port") - - if [[ -n "$api_key" ]]; then - echo "$api_key" - return 0 - fi - - # OAuth failed, offer manual entry - echo "" - log_warn "OAuth authentication failed or unavailable" - log_warn "You can enter your API key manually instead" - echo "" - local manual_choice=$(safe_read "Would you like to enter your API key manually? (Y/n): ") || { - log_error "Cannot prompt for manual entry in non-interactive mode" - log_warn "Set OPENROUTER_API_KEY environment variable for non-interactive usage" - return 1 - } - - if [[ ! "$manual_choice" =~ ^[Nn]$ ]]; then - api_key=$(get_openrouter_api_key_manual) - echo "$api_key" - return 0 - else - log_error "Authentication cancelled by user" - return 1 - fi -} +# Note: Provider-agnostic functions (logging, OAuth, browser, nc_listen) are now in shared/common.sh # ============================================================ # Hetzner Cloud specific functions # ============================================================ -HETZNER_API_BASE="https://api.hetzner.cloud/v1" -SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i $HOME/.ssh/id_ed25519" +readonly HETZNER_API_BASE="https://api.hetzner.cloud/v1" +readonly SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i $HOME/.ssh/id_ed25519" # Centralized curl wrapper for Hetzner API hetzner_api() { @@ -454,7 +236,7 @@ verify_server_connectivity() { local attempt=1 log_warn "Waiting for SSH connectivity to $ip..." - while [[ $attempt -le $max_attempts ]]; do + while [[ "$attempt" -le "$max_attempts" ]]; do if ssh $SSH_OPTS -o ConnectTimeout=5 "root@$ip" "echo ok" >/dev/null 2>&1; then log_info "SSH connection established" return 0 @@ -475,7 +257,7 @@ wait_for_cloud_init() { local attempt=1 log_warn "Waiting for cloud-init to complete..." - while [[ $attempt -le $max_attempts ]]; do + while [[ "$attempt" -le "$max_attempts" ]]; do if ssh $SSH_OPTS "root@$ip" "test -f /root/.cloud-init-complete" >/dev/null 2>&1; then log_info "Cloud-init completed" return 0