#!/bin/bash # # RunPod Control Center - A Beautiful CLI Wrapper for runpodctl # Manage RunPod GPU instances with style # # Usage: ./service_runpod_control.sh [COMMAND] [OPTIONS] # # Commands: # create Create a new pod from configuration # remove Remove a pod (by name or ID) # get Get pod status (all pods) # start Start a stopped pod # stop Stop a running pod # status Alias for 'get' # help Show help message # # Options: # -c, --config FILE Configuration file (default: $PWD/runpod.yml) # -n, --dry-run Show what would be done without executing # -h, --help Show help message # set -euo pipefail # ============================================================================ # COLOR PALETTE - GPU Computing Vibe (Orange/Yellow Theme) # ============================================================================ RESET='\033[0m' # Foreground Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' MAGENTA='\033[0;35m' CYAN='\033[0;36m' ORANGE='\033[38;5;208m' # Bold BOLD_RED='\033[1;31m' BOLD_GREEN='\033[1;32m' BOLD_YELLOW='\033[1;33m' BOLD_BLUE='\033[1;34m' BOLD_MAGENTA='\033[1;35m' BOLD_CYAN='\033[1;36m' BOLD_WHITE='\033[1;37m' BOLD_ORANGE='\033[1;38;5;208m' # Styles DIM='\033[2m' # ============================================================================ # UNICODE CHARACTERS # ============================================================================ CHECK_MARK="โœ“" CROSS_MARK="โœ—" ROCKET="๐Ÿš€" GPU="๐ŸŽฎ" SERVER="๐Ÿ–ฅ๏ธ" POWER="โšก" WARNING="โš ๏ธ" INFO="โ„น๏ธ" SPARKLES="โœจ" MONEY="๐Ÿ’ฐ" GLOBE="๐ŸŒ" PLUG="๐Ÿ”Œ" ID="๐Ÿ†”" STATUS_DOT="โ—" ARROW_RIGHT="โ†’" BOX_LIGHT="โ”€" BOX_DOUBLE="โ•" BOX_VERT="โ”‚" BOX_TL="โ•”" BOX_TR="โ•—" BOX_BL="โ•š" BOX_BR="โ•" # ============================================================================ # CONFIGURATION # ============================================================================ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_FILE="${PWD}/runpod.yml" COMMAND="" DRY_RUN=false POD_ID="" # ============================================================================ # LOGGING FUNCTIONS # ============================================================================ print_banner() { local width=70 local title="$SERVER RunPod Control Center $POWER" local title_plain=" RunPod Control Center " local padding=$(( (width - ${#title_plain} - 4) / 2 )) echo "" echo -e "${BOLD_ORANGE}${BOX_TL}$(printf '%*s' "$width" '' | tr ' ' "$BOX_DOUBLE")${BOX_TR}${RESET}" echo -e "${BOLD_ORANGE}${BOX_VERT}$(printf '%*s' "$padding" '')${BOLD_WHITE}${title}${BOLD_ORANGE}$(printf '%*s' "$padding" '')${BOX_VERT}${RESET}" echo -e "${BOLD_ORANGE}${BOX_BL}$(printf '%*s' "$width" '' | tr ' ' "$BOX_DOUBLE")${BOX_BR}${RESET}" echo "" } print_section() { local title="$1" echo -e "${BOLD_ORANGE}ยป ${BOLD_WHITE}${title}${RESET}" echo -e "${DIM}$(printf '%*s' 70 '' | tr ' ' "$BOX_LIGHT")${RESET}" } print_success() { echo -e "${BOLD_GREEN}${CHECK_MARK}${RESET} $1" } print_error() { echo -e "${BOLD_RED}${CROSS_MARK}${RESET} $1" >&2 } print_warning() { echo -e "${BOLD_YELLOW}${WARNING}${RESET} $1" } print_info() { echo -e "${BOLD_CYAN}${INFO}${RESET} $1" } print_detail() { echo -e " ${DIM}${ARROW_RIGHT}${RESET} $1" } print_field() { local icon="$1" local label="$2" local value="$3" printf " ${icon} ${BOLD_WHITE}%-12s${RESET} %s\n" "${label}:" "$value" } # ============================================================================ # HELPER FUNCTIONS # ============================================================================ check_dependencies() { local missing=() if ! command -v yq &>/dev/null; then missing+=("yq") fi if ! command -v runpodctl &>/dev/null; then missing+=("runpodctl") fi if [[ ${#missing[@]} -gt 0 ]]; then print_error "Missing required dependencies: ${missing[*]}" echo "" echo "Install with:" for dep in "${missing[@]}"; do case "$dep" in yq) echo " brew install yq # or snap install yq" ;; runpodctl) echo " See: https://github.com/runpod/runpodctl" ;; esac done exit 1 fi } load_config() { if [[ ! -f "$CONFIG_FILE" ]]; then print_error "Configuration file not found: $CONFIG_FILE" exit 1 fi # Validate YAML if ! yq eval '.' "$CONFIG_FILE" &>/dev/null; then print_error "Invalid YAML in configuration file: $CONFIG_FILE" exit 1 fi print_info "Using config: ${DIM}${CONFIG_FILE}${RESET}" } get_pod_field() { local field="$1" local default="${2:-}" local value value=$(yq eval ".$field // \"\"" "$CONFIG_FILE") if [[ -z "$value" || "$value" == "null" ]]; then echo "$default" else echo "$value" fi } get_pod_array() { local field="$1" yq eval ".$field[]? // \"\"" "$CONFIG_FILE" 2>/dev/null | grep -v '^$' || true } # Find pod ID by name from runpodctl get pod output find_pod_id_by_name() { local name="$1" local output output=$(runpodctl get pod 2>/dev/null) || { print_error "Failed to get pod list" return 1 } # Parse the output - runpodctl outputs a table # Skip header line and find matching name echo "$output" | tail -n +2 | while read -r line; do local pod_id pod_name pod_id=$(echo "$line" | awk '{print $1}') pod_name=$(echo "$line" | awk '{print $2}') if [[ "$pod_name" == "$name" ]]; then echo "$pod_id" return 0 fi done } get_status_color() { local status="$1" case "${status^^}" in RUNNING) echo -e "${BOLD_GREEN}${STATUS_DOT} RUNNING${RESET}" ;; EXITED|STOPPED) echo -e "${BOLD_YELLOW}${STATUS_DOT} STOPPED${RESET}" ;; ERROR|FAILED) echo -e "${BOLD_RED}${STATUS_DOT} ERROR${RESET}" ;; CREATING|STARTING) echo -e "${BOLD_CYAN}${STATUS_DOT} STARTING${RESET}" ;; *) echo -e "${DIM}${STATUS_DOT} ${status}${RESET}" ;; esac } # ============================================================================ # COMMAND IMPLEMENTATIONS # ============================================================================ cmd_create() { print_section "Creating Pod" load_config local name gpu_type gpu_count template_id network_volume_id local container_disk_size volume_size volume_path mem vcpu local secure_cloud community_cloud image_name # Required fields name=$(get_pod_field "pod.name") gpu_type=$(get_pod_field "pod.gpuType") if [[ -z "$name" ]]; then print_error "Pod name is required in configuration" exit 1 fi if [[ -z "$gpu_type" ]]; then print_error "GPU type is required in configuration" exit 1 fi # Optional fields gpu_count=$(get_pod_field "pod.gpuCount" "1") template_id=$(get_pod_field "pod.templateId") network_volume_id=$(get_pod_field "pod.networkVolumeId") container_disk_size=$(get_pod_field "pod.containerDiskSize" "20") volume_size=$(get_pod_field "pod.volumeSize" "0") volume_path=$(get_pod_field "pod.volumePath" "/runpod") mem=$(get_pod_field "pod.mem") vcpu=$(get_pod_field "pod.vcpu") secure_cloud=$(get_pod_field "pod.secureCloud" "false") community_cloud=$(get_pod_field "pod.communityCloud" "false") image_name=$(get_pod_field "pod.imageName") # Display configuration echo "" print_field "$GPU" "Name" "$name" print_field "$POWER" "GPU" "$gpu_type (x$gpu_count)" [[ -n "$template_id" ]] && print_field "$SERVER" "Template" "$template_id" [[ -n "$network_volume_id" ]] && print_field "$PLUG" "Network Vol" "$network_volume_id" [[ -n "$image_name" ]] && print_field "$ROCKET" "Image" "$image_name" # Ports local ports=() while IFS= read -r port; do [[ -n "$port" ]] && ports+=("$port") done < <(get_pod_array "pod.ports") if [[ ${#ports[@]} -gt 0 ]]; then print_field "$PLUG" "Ports" "${ports[*]}" fi # Environment variables local env_vars=() while IFS= read -r env; do [[ -n "$env" ]] && env_vars+=("$env") done < <(get_pod_array "pod.env") echo "" # Build command arguments local args=() args+=(--name "$name") args+=(--gpuType "$gpu_type") args+=(--gpuCount "$gpu_count") args+=(--containerDiskSize "$container_disk_size") [[ -n "$template_id" ]] && args+=(--templateId "$template_id") [[ -n "$network_volume_id" ]] && args+=(--networkVolumeId "$network_volume_id") [[ -n "$image_name" ]] && args+=(--imageName "$image_name") [[ "$volume_size" != "0" ]] && args+=(--volumeSize "$volume_size") [[ -n "$volume_path" ]] && args+=(--volumePath "$volume_path") [[ -n "$mem" ]] && args+=(--mem "$mem") [[ -n "$vcpu" ]] && args+=(--vcpu "$vcpu") [[ "$secure_cloud" == "true" ]] && args+=(--secureCloud) [[ "$community_cloud" == "true" ]] && args+=(--communityCloud) for port in "${ports[@]}"; do args+=(--ports "$port") done for env in "${env_vars[@]}"; do args+=(--env "$env") done if [[ "$DRY_RUN" == "true" ]]; then print_warning "Dry run - would execute:" echo "" echo -e " ${DIM}runpodctl create pod ${args[*]}${RESET}" echo "" else print_info "Executing: runpodctl create pod..." echo "" local output if output=$(runpodctl create pod "${args[@]}" 2>&1); then print_success "Pod created successfully!" echo "" echo -e " ${DIM}${output}${RESET}" echo "" else print_error "Failed to create pod" echo -e " ${DIM}${output}${RESET}" exit 1 fi fi } cmd_get() { print_section "Pod Status" echo "" local output if ! output=$(runpodctl get pod 2>&1); then print_error "Failed to get pod status" echo -e " ${DIM}${output}${RESET}" exit 1 fi # Check if empty if [[ -z "$output" || "$output" == *"No pods found"* ]]; then print_warning "No pods found" return 0 fi # Parse and display pods beautifully local header_shown=false local count=0 while IFS= read -r line; do # Skip empty lines [[ -z "$line" ]] && continue # Skip header line if [[ "$header_shown" == "false" ]]; then header_shown=true continue fi # Parse fields - runpodctl output format varies # Typically: ID NAME STATUS GPU COST local pod_id pod_name pod_status gpu_info cost_info pod_id=$(echo "$line" | awk '{print $1}') pod_name=$(echo "$line" | awk '{print $2}') pod_status=$(echo "$line" | awk '{print $3}') gpu_info=$(echo "$line" | awk '{print $4}') cost_info=$(echo "$line" | awk '{print $5}') [[ -z "$pod_id" ]] && continue ((count++)) if [[ $count -gt 1 ]]; then echo "" fi print_field "$GPU" "Name" "$pod_name" print_field "$ID" "ID" "$pod_id" echo -e " ${STATUS_DOT} ${BOLD_WHITE}Status:${RESET} $(get_status_color "$pod_status")" [[ -n "$gpu_info" && "$gpu_info" != "-" ]] && print_field "$POWER" "GPU" "$gpu_info" [[ -n "$cost_info" && "$cost_info" != "-" ]] && print_field "$MONEY" "Cost" "$cost_info" done <<< "$output" if [[ $count -eq 0 ]]; then print_warning "No pods found" else echo "" print_info "Total pods: $count" fi echo "" } cmd_start() { print_section "Starting Pod" load_config local name pod_id name=$(get_pod_field "pod.name") if [[ -n "$POD_ID" ]]; then pod_id="$POD_ID" elif [[ -n "$name" ]]; then print_info "Looking up pod by name: $name" pod_id=$(find_pod_id_by_name "$name") fi if [[ -z "$pod_id" ]]; then print_error "Pod not found. Specify --id or configure pod.name in config." exit 1 fi echo "" print_field "$ID" "Pod ID" "$pod_id" echo "" if [[ "$DRY_RUN" == "true" ]]; then print_warning "Dry run - would execute:" echo "" echo -e " ${DIM}runpodctl start pod $pod_id${RESET}" echo "" else local output if output=$(runpodctl start pod "$pod_id" 2>&1); then print_success "Pod starting!" echo -e " ${DIM}${output}${RESET}" else print_error "Failed to start pod" echo -e " ${DIM}${output}${RESET}" exit 1 fi fi echo "" } cmd_stop() { print_section "Stopping Pod" load_config local name pod_id name=$(get_pod_field "pod.name") if [[ -n "$POD_ID" ]]; then pod_id="$POD_ID" elif [[ -n "$name" ]]; then print_info "Looking up pod by name: $name" pod_id=$(find_pod_id_by_name "$name") fi if [[ -z "$pod_id" ]]; then print_error "Pod not found. Specify --id or configure pod.name in config." exit 1 fi echo "" print_field "$ID" "Pod ID" "$pod_id" echo "" if [[ "$DRY_RUN" == "true" ]]; then print_warning "Dry run - would execute:" echo "" echo -e " ${DIM}runpodctl stop pod $pod_id${RESET}" echo "" else local output if output=$(runpodctl stop pod "$pod_id" 2>&1); then print_success "Pod stopping!" echo -e " ${DIM}${output}${RESET}" else print_error "Failed to stop pod" echo -e " ${DIM}${output}${RESET}" exit 1 fi fi echo "" } cmd_remove() { print_section "Removing Pod" load_config local name pod_id name=$(get_pod_field "pod.name") if [[ -n "$POD_ID" ]]; then pod_id="$POD_ID" elif [[ -n "$name" ]]; then print_info "Looking up pod by name: $name" pod_id=$(find_pod_id_by_name "$name") fi if [[ -z "$pod_id" ]]; then print_error "Pod not found. Specify --id or configure pod.name in config." exit 1 fi echo "" print_field "$ID" "Pod ID" "$pod_id" print_field "$GPU" "Name" "${name:-unknown}" echo "" if [[ "$DRY_RUN" == "true" ]]; then print_warning "Dry run - would execute:" echo "" echo -e " ${DIM}runpodctl remove pod $pod_id${RESET}" echo "" else # Confirm removal print_warning "This will permanently delete the pod!" echo -n " Continue? [y/N] " read -r confirm if [[ "${confirm,,}" != "y" ]]; then print_info "Cancelled" exit 0 fi echo "" local output if output=$(runpodctl remove pod "$pod_id" 2>&1); then print_success "Pod removed!" echo -e " ${DIM}${output}${RESET}" else print_error "Failed to remove pod" echo -e " ${DIM}${output}${RESET}" exit 1 fi fi echo "" } cmd_help() { echo -e "${BOLD_WHITE}USAGE${RESET}" echo " service_runpod_control.sh [COMMAND] [OPTIONS]" echo "" echo -e "${BOLD_WHITE}COMMANDS${RESET}" echo -e " ${BOLD_ORANGE}create${RESET} Create a new pod from configuration" echo -e " ${BOLD_ORANGE}remove${RESET} Remove a pod (by name or ID)" echo -e " ${BOLD_ORANGE}get${RESET} Get pod status (all pods)" echo -e " ${BOLD_ORANGE}start${RESET} Start a stopped pod" echo -e " ${BOLD_ORANGE}stop${RESET} Stop a running pod" echo -e " ${BOLD_ORANGE}status${RESET} Alias for 'get'" echo -e " ${BOLD_ORANGE}help${RESET} Show this help message" echo "" echo -e "${BOLD_WHITE}OPTIONS${RESET}" echo " -c, --config FILE Configuration file (default: \$PWD/runpod.yml)" echo " --id POD_ID Pod ID for start/stop/remove commands" echo " -n, --dry-run Show what would be done without executing" echo " -h, --help Show this help message" echo "" echo -e "${BOLD_WHITE}CONFIGURATION${RESET}" echo " Create a runpod.yml file with your pod configuration:" echo "" echo -e " ${DIM}pod:" echo " name: \"my-gpu-pod\"" echo " gpuType: \"NVIDIA GeForce RTX 4090\"" echo " gpuCount: 1" echo " templateId: \"your-template-id\"" echo " networkVolumeId: \"your-volume-id\"" echo " ports:" echo -e " - \"22/tcp\"${RESET}" echo "" echo -e "${BOLD_WHITE}EXAMPLES${RESET}" echo " # Create a pod from config" echo " ./service_runpod_control.sh create" echo "" echo " # Get status of all pods" echo " ./service_runpod_control.sh get" echo "" echo " # Stop a pod by name (from config)" echo " ./service_runpod_control.sh stop" echo "" echo " # Stop a specific pod by ID" echo " ./service_runpod_control.sh stop --id abc123xyz" echo "" } # ============================================================================ # ARGUMENT PARSING # ============================================================================ parse_args() { while [[ $# -gt 0 ]]; do case "$1" in create|remove|get|start|stop|status|help) COMMAND="$1" shift ;; -c|--config) CONFIG_FILE="$2" shift 2 ;; --id) POD_ID="$2" shift 2 ;; -n|--dry-run) DRY_RUN=true shift ;; -h|--help) COMMAND="help" shift ;; *) print_error "Unknown argument: $1" echo "Use 'help' command for usage information" exit 1 ;; esac done } # ============================================================================ # MAIN # ============================================================================ main() { parse_args "$@" # Default to help if no command if [[ -z "$COMMAND" ]]; then cmd_help exit 0 fi # Check dependencies (except for help) if [[ "$COMMAND" != "help" ]]; then check_dependencies fi print_banner case "$COMMAND" in create) cmd_create ;; remove) cmd_remove ;; get|status) cmd_get ;; start) cmd_start ;; stop) cmd_stop ;; help) cmd_help ;; *) print_error "Unknown command: $COMMAND" exit 1 ;; esac } main "$@"