服务器登录脚本

bash 复制代码
#!/bin/bash

# 定义服务器列表(直接在脚本中维护)
# 将 SERVER_LIST 分为多个分类(可在原定义后添加)
# 格式为 "分组名|服务器名|用户名|IP|密码|端口"
GROUPED_SERVER_LIST=(
    "测试环境|测试虚拟机single|single|192.168.1.10|123456|22"
    "生产环境|服务器ai-01|test|192.168.1.80|123456|22"
)


# 更新的显示菜单函数 - 支持分组
show_menu() {
    clear
    echo -e "\033[34m$(printf '%.0s═' {1..70})\033[0m"
    echo -e "\033[34m║$(printf '%29s' "服务器登录系统")$(printf '%37s' "")║\033[0m"
    echo -e "\033[34m$(printf '%.0s═' {1..70})\033[0m"
    echo -e "\033[32m$(printf "%2s %-${#servername_max_length}s %-${#username_max_length}s@%-15s %-${#group_max_length}s %s\033[0m" "编号" "服务器名称" "登录信息" "分组" "状态")"
    echo -e "\033[34m$(printf '%.0s─' {1..70})\033[0m"
    
    # 计算各列最大长度
    local group_max_length=0
    local servername_max_length=0
    local username_max_length=0
    
    for server_entry in "${GROUPED_SERVER_LIST[@]}"; do
        IFS='|' read -r -a server_info <<< "$server_entry"
        local group_name="${server_info[0]}"
        local server_name="${server_info[1]}"
        local username="${server_info[2]}"
        
        if [[ ${#group_name} -gt $group_max_length ]]; then
            group_max_length=${#group_name}
        fi
        if [[ ${#server_name} -gt $servername_max_length ]]; then
            servername_max_length=${#server_name}
        fi
        if [[ ${#username} -gt $username_max_length ]]; then
            username_max_length=${#username}
        fi
    done
    
    # 设置最小宽度
    group_max_length=$((group_max_length > 10 ? group_max_length : 10))
    servername_max_length=$((servername_max_length > 18 ? servername_max_length : 18))
    username_max_length=$((username_max_length > 8 ? username_max_length : 8))
    
    # 记录上一个分组以显示分组标题
    local last_group=""
    
    for i in "${!GROUPED_SERVER_LIST[@]}"; do
        IFS='|' read -r -a server_info <<< "${GROUPED_SERVER_LIST[i]}"
        local group_name="${server_info[0]}"
        local server_name="${server_info[1]}"
        local username="${server_info[2]}"
        local host="${server_info[3]}"
        local password="${server_info[4]}"
        local port="${server_info[5]}"  # 添加端口信息
        
        # 如果分组变化,显示分组标题
        if [[ "$group_name" != "$last_group" ]]; then
            echo -e "\033[35m$(printf '├─ %s ─────────────────────────────────────────────────────' "$group_name")\033[0m"
            last_group="$group_name"
        fi
        
        local password_status=""
        if [[ -z "${password}" ]]; then
            password_status="\033[31m(需输入密码)\033[0m"
        else
            password_status="\033[32m(密码已配置)\033[0m"
        fi
        
        printf "\033[33m%2d\033[0m  %-${servername_max_length}s  \033[36m%-${username_max_length}s@%-15s\033[0m  %-${group_max_length}s  %s\n" \
            $((i+1)) "$server_name" "$username" "$host" "$group_name" "$password_status"
    done
    
    echo -e "\033[34m$(printf '%.0s─' {1..70})\033[0m"
    echo -e "输入 \033[31mq\033[0m 或 \033[31mquit\033[0m 退出系统"
    echo -e "输入 \033[33ml\033[0m 查看连接日志"
    echo -e "输入 \033[33ma\033[0m 添加新服务器"
    echo -e "输入 \033[33md\033[0m 删除服务器"
    echo -e "\033[34m$(printf '%.0s═' {1..70})\033[0m"
}


# 安全读取密码函数(优化版)
read_password_secure() {
    local prompt="$1"
    local password=""
    local char=""
    
    echo -n -e "$prompt"
    
    # 保存当前终端设置并禁用回显
    local stty_settings
    stty_settings=$(stty -g 2>/dev/null)
    stty -echo -icanic min 1 time 0 2>/dev/null
    
    # 逐字符读取密码
    while IFS= read -r -n1 -s char; do
        # 检测回车键(空字符) - 结束输入
        if [[ -z "$char" ]]; then
            break
        fi
        
        # 处理退格键 (127) 或删除键 (8)
        if [[ "$char" == $'\x7f' || "$char" == $'\x08' ]]; then
            if [[ -n "$password" ]]; then
                password="${password%?}"
                printf '\b \b'
            fi
        # 处理Ctrl+C (中断)
        elif [[ "$char" == $'\x03' ]]; then
            echo -e "\n\033[33m输入已取消\033[0m"
            password=""
            break
        # 处理普通字符
        else
            password+="$char"
            printf '*'
        fi
    done
    
    # 恢复终端设置
    if [[ -n "$stty_settings" ]]; then
        stty "$stty_settings" 2>/dev/null
    fi
    
    echo
    echo "$password"
}

# 密码验证函数
validate_password_input() {
    local hostname="$1"
    local username="$2"
    local port="$3"
    local max_attempts=3
    local attempt=1
    
    while [[ $attempt -le $max_attempts ]]; do
        echo -e "\n\033[36m════════ 密码认证 ($attempt/$max_attempts) ════════\033[0m"
        
        # 第一次输入密码
        local password1=$(read_password_secure "请输入SSH密码: ")
        
        # 检查是否取消输入
        if [[ -z "$password1" ]]; then
            echo -e "\033[33m密码输入已取消\033[0m"
            return 1
        fi
        
        # 密码强度基础检查
        if [[ ${#password1} -lt 4 ]]; then
            echo -e "\033[31m密码过短,请至少输入4个字符\033[0m"
            ((attempt++))
            continue
        fi
        
        # 首次尝试需要确认密码
        if [[ $attempt -eq 1 ]]; then
            echo
            local password2=$(read_password_secure "请再次输入密码确认: ")
            
            if [[ "$password1" != "$password2" ]]; then
                echo -e "\033[31m错误:两次输入的密码不匹配\033[0m"
                ((attempt++))
                continue
            fi
        fi
        
        # 测试密码有效性
        if test_password "$hostname" "$username" "$password1" "$port"; then
            echo -e "\033[32m✓ 密码验证成功\033[0m"
            echo "$password1"
            return 0
        else
            ((attempt++))
            if [[ $attempt -le $max_attempts ]]; then
                echo -e "\033[33m密码验证失败,请重试...\033[0m"
                echo -e "\033[90m提示:检查大小写和特殊字符\033[0m"
            else
                echo -e "\033[31m错误:超过最大尝试次数\033[0m"
            fi
        fi
    done
    
    return 1
}

# 测试密码有效性
test_password() {
    local host="$1"
    local user="$2"
    local pass="$3"
    local port="${4:-22}"
    
    echo -e "\033[36m测试密码有效性...\033[0m"
    
    # 使用短超时快速测试密码
    timeout 5 expect -c "
set timeout 5
log_user 0
spawn ssh -p $port -o ConnectTimeout=3 -o StrictHostKeyChecking=no $user@$host
expect {
    \"*password:\" { 
        send \"$pass\\r\"
        expect {
            \"*Permission denied*\" { exit 1 }
            \"*@*\" { send \"exit\\r\"; expect eof; exit 0 }
            timeout { exit 2 }
        }
    }
    \"*?assword:\" { 
        send \"$pass\\r\"
        expect {
            \"*Permission denied*\" { exit 1 }
            \"*@*\" { send \"exit\\r\"; expect eof; exit 0 }
            timeout { exit 2 }
        }
    }
    \"*@*\" { 
        send \"exit\\r\"
        expect eof
        exit 0
    }
    timeout { exit 2 }
}
" >/dev/null 2>&1

    local test_result=$?
    case $test_result in
        0) return 0 ;;
        1) return 1 ;;
        *) return 2 ;;
    esac
}

# 日志函数
log_message() {
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] $1" >> /tmp/ssh_login.log
}

# 测试SSH连接函数
test_ssh_connection() {
    local host="$1"
    local port="$2"
    
    echo -e "\n\033[36m测试连接 $host:$port ...\033[0m"
    
    if command -v nc &> /dev/null; then
        if nc -z -w 3 "$host" "$port" &> /dev/null; then
            echo -e "\033[32m✓ 端口 $port 开放\033[0m"
            return 0
        else
            echo -e "\033[31m✗ 无法连接到 $host:$port\033[0m"
            return 1
        fi
    else
        if timeout 3 bash -c "cat < /dev/null > /dev/tcp/$host/$port" 2>/dev/null; then
            echo -e "\033[32m✓ 端口 $port 开放\033[0m"
            return 0
        else
            echo -e "\033[31m✗ 无法连接到 $host:$port\033[0m"
            return 1
        fi
    fi
}

# 显示连接日志
show_logs() {
    echo -e "\n\033[34m================ 最近连接日志 =================\033[0m"
    if [[ -f "/tmp/ssh_login.log" ]]; then
        if [[ -s "/tmp/ssh_login.log" ]]; then
            tail -20 /tmp/ssh_login.log
        else
            echo "暂无连接日志记录"
        fi
    else
        echo "日志文件不存在"
    fi
    echo -e "\033[34m================================================\033[0m"
    read -n 1 -s -r -p "按任意键返回主菜单..."
}

# 确认退出函数
confirm_exit() {
    echo -e -n "\n\033[33m确定要退出系统吗?(y/N): \033[0m"
    read -n 1 confirm
    echo
    if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
        echo -e "\033[32m已退出登录,再见!\033[0m"
        exit 0
    else
        echo -e "\033[36m取消退出,返回主菜单...\033[0m"
        sleep 1
    fi
}

# 创建安全的expect登录脚本
create_expect_script() {
    local username="$1"
    local hostip="$2"
    local password="$3"
    local port="$4"
    
    local expect_script=$(mktemp)
    cat > "$expect_script" << EOF
#!/usr/bin/expect -f
set timeout 15
log_user 1

# 启动SSH连接
spawn ssh -p $port -o ConnectTimeout=10 -o StrictHostKeyChecking=no $username@$hostip

expect {
    "yes/no" { 
        send "yes\r"
        exp_continue 
    }
    "password:" { 
        send "$password\r"
    }
    "?assword:" { 
        send "$password\r"
    }
    "Permission denied" {
        send_user "认证失败:密码错误或权限不足\n"
        exit 1
    }
    "Could not resolve hostname" {
        send_user "错误:无法解析主机名\n"
        exit 1
    }
    "Connection refused" {
        send_user "错误:连接被拒绝(可能SSH服务未运行)\n"
        exit 1
    }
    "Connection timed out" {
        send_user "错误:连接超时\n"
        exit 1
    }
    "Network is unreachable" {
        send_user "错误:网络不可达\n"
        exit 1
    }
    timeout {
        send_user "错误:连接超时(15秒)\n"
        exit 1
    }
    eof {
        send_user "SSH连接已关闭\n"
        exit 0
    }
}

# 检查登录是否成功
expect {
    "*\$ " {
        send_user "\n成功登录到服务器(普通用户权限),按下回车开始使用。\n"
    }
    "*# " {
        send_user "\n成功登录到服务器(root管理员权限),按下回车开始使用。\n"
    }
    timeout {
        send_user "\n已成功连接到服务器\n"
    }
}

# 交互模式
interact

# 处理退出
expect eof {
    send_user "SSH会话已结束\n"
}
EOF
    echo "$expect_script"
}

# 主循环
while true; do
    show_menu
    read -p "请输入要登录的服务器编号: " choice
    
    # 检查退出条件
    if [[ "$choice" == "q" ]] || [[ "$choice" == "quit" ]]; then
        confirm_exit
        continue
    fi
    
    # 显示日志
    if [[ "$choice" == "l" ]] || [[ "$choice" == "L" ]]; then
        show_logs
        continue
    fi
    
    # 验证输入有效性
    if ! [[ "$choice" =~ ^[0-9]+$ ]]; then
        echo -e "\033[31m错误:请输入有效的数字编号!\033[0m"
        read -n 1 -s -r -p "按任意键继续..."
        continue
    fi
    
    if [[ $choice -lt 1 ]] || [[ $choice -gt ${#GROUPED_SERVER_LIST[@]} ]]; then
        echo -e "\033[31m错误:编号必须在 1-${#GROUPED_SERVER_LIST[@]} 之间!\033[0m"
        read -n 1 -s -r -p "按任意键继续..."
        continue
    fi
    
    # 解析服务器信息
    index=$((choice-1))
    IFS='|' read -r -a server_info <<< "${GROUPED_SERVER_LIST[index]}"
    
    groupname="${server_info[0]}"      # 添加分组名称
    servername="${server_info[1]}"     # 服务器名称现在在位置1
    username="${server_info[2]}"       # 用户名在位置2
    hostip="${server_info[3]}"         # IP在位置3
    password="${server_info[4]}"       # 密码在位置4
    port="${server_info[5]:-22}"       # 端口在位置5,默认值为22
    
    # 日志记录
    log_message "用户选择登录服务器: $servername ($username@$hostip:$port)"
    
    
    # 先测试连接
    if ! test_ssh_connection "$hostip" "$port"; then
        echo -e "\n\033[31m连接测试失败,请检查以下可能原因:\033[0m"
        echo -e "1. 服务器地址是否正确: \033[36m$hostip\033[0m"
        echo -e "2. SSH端口是否开放: \033[36m$port\033[0m"
        echo -e "3. 网络是否畅通"
        echo -e "4. 防火墙设置是否正确"
        log_message "连接测试失败: $hostip:$port"
        read -n 1 -s -r -p "按任意键返回主菜单..."
        continue
    fi
    
    # 密码处理逻辑
    if [[ -z "$password" ]]; then
        echo -e "\n\033[36m════════════ 登录认证 ════════════\033[0m"
        echo -e "服务器: \033[1m$servername\033[0m"
        echo -e "连接: \033[36m$username@$hostip:$port\033[0m"
        
        password=$(validate_password_input "$hostip" "$username" "$port")
        if [[ $? -ne 0 ]]; then
            read -n 1 -s -r -p "按任意键返回主菜单..."
            continue
        fi
    else
        echo -e "\n\033[32m✓ 使用预设密码连接\033[0m"
        # 即使有预设密码也验证一下
        if ! test_password "$hostip" "$username" "$password" "$port"; then
            echo -e "\033[33m预设密码可能已失效,请手动输入新密码:\033[0m"
            password=$(validate_password_input "$hostip" "$username" "$port")
            if [[ $? -ne 0 ]]; then
                read -n 1 -s -r -p "按任意键返回主菜单..."
                continue
            fi
        fi
    fi
    
    # 显示登录信息
    echo -e "\n\033[32m════════════ 开始登录 ════════════\033[0m"
    echo -e "服务器名称: \033[36m$servername\033[0m"
    echo -e "服务器地址: \033[36m$hostip:$port\033[0m"
    echo -e "用户账号: \033[36m$username\033[0m"
    echo -e "\033[33m正在建立SSH连接,请稍候...\033[0m"
    echo -e "\033[90m提示:按 Ctrl+C 可终止连接\033[0m\n"
    
    # 记录登录尝试
    log_message "开始SSH登录尝试: $username@$hostip:$port"
    
    # 创建并执行expect脚本
    expect_script=$(create_expect_script "$username" "$hostip" "$password" "$port")
    
    # 执行expect脚本
    expect -f "$expect_script"
    local expect_status=$?
    
    # 清理临时文件
    rm -f "$expect_script"
    
    # 清理密码变量
    unset password
    
    echo -e "\n\033[34m════════════ 连接总结 ════════════\033[0m"
    
    # 根据退出状态显示不同消息
    case $expect_status in
        0)
            echo -e "\033[32m✓ SSH连接正常结束\033[0m"
            log_message "SSH连接正常结束: $username@$hostip"
            ;;
        1)
            echo -e "\033[31m✗ SSH连接失败\033[0m"
            echo -e "可能的原因:"
            echo -e "  • 密码错误"
            echo -e "  • 用户名不正确" 
            echo -e "  • 服务器拒绝连接"
            log_message "SSH连接失败: $username@$hostip (错误码: $expect_status)"
            ;;
        124)
            echo -e "\033[33m⚠ SSH连接超时\033[0m"
            log_message "SSH连接超时: $username@$hostip"
            ;;
        *)
            echo -e "\033[33m⚠ SSH连接异常结束 (代码: $expect_status)\033[0m"
            log_message "SSH连接异常: $username@$hostip (错误码: $expect_status)"
            ;;
    esac
    
    # 连接结束后返回菜单
    echo -e "\n\033[33m返回主菜单...\033[0m"
    echo -e "\033[34m══════════════════════════════════\033[0m"
    read -n 1 -s -r -p "按任意键继续..."
done
相关推荐
EveryPossible2 小时前
工作流练习
服务器·python·缓存
云服务器租用费用2 小时前
2026年零基础部署OpenClaw(前身为Clawdbot)+接入微信保姆级教程
服务器·人工智能·云原生·飞书·京东云
西柚云2 小时前
告别命令行!在VSCode中直接使用Claude Code编程
服务器·ide·vscode·编辑器·claude
Linux运维技术栈2 小时前
禅道一键包:跨服务器迁移 + 迁移至LVM分区 实战运维笔记
运维·服务器·禅道
一匹电信狗2 小时前
【Linux我做主】从 fopen 到 open:Linux 文件 I/O 的本质与内核视角
linux·运维·服务器·c++·ubuntu·小程序·开源
WW、forever2 小时前
【服务器-R环境配置】导出配置文件并重建
运维·服务器·r语言
之歆2 小时前
Linux 启动流程、GRUB、init、SysV 服务与内核初步
linux·运维·服务器
2501_915921432 小时前
Fastlane 结合 AppUploader 来实现 CI 集成自动化上架
android·运维·ci/cd·小程序·uni-app·自动化·iphone
倚肆2 小时前
Kafka TopicBuilder 主题配置详解
java·服务器·kafka