diff --git a/vps_management.sh b/vps_management.sh new file mode 100755 index 0000000..cf54afe --- /dev/null +++ b/vps_management.sh @@ -0,0 +1,532 @@ +#!/bin/bash + +################################################################################ +# Multi-Hosting Provider Management Script +# Supports: Hetzner, Vultr, DigitalOcean +################################################################################ + +export TERM=tmux-256color + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +API_KEYS_DIR="$HOME/.apikeys" +LOG_FILE="$HOME/.hosting_manager.log" + +check_dependencies() { + local missing_tools=() + for tool in curl jq bc; do + if ! command -v "$tool" &> /dev/null; then + missing_tools+=("$tool") + fi + done + + if [[ ${#missing_tools[@]} -gt 0 ]]; then + echo -e "${RED}Error: Missing required tools: ${missing_tools[*]}${NC}" + echo -e "${YELLOW}Install them with: sudo apt-get install ${missing_tools[*]}${NC}" + exit 1 + fi +} + +log_action() { + local level=$1 + local message=$2 + echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message" >> "$LOG_FILE" +} + +clear_screen() { + clear +} + +print_header() { + echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}" + echo -e "${CYAN}║${NC} Multi-Hosting Provider Management Tool ${CYAN}║${NC}" + echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}" +} + +pause_menu() { + echo -e "\n${YELLOW}Press Enter to continue...${NC}" + read -r +} + +# ============================================================================ +# VULTR FUNCTIONS +# ============================================================================ + +vultr_get_api_key() { + if [[ ! -f "$API_KEYS_DIR/vultr.txt" ]]; then + echo -e "${RED}Error: Vultr API key not found${NC}" + return 1 + fi + cat "$API_KEYS_DIR/vultr.txt" | tr -d '\n' | tr -d ' ' +} + +vultr_list_instances() { + local api_key=$(vultr_get_api_key) + [[ -z "$api_key" ]] && echo '{"instances":[]}' && return 1 + + local response=$(curl -s -w "\n%{http_code}" -X GET "https://api.vultr.com/v2/instances" \ + -H "Authorization: Bearer $api_key" -H "Content-Type: application/json") + + local http_code=$(echo "$response" | tail -n1) + local body=$(echo "$response" | sed '$d') + + if [[ "$http_code" != "200" ]]; then + echo -e "${RED}Vultr API Error (HTTP $http_code)${NC}" >&2 + log_action "ERROR" "Vultr API returned HTTP $http_code" + echo '{"instances":[]}' + return 1 + fi + echo "$body" +} + +vultr_display_instances() { + clear_screen + print_header + echo -e "\n${BLUE}Vultr Instances:${NC}\n" + + local instances=$(vultr_list_instances) + + if ! echo "$instances" | jq empty 2>/dev/null; then + echo -e "${RED}Failed to retrieve instances${NC}" + log_action "ERROR" "Invalid Vultr API response" + pause_menu + return + fi + + local instance_count=$(echo "$instances" | jq '.instances | length' 2>/dev/null || echo 0) + [[ $instance_count -eq 0 ]] && echo -e "${YELLOW}No instances found${NC}" && pause_menu && return + + echo "$instances" | jq -r '.instances[] | "\(if .label != null then .label else "N/A" end)\t|\t\(.id)\t|\t\(.region)\t|\t\(.status)"' | \ + column -t -s $'\t' -N "NAME,INSTANCE_ID,REGION,STATUS" + + log_action "INFO" "Displayed Vultr instances ($instance_count found)" + pause_menu +} + +vultr_instance_bandwidth() { + clear_screen + print_header + echo -e "\n${BLUE}Vultr Bandwidth Usage (Today):${NC}\n" + + local api_key=$(vultr_get_api_key) + [[ -z "$api_key" ]] && echo -e "${RED}API key not found${NC}" && pause_menu && return 1 + + local instances=$(vultr_list_instances) + if ! echo "$instances" | jq empty 2>/dev/null; then + echo -e "${RED}Failed to retrieve instances${NC}" + pause_menu + return + fi + + local instance_count=$(echo "$instances" | jq '.instances | length' 2>/dev/null || echo 0) + [[ $instance_count -eq 0 ]] && echo -e "${YELLOW}No instances found${NC}" && pause_menu && return + + local today=$(date '+%Y-%m-%d') + + echo "$instances" | jq -r '.instances[] | .id' | while read -r instance_id; do + local label=$(echo "$instances" | jq -r ".instances[] | select(.id == \"$instance_id\") | .label") + local bandwidth=$(curl -s -X GET "https://api.vultr.com/v2/instances/$instance_id/bandwidth" \ + -H "Authorization: Bearer $api_key" -H "Content-Type: application/json") + + local incoming=$(echo "$bandwidth" | jq -r ".bandwidth.\"$today\"[0]" 2>/dev/null || echo "0") + local outgoing=$(echo "$bandwidth" | jq -r ".bandwidth.\"$today\"[1]" 2>/dev/null || echo "0") + + local incoming_mb=$(echo "scale=2; $incoming / 1024 / 1024" | bc -l 2>/dev/null || echo "0") + local outgoing_mb=$(echo "scale=2; $outgoing / 1024 / 1024" | bc -l 2>/dev/null || echo "0") + + echo -e "${CYAN}$label:${NC}" + echo -e " Incoming: ${GREEN}${incoming_mb} MB${NC}" + echo -e " Outgoing: ${GREEN}${outgoing_mb} MB${NC}\n" + done + + log_action "INFO" "Viewed Vultr bandwidth usage" + pause_menu +} + +# ============================================================================ +# DIGITALOCEAN FUNCTIONS +# ============================================================================ + +digitalocean_get_api_key() { + if [[ ! -f "$API_KEYS_DIR/digitalocean.txt" ]]; then + echo -e "${RED}Error: DigitalOcean API key not found${NC}" + return 1 + fi + cat "$API_KEYS_DIR/digitalocean.txt" | tr -d '\n' | tr -d ' ' +} + +digitalocean_list_droplets() { + local api_key=$(digitalocean_get_api_key) + [[ -z "$api_key" ]] && echo '{"droplets":[]}' && return 1 + + local response=$(curl -s -w "\n%{http_code}" -X GET "https://api.digitalocean.com/v2/droplets" \ + -H "Authorization: Bearer $api_key" -H "Content-Type: application/json") + + local http_code=$(echo "$response" | tail -n1) + local body=$(echo "$response" | sed '$d') + + if [[ "$http_code" != "200" ]]; then + echo -e "${RED}DigitalOcean API Error (HTTP $http_code)${NC}" >&2 + log_action "ERROR" "DigitalOcean API returned HTTP $http_code" + echo '{"droplets":[]}' + return 1 + fi + echo "$body" +} + +digitalocean_display_droplets() { + clear_screen + print_header + echo -e "\n${BLUE}DigitalOcean Droplets:${NC}\n" + + local droplets=$(digitalocean_list_droplets) + + if ! echo "$droplets" | jq empty 2>/dev/null; then + echo -e "${RED}Failed to retrieve droplets${NC}" + log_action "ERROR" "Invalid DigitalOcean API response" + pause_menu + return + fi + + local droplet_count=$(echo "$droplets" | jq '.droplets | length' 2>/dev/null || echo 0) + [[ $droplet_count -eq 0 ]] && echo -e "${YELLOW}No droplets found${NC}" && pause_menu && return + + echo "$droplets" | jq -r '.droplets[] | "\(.name)\t|\t\(.id)\t|\t\(.region.slug)\t|\t\(.status)"' | \ + column -t -s $'\t' -N "NAME,DROPLET_ID,REGION,STATUS" + + log_action "INFO" "Displayed DigitalOcean droplets ($droplet_count found)" + pause_menu +} + +digitalocean_display_droplet_info() { + clear_screen + print_header + echo -e "\n${BLUE}DigitalOcean Droplet Detailed Info:${NC}\n" + + local droplets=$(digitalocean_list_droplets) + + if ! echo "$droplets" | jq empty 2>/dev/null; then + echo -e "${RED}Failed to retrieve droplets${NC}" + pause_menu + return + fi + + local droplet_count=$(echo "$droplets" | jq '.droplets | length' 2>/dev/null || echo 0) + [[ $droplet_count -eq 0 ]] && echo -e "${YELLOW}No droplets found${NC}" && pause_menu && return + + echo "$droplets" | jq -r '.droplets[] | "\(.name)\t|\t\(.memory)MB\t|\t\(.vcpus) vCPU\t|\t\(.disk)GB\t|\t\(.region.name)\t|\t\(.networks.v4[] | select(.type == \"public\") | .ip_address // \"N/A\")"' | \ + column -t -s $'\t' -N "NAME,MEMORY,VCPUS,DISK,REGION,PUBLIC_IP" + + log_action "INFO" "Viewed DigitalOcean droplet details" + pause_menu +} + +# ============================================================================ +# HETZNER FUNCTIONS +# ============================================================================ + +hetzner_find_api_keys() { + local keys=() + + if [[ ! -d "$API_KEYS_DIR" ]]; then + return 1 + fi + + while IFS= read -r -d '' file; do + keys+=("$file") + done < <(find "$API_KEYS_DIR" -maxdepth 1 -name "hetzner*.txt" -type f -print0 2>/dev/null) + + if [[ ${#keys[@]} -eq 0 ]]; then + return 1 + fi + + IFS=$'\n' sorted_keys=($(sort <<<"${keys[*]}")) + unset IFS + + printf '%s\n' "${sorted_keys[@]}" +} + +hetzner_select_project() { + local api_key="" + local selected_file="" + + local keys_array=() + while IFS= read -r key; do + keys_array+=("$key") + done < <(hetzner_find_api_keys) + + if [[ ${#keys_array[@]} -eq 0 ]]; then + echo -e "${RED}Error: No Hetzner API keys found in $API_KEYS_DIR${NC}" + log_action "ERROR" "No Hetzner API keys found" + return 1 + fi + + if [[ ${#keys_array[@]} -eq 1 ]]; then + selected_file="${keys_array[0]}" + else + clear_screen + print_header + echo -e "\n${BLUE}Select Hetzner Project:${NC}\n" + + for i in "${!keys_array[@]}"; do + local filename=$(basename "${keys_array[$i]}") + local project_name="${filename#hetzner-}" + project_name="${project_name%.txt}" + echo -e "$((i+1)). ${YELLOW}$project_name${NC}" + done + echo "" + + read -p "Choose a project (1-${#keys_array[@]}): " choice + + if ! [[ "$choice" =~ ^[0-9]+$ ]] || ((choice < 1 || choice > ${#keys_array[@]})); then + echo -e "${RED}Invalid selection${NC}" + log_action "ERROR" "Invalid Hetzner project selection" + return 1 + fi + + selected_file="${keys_array[$((choice-1))]}" + fi + + api_key=$(cat "$selected_file" | tr -d '\n' | tr -d ' ') + echo "$api_key" +} + +hetzner_list_servers() { + local api_key="$1" + + [[ -z "$api_key" ]] && return 1 + + local response + response=$(curl -s -w "\n%{http_code}" \ + -H "Authorization: Bearer $api_key" \ + -H "Content-Type: application/json" \ + https://api.hetzner.cloud/v1/servers) + + local http_code + http_code=$(echo "$response" | tail -n1) + + local body + body=$(echo "$response" | sed '$d') + + if [[ "$http_code" != "200" ]]; then + echo '{"servers":[]}' + log_action "ERROR" "Hetzner API HTTP $http_code" + return 1 + fi + + echo "$body" +} + + +hetzner_display_servers() { + clear_screen + print_header + echo -e "\n${BLUE}Hetzner Cloud Servers:${NC}\n" + + local api_key=$(hetzner_select_project) + [[ -z "$api_key" ]] && pause_menu && return + + local servers=$(hetzner_list_servers "$api_key") + if ! echo "$servers" | jq empty 2>/dev/null; then + echo -e "${RED}Failed to retrieve servers${NC}" + log_action "ERROR" "Invalid Hetzner API response" + pause_menu + return + fi + + local server_count=$(echo "$servers" | jq '.servers | length' 2>/dev/null || echo 0) + [[ $server_count -eq 0 ]] && echo -e "${YELLOW}No servers found${NC}" && pause_menu && return + + echo "$servers" | jq -r '.servers[] | "\(.name)\t|\t\(.id)\t|\t\(.datacenter.name)\t|\t\(.status)"' | \ + column -t -s $'\t' -N "NAME,SERVER_ID,DATACENTER,STATUS" + + log_action "INFO" "Displayed Hetzner servers ($server_count found)" + pause_menu +} + + +hetzner_display_server_info() { + clear_screen + print_header + echo -e "\n${BLUE}Hetzner Cloud Server Detailed Info:${NC}\n" + + local api_key=$(hetzner_select_project) + [[ -z "$api_key" ]] && pause_menu && return + + local servers=$(hetzner_list_servers "$api_key") + + if ! echo "$servers" | jq empty 2>/dev/null; then + echo -e "${RED}Failed to retrieve servers${NC}" + pause_menu + return + fi + + local server_count=$(echo "$servers" | jq '.servers | length' 2>/dev/null || echo 0) + [[ $server_count -eq 0 ]] && echo -e "${YELLOW}No servers found${NC}" && pause_menu && return + + echo "$servers" | jq -r '.servers[] | "\(.name)\t|\t\(.server_type.name)\t|\t\(.server_type.cores) cores\t|\t\(.server_type.memory)GB\t|\t\(.server_type.disk)GB\t|\t\(.datacenter.description)\t|\t\(.public_net.ipv4.ip // \"N/A\")"' | \ + column -t -s $'\t' -N "NAME,SERVER_TYPE,CORES,MEMORY,DISK,DATACENTER,PUBLIC_IP" + + log_action "INFO" "Viewed Hetzner server details" + pause_menu +} + +# ============================================================================ +# API KEY MANAGEMENT +# ============================================================================ + +setup_api_keys() { + clear_screen + print_header + echo -e "\n${BLUE}API Key Setup:${NC}\n" + + if [[ ! -d "$API_KEYS_DIR" ]]; then + mkdir -p "$API_KEYS_DIR" + chmod 700 "$API_KEYS_DIR" + echo -e "${GREEN}Created $API_KEYS_DIR${NC}" + fi + + echo -e "${YELLOW}1.${NC} Setup Vultr API Key" + echo -e "${YELLOW}2.${NC} Setup DigitalOcean API Key" + echo -e "${YELLOW}3.${NC} Setup Hetzner API Key (Single Project)" + echo -e "${YELLOW}4.${NC} Setup Hetzner API Key (Multiple Projects)" + echo -e "${YELLOW}5.${NC} Back to Main Menu" + echo "" + + read -p "Choose an option: " choice + + case $choice in + 1) + read -sp "Enter Vultr API Key: " vultr_key + echo "" + echo "$vultr_key" > "$API_KEYS_DIR/vultr.txt" + chmod 600 "$API_KEYS_DIR/vultr.txt" + echo -e "${GREEN}Vultr API key saved${NC}" + log_action "INFO" "Vultr API key configured" + ;; + 2) + read -sp "Enter DigitalOcean API Key: " do_key + echo "" + echo "$do_key" > "$API_KEYS_DIR/digitalocean.txt" + chmod 600 "$API_KEYS_DIR/digitalocean.txt" + echo -e "${GREEN}DigitalOcean API key saved${NC}" + log_action "INFO" "DigitalOcean API key configured" + ;; + 3) + read -sp "Enter Hetzner API Key: " hz_key + echo "" + echo "$hz_key" > "$API_KEYS_DIR/hetzner.txt" + chmod 600 "$API_KEYS_DIR/hetzner.txt" + echo -e "${GREEN}Hetzner API key saved${NC}" + log_action "INFO" "Hetzner API key configured" + ;; + 4) + read -p "Enter project name (e.g., production, staging): " project_name + read -sp "Enter Hetzner API Key for $project_name: " hz_key + echo "" + echo "$hz_key" > "$API_KEYS_DIR/hetzner-${project_name}.txt" + chmod 600 "$API_KEYS_DIR/hetzner-${project_name}.txt" + echo -e "${GREEN}Hetzner API key for '$project_name' saved${NC}" + log_action "INFO" "Hetzner API key for project '$project_name' configured" + ;; + 5) + return + ;; + *) + echo -e "${RED}Invalid option${NC}" + ;; + esac + + pause_menu + setup_api_keys +} + +# ============================================================================ +# LOGGING +# ============================================================================ + +view_logs() { + clear_screen + print_header + echo -e "\n${BLUE}Recent Activity Log (Last 50 entries):${NC}\n" + + if [[ ! -f "$LOG_FILE" ]]; then + echo -e "${YELLOW}No log file found${NC}" + pause_menu + return + fi + + tail -n 50 "$LOG_FILE" | while IFS= read -r line; do + if [[ "$line" == *"ERROR"* ]]; then + echo -e "${RED}$line${NC}" + elif [[ "$line" == *"WARNING"* ]]; then + echo -e "${YELLOW}$line${NC}" + else + echo -e "${GREEN}$line${NC}" + fi + done + + pause_menu +} + +# ============================================================================ +# MAIN MENU +# ============================================================================ + +show_main_menu() { + clear_screen + print_header + echo -e "\n${BLUE}Main Menu:${NC}\n" + + echo -e "${YELLOW}1.${NC} List Vultr Instances" + echo -e "${YELLOW}2.${NC} Vultr Instance Bandwidth Usage (Today)" + echo -e "${YELLOW}3.${NC} List DigitalOcean Droplets" + echo -e "${YELLOW}4.${NC} DigitalOcean Droplet Detailed Info" + echo -e "${YELLOW}5.${NC} List Hetzner Servers" + echo -e "${YELLOW}6.${NC} Hetzner Server Detailed Info" + echo -e "${YELLOW}7.${NC} Setup/Update API Keys" + echo -e "${YELLOW}8.${NC} View Activity Logs" + echo -e "${YELLOW}9.${NC} Exit" + echo "" + + read -p "Choose an option (1-9): " choice + + case $choice in + 1) vultr_display_instances ;; + 2) vultr_instance_bandwidth ;; + 3) digitalocean_display_droplets ;; + 4) digitalocean_display_droplet_info ;; + 5) hetzner_display_servers ;; + 6) hetzner_display_server_info ;; + 7) setup_api_keys ;; + 8) view_logs ;; + 9) + clear_screen + echo -e "${CYAN}Goodbye!${NC}" + log_action "INFO" "Script terminated" + exit 0 + ;; + *) + echo -e "${RED}Invalid option${NC}" + pause_menu + ;; + esac + + show_main_menu +} + +# ============================================================================ +# MAIN EXECUTION +# ============================================================================ + +check_dependencies + +log_action "INFO" "Script started" + +show_main_menu +