Part 10 of 10 — Series Finale

    Building Custom CLI Tools for Your Infrastructure

    Tie everything together into cohesive, professional CLI tools that feel native to your workflow. Build management CLIs, API wrappers, and interactive dashboards.

    Server CLI
    API Wrappers
    TUI Dashboard

    Throughout this series, we've generated scripts for specific tasks. Now it's time to tie everything together into cohesive, professional CLI tools that feel native to your workflow. This final guide covers building custom management CLIs, API wrappers, interactive admin tools, and the patterns that make command-line tools a joy to use.

    1

    Prerequisites

    • Claude Code installed (see Part 1)
    • Familiarity with bash scripting
    • Understanding of your infrastructure from previous parts
    2

    The Server Management CLI

    Let's build a comprehensive server management tool that wraps common operations:

    Prompt for Claude Code
    Create a professional CLI tool called "srv" for server management with:
    
    Commands:
    - srv status          Show system overview
    - srv services        List and manage systemd services
    - srv logs [service]  View and search logs
    - srv docker          Docker management subcommands
    - srv backup          Trigger and manage backups
    - srv deploy [app]    Deploy applications
    - srv ssh [host]      SSH to managed hosts
    - srv secrets         Manage encrypted secrets
    - srv update          Update system packages
    - srv firewall        Firewall management
    
    Features:
    - Bash completion
    - Colored output with consistent styling
    - Configuration file for customization
    - Help text for all commands
    - Dry-run mode for dangerous operations
    - Logging of all operations
    
    Make it modular so commands can be added easily.
    srv (main executable)
    #!/bin/bash
    # =============================================================================
    # srv - Server Management CLI
    # =============================================================================
    
    set -uo pipefail
    
    # Version
    VERSION="1.0.0"
    
    # Paths
    SRV_HOME="${SRV_HOME:-$HOME/.srv}"
    SRV_CONFIG="$SRV_HOME/config"
    SRV_LOG="$SRV_HOME/srv.log"
    SRV_COMMANDS="$SRV_HOME/commands"
    
    # Create directories
    mkdir -p "$SRV_HOME" "$SRV_COMMANDS"
    
    # Colors
    export RED='\033[0;31m'
    export GREEN='\033[0;32m'
    export YELLOW='\033[0;33m'
    export BLUE='\033[0;34m'
    export CYAN='\033[0;36m'
    export BOLD='\033[1m'
    export DIM='\033[2m'
    export NC='\033[0m'
    
    # Configuration defaults
    DRY_RUN="${DRY_RUN:-false}"
    VERBOSE="${VERBOSE:-false}"
    NO_COLOR="${NO_COLOR:-false}"
    
    # Disable colors if requested or not a terminal
    if [[ "$NO_COLOR" == "true" ]] || [[ ! -t 1 ]]; then
        RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' DIM='' NC=''
    fi
    
    # Load config if exists
    [[ -f "$SRV_CONFIG" ]] && source "$SRV_CONFIG"
    
    # -----------------------------------------------------------------------------
    # Utility Functions (exported for subcommands)
    # -----------------------------------------------------------------------------
    
    srv_log() {
        local level="$1"
        shift
        local message="$*"
        local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
        echo "[$timestamp] [$level] $message" >> "$SRV_LOG"
        
        if [[ "$VERBOSE" == "true" ]] || [[ "$level" == "ERROR" ]]; then
            case "$level" in
                INFO)  echo -e "${BLUE}ℹ${NC} $message" ;;
                OK)    echo -e "${GREEN}✓${NC} $message" ;;
                WARN)  echo -e "${YELLOW}⚠${NC} $message" ;;
                ERROR) echo -e "${RED}✗${NC} $message" >&2 ;;
            esac
        fi
    }
    
    srv_info()  { srv_log "INFO" "$@"; }
    srv_ok()    { srv_log "OK" "$@"; }
    srv_warn()  { srv_log "WARN" "$@"; }
    srv_error() { srv_log "ERROR" "$@"; }
    
    srv_header() {
        echo -e "\n${BOLD}${BLUE}━━━ $* ━━━${NC}\n"
    }
    
    srv_confirm() {
        local prompt="${1:-Continue?}"
        if [[ "$DRY_RUN" == "true" ]]; then
            echo -e "${YELLOW}[DRY-RUN]${NC} Would prompt: $prompt"
            return 0
        fi
        read -p "$prompt [y/N] " -n 1 -r
        echo
        [[ $REPLY =~ ^[Yy]$ ]]
    }
    
    srv_run() {
        # Execute command with logging and dry-run support
        local cmd="$*"
        srv_info "Running: $cmd"
        
        if [[ "$DRY_RUN" == "true" ]]; then
            echo -e "${YELLOW}[DRY-RUN]${NC} $cmd"
            return 0
        fi
        
        eval "$cmd"
    }
    
    # Export utilities for subcommands
    export -f srv_log srv_info srv_ok srv_warn srv_error srv_header srv_confirm srv_run
    export SRV_HOME SRV_CONFIG SRV_LOG DRY_RUN VERBOSE
    
    # -----------------------------------------------------------------------------
    # Help
    # -----------------------------------------------------------------------------
    
    show_help() {
        cat << EOF
    ${BOLD}srv${NC} - Server Management CLI v$VERSION
    
    ${BOLD}USAGE:${NC}
        srv [OPTIONS] <command> [args]
    
    ${BOLD}OPTIONS:${NC}
        -n, --dry-run       Show what would be done without executing
        -v, --verbose       Verbose output
        -h, --help          Show this help
        --version           Show version
        --no-color          Disable colored output
    
    ${BOLD}COMMANDS:${NC}
        status              System status overview
        services [action]   Manage systemd services
        logs [service]      View and search logs
        docker <cmd>        Docker management
        backup [action]     Backup operations
        deploy <app>        Deploy applications
        ssh <host>          SSH to managed hosts
        secrets <action>    Manage secrets
        update              Update system packages
        firewall <action>   Firewall management
        config              Edit configuration
        help <command>      Help for specific command
    
    ${BOLD}EXAMPLES:${NC}
        srv status
        srv services restart nginx
        srv logs nginx --since 1h --grep error
        srv docker ps
        srv backup run
        srv deploy myapp --branch main
        srv --dry-run update
    
    ${BOLD}CONFIGURATION:${NC}
        Config file: $SRV_CONFIG
        Log file: $SRV_LOG
        
    Run 'srv help <command>' for detailed help on any command.
    EOF
    }
    
    # -----------------------------------------------------------------------------
    # Built-in Commands
    # -----------------------------------------------------------------------------
    
    srv_cmd_status() {
        if [[ "${1:-}" == "--help" ]]; then
            echo "Usage: srv status"
            echo "Show system status overview"
            return
        fi
        
        srv_header "System Status"
        
        # Host info
        echo -e "${BOLD}Host:${NC} $(hostname) ($(uname -r))"
        echo -e "${BOLD}Uptime:${NC} $(uptime -p)"
        
        # Load
        local load=$(cat /proc/loadavg | cut -d' ' -f1-3)
        local cpus=$(nproc)
        echo -e "${BOLD}Load:${NC} $load (${cpus} CPUs)"
        
        # Memory
        local mem_info=$(free -h | awk '/Mem:/ {print $3 "/" $2 " (" int($3/$2*100) "%)"}')
        echo -e "${BOLD}Memory:${NC} $mem_info"
        
        # Disk
        echo -e "\n${BOLD}Disk Usage:${NC}"
        df -h -x tmpfs -x devtmpfs -x overlay | awk 'NR==1 || $5+0 > 50'
        
        # Services
        local failed=$(systemctl --failed --no-pager --no-legend 2>/dev/null | wc -l)
        if [[ $failed -gt 0 ]]; then
            echo -e "\n${RED}${BOLD}Failed Services: $failed${NC}"
            systemctl --failed --no-pager --no-legend 2>/dev/null | head -5
        else
            echo -e "\n${GREEN}${BOLD}All services healthy${NC}"
        fi
        
        # Docker
        if command -v docker &>/dev/null && docker info &>/dev/null; then
            local containers=$(docker ps -q | wc -l)
            echo -e "\n${BOLD}Docker:${NC} $containers running"
        fi
    }
    
    # ... Additional commands continue in main script
    main() {
        # Parse global options then run command
        run_command "$@"
    }
    
    main "$@"
    3

    Docker Management Subcommand

    Create a modular subcommand for Docker operations:

    srv-docker (subcommand)
    #!/bin/bash
    # =============================================================================
    # srv docker - Docker management subcommand
    # =============================================================================
    
    source "${SRV_HOME:-$HOME/.srv}/commands/.common" 2>/dev/null || true
    
    show_help() {
        cat << EOF
    Usage: srv docker <command> [args]
    
    Commands:
        ps                  List containers (default)
        logs <container>    View container logs
        shell <container>   Open shell in container
        restart <container> Restart container
        stop <container>    Stop container
        start <container>   Start container
        stats               Show resource usage
        clean               Clean up unused resources
        pull                Pull latest images for running containers
        compose <args>      Run docker compose commands
    
    Examples:
        srv docker ps
        srv docker logs nginx --follow
        srv docker shell webapp
        srv docker clean
        srv docker compose up -d
    EOF
    }
    
    cmd_ps() {
        srv_header "Docker Containers"
        docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}\t{{.Ports}}" | head -20
        
        # Show unhealthy
        local unhealthy=$(docker ps --filter "health=unhealthy" --format "{{.Names}}" 2>/dev/null)
        if [[ -n "$unhealthy" ]]; then
            echo -e "\n${RED}${BOLD}Unhealthy:${NC}"
            echo "$unhealthy"
        fi
    }
    
    cmd_logs() {
        local container="$1"
        shift || { srv_error "Container name required"; return 1; }
        
        local follow=""
        local tail="100"
        
        while [[ $# -gt 0 ]]; do
            case "$1" in
                -f|--follow) follow="-f"; shift ;;
                -n|--tail) tail="$2"; shift 2 ;;
                *) break ;;
            esac
        done
        
        docker logs $follow --tail "$tail" "$container"
    }
    
    cmd_shell() {
        local container="$1"
        [[ -z "$container" ]] && { srv_error "Container name required"; return 1; }
        
        # Try bash first, fall back to sh
        docker exec -it "$container" bash 2>/dev/null || \
        docker exec -it "$container" sh
    }
    
    cmd_clean() {
        srv_header "Docker Cleanup"
        
        echo "Current disk usage:"
        docker system df
        
        echo ""
        srv_confirm "Remove unused containers, images, and volumes?" || return
        
        srv_run docker system prune -af --volumes
        srv_ok "Cleanup complete"
    }
    
    cmd_stats() {
        docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"
    }
    
    # Main
    case "${1:-ps}" in
        --help|-h) show_help ;;
        ps) shift; cmd_ps "$@" ;;
        logs) shift; cmd_logs "$@" ;;
        shell|exec) shift; cmd_shell "$@" ;;
        restart) shift; srv_confirm "Restart $1?" && docker restart "$1" ;;
        stop) shift; srv_confirm "Stop $1?" && docker stop "$1" ;;
        start) shift; docker start "$1" ;;
        stats) shift; cmd_stats "$@" ;;
        clean|prune) shift; cmd_clean "$@" ;;
        pull) docker ps --format "{{.Image}}" | sort -u | xargs -I{} docker pull {} ;;
        compose) shift; docker compose "$@" ;;
        *) srv_error "Unknown command: $1"; show_help ;;
    esac
    4

    Backup Management Subcommand

    Handle backup operations with status, listing, and verification:

    srv-backup (subcommand)
    #!/bin/bash
    # =============================================================================
    # srv backup - Backup management subcommand
    # =============================================================================
    
    BACKUP_CONFIG="${BACKUP_CONFIG:-/etc/srv/backup.conf}"
    BACKUP_DIR="${BACKUP_DIR:-/var/backups}"
    
    show_help() {
        cat << EOF
    Usage: srv backup <command> [args]
    
    Commands:
        run                 Run backup now
        status              Show backup status
        list                List available backups
        restore <backup>    Restore from backup
        verify              Verify backup integrity
        schedule            Show/edit backup schedule
    
    Configuration: $BACKUP_CONFIG
    EOF
    }
    
    cmd_status() {
        srv_header "Backup Status"
        
        echo "Last backup runs:"
        
        # Check database backup
        if [[ -d "$BACKUP_DIR/databases/daily" ]]; then
            local last_db=$(ls -t "$BACKUP_DIR/databases/daily" 2>/dev/null | head -1)
            if [[ -n "$last_db" ]]; then
                echo -e "  ${GREEN}✓${NC} Database: $last_db"
            else
                echo -e "  ${RED}✗${NC} Database: No backups found"
            fi
        fi
        
        # Show disk usage
        echo ""
        echo "Backup storage usage:"
        du -sh "$BACKUP_DIR"/* 2>/dev/null | while read -r line; do
            echo "  $line"
        done
    }
    
    cmd_verify() {
        srv_header "Verifying Backups"
        
        echo "Checking backup file integrity..."
        
        find "$BACKUP_DIR" -name "*.sha256" -type f | while read -r checksum_file; do
            local backup_file="${checksum_file%.sha256}"
            if [[ -f "$backup_file" ]]; then
                if sha256sum -c "$checksum_file" &>/dev/null; then
                    echo -e "  ${GREEN}✓${NC} $(basename "$backup_file")"
                else
                    echo -e "  ${RED}✗${NC} $(basename "$backup_file") - CHECKSUM FAILED"
                fi
            fi
        done
    }
    
    cmd_schedule() {
        srv_header "Backup Schedule"
        
        echo "Current cron jobs:"
        crontab -l 2>/dev/null | grep -E "(backup|Backup)" || echo "  No backup cron jobs found"
        
        echo ""
        echo "Systemd timers:"
        systemctl list-timers --all 2>/dev/null | grep -i backup || echo "  No backup timers found"
    }
    
    # Main
    case "${1:-status}" in
        --help|-h) show_help ;;
        run) shift; /opt/scripts/backup-databases.sh && /opt/scripts/backup-files.sh ;;
        status) shift; cmd_status "$@" ;;
        list|ls) find "$BACKUP_DIR" -name "*.tar.zst*" -type f | sort -r | head -20 ;;
        restore) shift; srv_warn "Restore from: $1"; /opt/scripts/restore.sh "$1" ;;
        verify) shift; cmd_verify "$@" ;;
        schedule) shift; cmd_schedule "$@" ;;
        *) srv_error "Unknown command: $1"; show_help ;;
    esac
    5

    Deployment Subcommand

    Automate application deployments with rollback support:

    srv-deploy (subcommand)
    #!/bin/bash
    # =============================================================================
    # srv deploy - Application deployment subcommand
    # =============================================================================
    
    APPS_DIR="${APPS_DIR:-/opt/apps}"
    
    deploy_app() {
        local app="$1"
        local branch="${BRANCH:-main}"
        local rollback="${ROLLBACK:-false}"
        
        local app_dir="$APPS_DIR/$app"
        
        if [[ ! -d "$app_dir" ]]; then
            srv_error "App not found: $app"
            return 1
        fi
        
        srv_header "Deploying $app"
        cd "$app_dir" || return 1
        
        # Rollback
        if [[ "$rollback" == "true" ]]; then
            srv_warn "Rolling back $app"
            srv_run git checkout HEAD~1
            srv_run docker compose up -d
            srv_ok "Rollback complete"
            return
        fi
        
        # Show current state
        local current_commit=$(git rev-parse --short HEAD 2>/dev/null)
        echo "Current: $current_commit"
        
        # Pull updates
        srv_info "Deploying branch: $branch"
        srv_run git fetch origin
        srv_run git checkout "$branch"
        srv_run git pull origin "$branch"
        
        local new_commit=$(git rev-parse --short HEAD 2>/dev/null)
        echo "Deploying: $new_commit"
        
        # Check for docker-compose
        if [[ -f "docker-compose.yml" ]] || [[ -f "compose.yml" ]]; then
            srv_info "Pulling images..."
            srv_run docker compose pull
            
            srv_info "Starting containers..."
            srv_run docker compose up -d --remove-orphans
            
            # Wait and check health
            sleep 5
            
            local unhealthy=$(docker compose ps --filter "health=unhealthy" -q 2>/dev/null | wc -l)
            if [[ "$unhealthy" -gt 0 ]]; then
                srv_error "Unhealthy containers detected!"
                
                if srv_confirm "Rollback to previous version?"; then
                    srv_run git checkout "$current_commit"
                    srv_run docker compose up -d
                fi
                return 1
            fi
            
            srv_ok "Deployment successful: $current_commit → $new_commit"
        else
            srv_warn "No docker-compose.yml found"
        fi
    }
    
    # Parse arguments and run
    APP=""
    BRANCH="main"
    ROLLBACK=false
    
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h) echo "Usage: srv deploy <app> [--branch <branch>] [--rollback]"; exit 0 ;;
            --branch|-b) BRANCH="$2"; shift 2 ;;
            --rollback) ROLLBACK=true; shift ;;
            *) APP="$1"; shift ;;
        esac
    done
    
    [[ -z "$APP" ]] && { srv_error "App name required"; exit 1; }
    deploy_app "$APP"
    6

    Bash Completion

    Make the CLI feel professional with tab completion:

    srv-completion.bash
    # Bash completion for srv command
    
    _srv_completions() {
        local cur prev words cword
        _init_completion || return
        
        local commands="status services logs docker backup deploy ssh secrets update firewall config help"
        local docker_commands="ps logs shell restart stop start stats clean pull compose"
        local backup_commands="run status list restore verify schedule"
        local services_actions="list status start stop restart enable disable logs"
        local firewall_actions="status allow deny list reset"
        
        case $cword in
            1)
                COMPREPLY=($(compgen -W "$commands" -- "$cur"))
                ;;
            2)
                case "${words[1]}" in
                    docker)
                        COMPREPLY=($(compgen -W "$docker_commands" -- "$cur"))
                        ;;
                    backup)
                        COMPREPLY=($(compgen -W "$backup_commands" -- "$cur"))
                        ;;
                    services)
                        COMPREPLY=($(compgen -W "$services_actions" -- "$cur"))
                        ;;
                    firewall)
                        COMPREPLY=($(compgen -W "$firewall_actions" -- "$cur"))
                        ;;
                    deploy)
                        local apps=$(ls -1 /opt/apps 2>/dev/null)
                        COMPREPLY=($(compgen -W "$apps" -- "$cur"))
                        ;;
                    logs|ssh)
                        local services=$(systemctl list-units --type=service --state=running --no-pager --no-legend | awk '{print $1}' | sed 's/.service$//')
                        COMPREPLY=($(compgen -W "$services" -- "$cur"))
                        ;;
                esac
                ;;
            3)
                case "${words[1]}" in
                    docker)
                        case "${words[2]}" in
                            logs|shell|restart|stop|start)
                                local containers=$(docker ps --format '{{.Names}}' 2>/dev/null)
                                COMPREPLY=($(compgen -W "$containers" -- "$cur"))
                                ;;
                        esac
                        ;;
                esac
                ;;
        esac
    }
    
    complete -F _srv_completions srv
    Install completion
    # Add to ~/.bashrc or /etc/bash_completion.d/
    source /path/to/srv-completion.bash
    7

    API Wrapper for External Services

    Create a unified interface for commonly used APIs like Cloudflare, Discord, and Healthchecks.io:

    api-wrapper.sh
    #!/bin/bash
    # =============================================================================
    # API Wrapper - Unified interface for external APIs
    # =============================================================================
    
    set -uo pipefail
    
    API_CONFIG="${API_CONFIG:-$HOME/.config/api-wrapper/config}"
    API_CACHE="${API_CACHE:-$HOME/.cache/api-wrapper}"
    
    mkdir -p "$API_CACHE"
    [[ -f "$API_CONFIG" ]] && source "$API_CONFIG"
    
    # Rate limiting
    check_rate_limit() {
        local api="$1"
        local limit="${2:-60}"
        local now=$(date +%s)
        # Check and update rate limit file
        # Returns 1 if limit exceeded
    }
    
    # Generic API request with retries
    api_request() {
        local method="$1"
        local url="$2"
        local data="${3:-}"
        local headers=("${@:4}")
        local retries=3
        
        local attempt=1
        while [[ $attempt -le $retries ]]; do
            local response=$(curl -s -w "\n%{http_code}" -X "$method" \
                "${headers[@]/#/-H }" \
                ${data:+-d "$data"} \
                "$url")
            
            local http_code=$(echo "$response" | tail -1)
            local body=$(echo "$response" | sed '$d')
            
            case "$http_code" in
                2*) echo "$body"; return 0 ;;
                429) sleep $((5 * attempt)) ;;
                5*) sleep 5 ;;
                *) echo "$body" >&2; return 1 ;;
            esac
            ((attempt++))
        done
    }
    
    # Cloudflare API
    cf_api() {
        api_request "${2:-GET}" \
            "https://api.cloudflare.com/client/v4$1" \
            "${3:-}" \
            "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
            "Content-Type: application/json"
    }
    
    cf_list_zones() { cf_api "/zones" | jq -r '.result[] | "\(.id)\t\(.name)"'; }
    cf_purge_cache() { cf_api "/zones/$1/purge_cache" "POST" '{"purge_everything":true}'; }
    
    # Discord webhooks
    discord_send() {
        local message="$1"
        local color="${2:-}"
        local data=$(jq -n --arg msg "$message" '{content: $msg}')
        api_request "POST" "$DISCORD_WEBHOOK_URL" "$data" "Content-Type: application/json"
    }
    
    # Healthchecks.io
    healthcheck_ping() {
        curl -fsS -m 10 --retry 5 "$1${2:+/$2}" > /dev/null
    }
    
    # Main CLI
    case "${1:-}" in
        cf|cloudflare) shift; cf_api "$@" ;;
        discord) shift; discord_send "$@" ;;
        hc|healthcheck) shift; healthcheck_ping "$@" ;;
        *) echo "Usage: api-wrapper <cf|discord|hc> <command>" ;;
    esac
    8

    Interactive Server Dashboard

    Build a TUI dashboard for real-time server monitoring:

    dashboard.sh
    #!/bin/bash
    # =============================================================================
    # Terminal Dashboard - Real-time server monitoring
    # =============================================================================
    
    set -uo pipefail
    REFRESH_INTERVAL="${REFRESH_INTERVAL:-5}"
    
    # Colors and terminal
    RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
    BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m'
    
    draw_bar() {
        local percent="$1" width="${2:-20}" color="$GREEN"
        [[ $percent -gt 75 ]] && color=$YELLOW
        [[ $percent -gt 90 ]] && color=$RED
        
        local filled=$((percent * width / 100))
        local empty=$((width - filled))
        
        printf "["
        printf "$color"
        printf '%*s' "$filled" '' | tr ' ' '█'
        printf "$NC"
        printf '%*s' "$empty" '' | tr ' ' '░'
        printf "] %3d%%\n" "$percent"
    }
    
    section_system() {
        echo -e "${BOLD}${BLUE}━━━ System ━━━${NC}"
        
        # CPU
        local cpu_idle=$(top -bn1 | grep "Cpu(s)" | awk '{print $8}' | cut -d. -f1)
        printf "CPU   "; draw_bar "$((100 - ${cpu_idle:-0}))" 25
        
        # Memory
        local mem=$(free | awk '/Mem:/ {printf "%.0f", $3/$2*100}')
        printf "MEM   "; draw_bar "$mem" 25
        
        # Disk
        local disk=$(df / | awk 'NR==2 {gsub(/%/,""); print $5}')
        printf "DISK  "; draw_bar "$disk" 25
        
        echo "Load: $(cat /proc/loadavg | cut -d' ' -f1-3)"
        echo "Up: $(uptime -p | sed 's/up //')"
    }
    
    section_docker() {
        echo -e "\n${BOLD}${BLUE}━━━ Docker ━━━${NC}"
        if docker info &>/dev/null 2>&1; then
            local running=$(docker ps -q | wc -l)
            echo "Containers: $running running"
            docker stats --no-stream --format "  {{.Name}}\t{{.CPUPerc}}\t{{.MemPerc}}" | head -5
        else
            echo "Docker not available"
        fi
    }
    
    section_services() {
        echo -e "\n${BOLD}${BLUE}━━━ Services ━━━${NC}"
        local failed=$(systemctl --failed --no-pager --no-legend 2>/dev/null | wc -l)
        if [[ $failed -gt 0 ]]; then
            echo -e "${RED}Failed: $failed${NC}"
        else
            echo -e "${GREEN}All services healthy${NC}"
        fi
    }
    
    render() {
        clear
        echo -e "${BOLD}${CYAN}╔═══════════════════════════════════════════════════════════╗"
        echo -e "║          SERVER DASHBOARD - $(hostname)              ║"
        echo -e "║          $(date '+%Y-%m-%d %H:%M:%S')                        ║"
        echo -e "╚═══════════════════════════════════════════════════════════╝${NC}"
        
        section_system
        section_docker
        section_services
        
        echo -e "\n${DIM}Refresh: ${REFRESH_INTERVAL}s | Press 'q' to quit${NC}"
    }
    
    # Main loop
    while true; do
        render
        if read -rsn1 -t "$REFRESH_INTERVAL" key; then
            [[ "$key" == "q" ]] && break
        fi
    done
    9

    Installation Script

    Tie everything together with an installer:

    install-srv.sh
    #!/bin/bash
    # =============================================================================
    # srv CLI Installation Script
    # =============================================================================
    
    set -euo pipefail
    
    INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"
    SRV_HOME="${SRV_HOME:-$HOME/.srv}"
    
    echo "Installing srv CLI..."
    
    # Create directories
    mkdir -p "$SRV_HOME/commands"
    mkdir -p "$HOME/.config/api-wrapper"
    
    # Install main executable
    cp ./srv "$INSTALL_DIR/srv"
    chmod +x "$INSTALL_DIR/srv"
    
    # Install subcommands
    for cmd in srv-docker srv-backup srv-deploy; do
        if [[ -f "./$cmd" ]]; then
            cp "./$cmd" "$SRV_HOME/commands/"
            chmod +x "$SRV_HOME/commands/$cmd"
        fi
    done
    
    # Create default config
    if [[ ! -f "$SRV_HOME/config" ]]; then
        cat > "$SRV_HOME/config" << 'EOF'
    # srv CLI Configuration
    EDITOR="${EDITOR:-vim}"
    APPS_DIR="/opt/apps"
    BACKUP_DIR="/var/backups"
    # DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/xxx/yyy"
    EOF
    fi
    
    # Install completion
    if [[ -f "./srv-completion.bash" ]]; then
        cp "./srv-completion.bash" "$SRV_HOME/"
        echo "source $SRV_HOME/srv-completion.bash" >> "$HOME/.bashrc"
    fi
    
    echo ""
    echo "✓ Installation complete!"
    echo ""
    echo "Usage:"
    echo "  srv status          # System overview"
    echo "  srv docker ps       # Docker containers"
    echo "  srv help            # Full help"
    echo ""
    echo "Reload your shell: source ~/.bashrc"
    10

    Tips & Quick Reference

    Tips for Building CLI Tools

    • Consistent interface — Use the same patterns for all commands
    • Fail gracefully — Check for dependencies, provide helpful errors
    • Document everything — Every command should have --help
    • Add completion — Tab completion makes a huge difference
    • Log operations — Keep a record for debugging
    • Dry-run mode — Let users preview dangerous operations
    • Color thoughtfully — Highlight important info, don't decorate

    Quick Reference: CLI Building Prompts

    NeedPrompt Pattern
    Main CLI"Create CLI tool called [name] with commands for [operations]"
    Subcommand"Add subcommand [name] to handle [operations] with [options]"
    Completion"Generate bash completion for [tool] supporting [commands]"
    API wrapper"Create wrapper for [API] with [auth] and [operations]"
    Dashboard"Build terminal dashboard showing [metrics] with [refresh rate]"
    Installer"Generate install script that sets up [components] at [paths]"

    Series Conclusion

    Over these ten parts, you've learned to use Claude Code for:

    1. Server hardening and initial setup
    2. OpenStack infrastructure automation with Terraform
    3. Docker Compose generation for any application stack
    4. Monitoring pipelines with Prometheus and Grafana
    5. GitOps workflows with Ansible and CI/CD
    6. AI model deployment for local inference
    7. Backup and disaster recovery automation
    8. Security hardening and compliance checking
    9. Log analysis and troubleshooting workflows
    10. Custom CLI tools that tie everything together

    The key insight: Claude Code isn't just for generating one-off scripts. It's a conversational partner for building complete, professional infrastructure tooling. Describe what you need, iterate on the output, and build a personalized toolkit that matches exactly how you work.