#!/bin/bash # mssh - SSH 管理工具 # 配置文件路径 CONFIG_FILE="$HOME/.mssh.conf" # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' ORANGE='\033[0;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' NC='\033[0m' # Emoji 定义 SERVER_EMOJI="🌐" KEY_EMOJI="🔑" PASSWORD_EMOJI="🔒" CONNECT_EMOJI="🔌" SUCCESS_EMOJI="✅" ERROR_EMOJI="❌" WARNING_EMOJI="⚠️" INFO_EMOJI="𝒊" FORWARD_EMOJI="📡" # 信号处理 graceful_exit() { echo -e "\n${INFO_EMOJI} ${CYAN} 正在退出 mssh...${NC}" # 检查是否有活跃的端口转发 local active_forwards active_forwards=$(get_section "active_forwards") if [ -n "$active_forwards" ]; then echo -e "${WARNING_EMOJI} ${YELLOW}检测到活跃的端口转发进程${NC}" echo -e "${INFO_EMOJI} ${CYAN}这些进程将继续在后台运行${NC}" echo -e "${CYAN}您可以随时重新启动 mssh 来管理它们${NC}" echo # 显示活跃的转发 local i=1 while IFS='|' read -r rule_name local_port pid user_host port server_alias remote_host remote_port; do [ -z "$rule_name" ] && continue printf " ${GREEN}%2d.${NC} ${FORWARD_EMOJI} ${YELLOW}%s${NC} (PID:%s)\n" "$i" "$rule_name" "$pid" ((i++)) done <<< "$active_forwards" fi echo -e "\n🔚 脚本已退出" exit 0 } # 设置信号处理 trap graceful_exit SIGINT SIGTERM SIGQUIT # 密码加密函数 encrypt_password() { local password="$1" [ -z "$password" ] && return # 简单的字符位移 + base64 local encrypted encrypted=$(echo "$password" | tr 'A-Za-z0-9' 'N-ZA-Mn-za-m5-90-4' | base64 -w 0) echo "ENC:$encrypted" } # 密码解密函数 decrypt_password() { local encrypted="$1" [ -z "$encrypted" ] && return # 解密格式:ENC:base64data if [[ "$encrypted" =~ ^ENC: ]]; then local encrypted_data="${encrypted#ENC:}" echo "$encrypted_data" | base64 -d | tr 'N-ZA-Mn-za-m5-90-4' 'A-Za-z0-9' fi } # 安全读取函数 safe_read() { local prompt="$1" local var_name="$2" local response if ! read -r -p "$prompt" response; then graceful_exit fi if [ -n "$var_name" ]; then eval "$var_name=\"$response\"" fi } # 检查 sshpass check_sshpass() { if ! command -v sshpass >/dev/null 2>&1; then echo -e "${ERROR_EMOJI} ${RED}未找到 sshpass。某些功能可能不可用。${NC}" echo -e "${INFO_EMOJI} ${YELLOW}可以运行以下命令安装:${NC}" echo -e " ${CYAN}Ubuntu/Debian: sudo apt install sshpass${NC}" echo -e " ${CYAN}CentOS/RHEL: sudo yum install sshpass${NC}" fi } # 初始化配置文件 init_config() { if [ ! -f "$CONFIG_FILE" ]; then cat > "$CONFIG_FILE" << 'EOF' # mssh 配置文件 # 格式说明: # [servers] 部分:alias|user@host|port|keypath|encrypted_password # [port_forwards] 部分:server_alias|rule_name|local_port|remote_host|remote_port|user@host|port|keypath|encrypted_password # [active_forwards] 部分:rule_name|local_port|pid|user@host|port|server_alias|remote_host|remote_port [servers] [port_forwards] [active_forwards] EOF echo -e "${SUCCESS_EMOJI} ${GREEN}配置文件已创建: $CONFIG_FILE${NC}" fi } # 获取配置文件中的特定部分 get_section() { local section="$1" local config_file="${2:-$CONFIG_FILE}" awk -v section="[$section]" ' $0 == section { in_section = 1; next } /^\[.*\]$/ { in_section = 0; next } in_section && NF > 0 && !/^#/ { print } ' "$config_file" } # 格式化端口转发显示 format_forward_display() { local num="$1" local rule_name="$2" local local_port="$3" local server_alias="$4" local remote_host="$5" local remote_port="$6" local pid="$7" local pid_text="" [ -n "$pid" ] && pid_text=" PID:$pid" if [ "$remote_host" = "127.0.0.1" ]; then printf " ${GREEN}%2d.${NC} ${FORWARD_EMOJI} ${YELLOW}%s${NC} (本地:%s -> %s:%s)%s\n" \ "$num" "$rule_name" "$local_port" "$server_alias" "$remote_port" "$pid_text" else printf " ${GREEN}%2d.${NC} ${FORWARD_EMOJI} ${YELLOW}%s${NC} (本地:%s -> %s -> %s:%s)%s\n" \ "$num" "$rule_name" "$local_port" "$server_alias" "$remote_host" "$remote_port" "$pid_text" fi } # 显示服务器列表 show_servers() { echo -e "${CYAN}已保存的服务器:${NC}" echo -e "${CYAN}---------------${NC}" local servers servers=$(get_section "servers") if [ -z "$servers" ]; then echo -e " ${YELLOW}暂无服务器${NC}" return 0 fi local i=1 while IFS='|' read -r alias_name user_host port key_path password; do [ -z "$alias_name" ] && continue # 显示密码和密钥状态 local has_password="" local has_key="" [ -n "$password" ] && has_password="${PASSWORD_EMOJI}" [ -n "$key_path" ] && [ -f "$key_path" ] && has_key="${KEY_EMOJI}" printf " ${GREEN}%2d.${NC} ${SERVER_EMOJI} ${YELLOW}%s${NC} (%s:%s) %s%s\n" \ "$i" "$alias_name" "$user_host" "$port" "$has_password" "$has_key" ((i++)) done <<< "$servers" return $((i-1)) } # 显示活跃的端口转发 show_active_forwards() { local active_forwards active_forwards=$(get_section "active_forwards") if [ -n "$active_forwards" ]; then echo echo -e "${CYAN}已启用的端口转发:${NC}" echo -e "${CYAN}------------------${NC}" local i=1 while IFS='|' read -r rule_name local_port pid user_host port server_alias remote_host remote_port; do [ -z "$rule_name" ] && continue format_forward_display "$i" "$rule_name" "$local_port" "$server_alias" "$remote_host" "$remote_port" "$pid" ((i++)) done <<< "$active_forwards" return 0 else return 1 fi } # 检查并清理无效的端口转发进程 check_and_cleanup_forwards() { local active_forwards active_forwards=$(get_section "active_forwards") if [ -z "$active_forwards" ]; then return 0 fi local temp_file temp_file=$(mktemp) local cleanup_needed=false local cleaned_count=0 # 复制配置文件到临时文件 cp "$CONFIG_FILE" "$temp_file" while IFS='|' read -r rule_name local_port pid user_host port server_alias remote_host remote_port; do [ -z "$rule_name" ] && continue # 检查进程是否存在 if ! kill -0 "$pid" 2>/dev/null; then # 进程不存在,从配置中移除 local rule_line="${rule_name}|${local_port}|${pid}|${user_host}|${port}|${server_alias}|${remote_host}|${remote_port}" awk -v target_line="$rule_line" ' $0 == target_line && in_active { next } /^\[active_forwards\]$/ { in_active=1 } /^\[.*\]$/ && !/^\[active_forwards\]$/ { in_active=0 } { print } ' "$temp_file" > "${temp_file}.new" && mv "${temp_file}.new" "$temp_file" cleanup_needed=true ((cleaned_count++)) fi done <<< "$active_forwards" # 如果有清理,更新配置文件并显示信息 if [ "$cleanup_needed" = true ]; then mv "$temp_file" "$CONFIG_FILE" if [ "$cleaned_count" -gt 0 ]; then echo -e "${INFO_EMOJI} ${YELLOW}已清理 $cleaned_count 个失效的端口转发进程${NC}" sleep 1 fi else rm -f "$temp_file" fi } # 主菜单 main_menu() { # 在显示菜单前检查并清理无效的端口转发进程 check_and_cleanup_forwards clear echo -e "${PURPLE}==========================================${NC}" echo -e "${PURPLE}:: mssh - SSH 管理工具 ::${NC}" echo -e "${PURPLE}==========================================${NC}" echo show_servers show_active_forwards local has_active_forwards=$? echo echo -e "${BLUE}选项分区:${NC}" echo -e "${BLUE}---------${NC}" echo -e " ${GREEN}[a] 增加服务器${NC}" echo -e " ${RED}[d] 删除服务器${NC}" echo -e " ${BLUE}[p] 端口转发${NC}" echo -e " ${CYAN}[q] 退出${NC}" # 显示端口转发持久化提示 if [ $has_active_forwards -eq 0 ]; then echo echo -e "${INFO_EMOJI} ${YELLOW}提示:端口转发将在退出程序后继续运行${NC}" fi echo echo -ne "${GREEN}请选择操作: ${NC}" } # 添加服务器 add_server() { while true; do clear echo -e "${PURPLE}============ 添加服务器 ============${NC}" echo echo -e "${INFO_EMOJI} ${CYAN}添加新服务器 (0或回车返回)${NC}" echo safe_read "$(echo -e "${CYAN}输入别名: ${NC}")" alias_name; [ "$alias_name" = "0" ] || [ -z "$alias_name" ] && return safe_read "$(echo -e "${CYAN}输入用户名@主机: ${NC}")" user_host; [ "$user_host" = "0" ] || [ -z "$user_host" ] && return safe_read "$(echo -e "${CYAN}输入端口 (默认22): ${NC}")" port; [ "$port" = "0" ] && return; port=${port:-22} echo -ne "${CYAN}输入密码 (可选): ${NC}" read -r -s password echo safe_read "$(echo -e "${CYAN}输入私钥路径 (可选): ${NC}")" key_path; [ "$key_path" = "0" ] && return if [ -n "$key_path" ] && [ ! -f "$key_path" ]; then echo -e "${ERROR_EMOJI} ${RED}密钥文件不存在${NC}" safe_read "$(echo -e "${YELLOW}是否继续? (y/N): ${NC}")" continue_without_key if [[ ! "${continue_without_key:-n}" =~ [yY] ]]; then continue fi key_path="" fi # 加密密码 [ -n "$password" ] && password=$(encrypt_password "$password") # 添加到配置文件 local temp_file temp_file=$(mktemp) awk -v new_server="${alias_name}|${user_host}|${port}|${key_path}|${password}" ' /^\[servers\]$/ { print; in_servers=1; next } /^\[.*\]$/ { in_servers=0 } in_servers && /^$/ { print new_server; added=1 } { print } END { if (!added && in_servers) print new_server } ' "$CONFIG_FILE" > "$temp_file" && mv "$temp_file" "$CONFIG_FILE" echo -e "${SUCCESS_EMOJI} ${GREEN}服务器已添加: $alias_name${NC}" echo safe_read "$(echo -e "${YELLOW}是否继续添加? (y/N): ${NC}")" continue_add if [[ ! "${continue_add:-n}" =~ [yY] ]]; then break fi done } # 删除服务器 delete_server() { while true; do clear echo -e "${PURPLE}============ 删除服务器 ============${NC}" echo echo -e "${WARNING_EMOJI} ${YELLOW}删除服务器${NC}" echo show_servers echo safe_read "$(echo -e "${CYAN}选择要删除的服务器编号 (0或回车返回): ${NC}")" choice [ "$choice" = "0" ] || [ -z "$choice" ] && return local servers servers=$(get_section "servers") local server_line server_line=$(echo "$servers" | sed -n "${choice}p") if [ -n "$server_line" ]; then local alias_name alias_name=$(echo "$server_line" | cut -d'|' -f1) echo -e "${WARNING_EMOJI} ${YELLOW}确认删除服务器: $alias_name${NC}" local temp_file temp_file=$(mktemp) awk -v target_line="$server_line" ' $0 == target_line && in_servers { next } /^\[servers\]$/ { in_servers=1 } /^\[.*\]$/ && !/^\[servers\]$/ { in_servers=0 } { print } ' "$CONFIG_FILE" > "$temp_file" && mv "$temp_file" "$CONFIG_FILE" echo -e "${SUCCESS_EMOJI} ${GREEN}服务器已删除: $alias_name${NC}" else echo -e "${ERROR_EMOJI} ${RED}无效的服务器编号!${NC}" fi echo safe_read "$(echo -e "${YELLOW}是否继续删除? (y/N): ${NC}")" continue_del if [[ ! "${continue_del:-n}" =~ [yY] ]]; then break fi done } # 添加端口转发规则 add_port_forward_rule() { echo -e "${INFO_EMOJI} ${CYAN}添加新端口转发规则 (0或回车返回):${NC}" safe_read "$(echo -e "${CYAN}输入规则名称: ${NC}")" rule_name if [ "$rule_name" = "0" ] || [ -z "$rule_name" ]; then return 1 fi safe_read "$(echo -e "${CYAN}输入本地端口: ${NC}")" local_port if [ "$local_port" = "0" ] || [ -z "$local_port" ]; then return 1 fi safe_read "$(echo -e "${CYAN}输入远程主机 (默认127.0.0.1): ${NC}")" remote_host if [ "$remote_host" = "0" ]; then return 1 fi remote_host=${remote_host:-127.0.0.1} safe_read "$(echo -e "${CYAN}输入远程端口 (默认${local_port}): ${NC}")" remote_port if [ "$remote_port" = "0" ]; then return 1 fi remote_port=${remote_port:-$local_port} echo -e "\n${CYAN}请选择要使用的服务器:${NC}" show_servers echo safe_read "$(echo -e "${CYAN}选择服务器编号: ${NC}")" server_choice if [ "$server_choice" = "0" ] || [ -z "$server_choice" ]; then return 1 fi local servers servers=$(get_section "servers") local selected_server selected_server=$(echo "$servers" | sed -n "${server_choice}p") if [ -n "$selected_server" ]; then IFS='|' read -r server_alias user_host port key_path password <<< "$selected_server" local temp_file temp_file=$(mktemp) awk -v new_rule="${server_alias}|${rule_name}|${local_port}|${remote_host}|${remote_port}|${user_host}|${port}|${key_path}|${password}" ' /^\[port_forwards\]$/ { print; in_forwards=1; next } /^\[.*\]$/ { in_forwards=0 } in_forwards && /^$/ { print new_rule; added=1 } { print } END { if (!added && in_forwards) print new_rule } ' "$CONFIG_FILE" > "$temp_file" && mv "$temp_file" "$CONFIG_FILE" echo -e "${SUCCESS_EMOJI} ${GREEN}端口转发规则已添加: $rule_name${NC}" return 0 else echo -e "${ERROR_EMOJI} ${RED}无效的服务器编号!${NC}" return 1 fi } # 删除端口转发规则 delete_port_forward() { local rule_number="$1" local forwards forwards=$(get_section "port_forwards") local rule_line rule_line=$(echo "$forwards" | sed -n "${rule_number}p") if [ -n "$rule_line" ]; then local rule_name rule_name=$(echo "$rule_line" | cut -d'|' -f2) local temp_file temp_file=$(mktemp) awk -v target_line="$rule_line" ' $0 == target_line && in_forwards { next } /^\[port_forwards\]$/ { in_forwards=1 } /^\[.*\]$/ && !/^\[port_forwards\]$/ { in_forwards=0 } { print } ' "$CONFIG_FILE" > "$temp_file" && mv "$temp_file" "$CONFIG_FILE" echo -e "${SUCCESS_EMOJI} ${GREEN}规则已删除: $rule_name${NC}" else echo -e "${ERROR_EMOJI} ${RED}无效的规则编号!${NC}" fi } # 停止后台端口转发 stop_background_forward() { local rule_number="$1" local active_forwards active_forwards=$(get_section "active_forwards") local active_line active_line=$(echo "$active_forwards" | sed -n "${rule_number}p") if [ -n "$active_line" ]; then IFS='|' read -r rule_name local_port pid user_host port server_alias remote_host remote_port <<< "$active_line" if kill "$pid" 2>/dev/null; then echo -e "${SUCCESS_EMOJI} ${GREEN}端口转发已停止: $rule_name (PID: $pid)${NC}" else echo -e "${WARNING_EMOJI} ${YELLOW}进程可能已经停止: $rule_name (PID: $pid)${NC}" fi # 从活跃转发列表中移除 local temp_file temp_file=$(mktemp) awk -v target_line="$active_line" ' $0 == target_line && in_active { next } /^\[active_forwards\]$/ { in_active=1 } /^\[.*\]$/ && !/^\[active_forwards\]$/ { in_active=0 } { print } ' "$CONFIG_FILE" > "$temp_file" && mv "$temp_file" "$CONFIG_FILE" else echo -e "${ERROR_EMOJI} ${RED}无效的活跃转发编号!${NC}" fi } # 启动后台端口转发 start_background_forward() { local rule_number="$1" local forwards forwards=$(get_section "port_forwards") local rule_line rule_line=$(echo "$forwards" | sed -n "${rule_number}p") if [ -n "$rule_line" ]; then IFS='|' read -r server_alias rule_name local_port remote_host remote_port user_host port key_path password <<< "$rule_line" # 解密密码 [ -n "$password" ] && password=$(decrypt_password "$password") # 构建 SSH 命令 local ssh_args=("-p" "$port" "-L" "${local_port}:${remote_host}:${remote_port}" "-N" "$user_host") [ -n "$key_path" ] && ssh_args+=("-i" "$key_path") # 启动后台进程 (使用密码或密钥认证) local ssh_pid if [ -n "$password" ] && command -v sshpass >/dev/null 2>&1; then echo -e "${INFO_EMOJI} ${YELLOW}使用保存的密码启动端口转发...${NC}" nohup sshpass -p "$password" ssh "${ssh_args[@]}" >/dev/null 2>&1 & ssh_pid=$! disown $ssh_pid 2>/dev/null # 等待SSH连接建立 sleep 1 # 检查进程是否仍在运行 if ! kill -0 "$ssh_pid" 2>/dev/null; then echo -e "${ERROR_EMOJI} ${RED}端口转发启动失败,请检查网络连接和认证信息${NC}" return 1 fi else echo -e "${INFO_EMOJI} ${YELLOW}启动端口转发 (需要手动输入密码)...${NC}" nohup ssh "${ssh_args[@]}" /dev/null 2>&1 & ssh_pid=$! disown $ssh_pid 2>/dev/null # 给用户一些时间输入密码 echo -e "${INFO_EMOJI} ${CYAN}请在上方输入SSH密码...${NC}" sleep 3 # 检查进程是否仍在运行 if ! kill -0 "$ssh_pid" 2>/dev/null; then echo -e "${ERROR_EMOJI} ${RED}端口转发启动失败,请检查网络连接和认证信息${NC}" return 1 fi fi local temp_file temp_file=$(mktemp) awk -v new_active="${rule_name}|${local_port}|${ssh_pid}|${user_host}|${port}|${server_alias}|${remote_host}|${remote_port}" ' /^\[active_forwards\]$/ { print; in_active=1; next } /^\[.*\]$/ { in_active=0 } in_active && /^$/ { print new_active; added=1 } { print } END { if (!added && in_active) print new_active } ' "$CONFIG_FILE" > "$temp_file" && mv "$temp_file" "$CONFIG_FILE" echo -e "${SUCCESS_EMOJI} ${GREEN}端口转发已启动: $rule_name (PID: $ssh_pid)${NC}" else echo -e "${ERROR_EMOJI} ${RED}无效的规则编号!${NC}" fi } # 端口转发管理 list_port_forwards() { while true; do clear echo -e "${PURPLE}=========== 端口转发管理 ===========${NC}" echo local forwards forwards=$(get_section "port_forwards") if [ -z "$forwards" ]; then echo -e "${CYAN}已配置的端口转发规则:${NC}" echo -e "${CYAN}------------------------${NC}" echo -e " ${YELLOW}暂无端口转发规则${NC}" else echo -e "${CYAN}已配置的端口转发规则:${NC}" echo -e "${CYAN}------------------------${NC}" local i=1 while IFS='|' read -r server_alias rule_name local_port remote_host remote_port _ _ _ _ || [ -n "$rule_name" ]; do [ -z "$rule_name" ] && continue format_forward_display "$i" "$rule_name" "$local_port" "$server_alias" "$remote_host" "$remote_port" ((i++)) done <<< "$forwards" fi show_active_forwards echo echo -e "${BLUE}操作选项:${NC}" echo -e "${BLUE}---------${NC}" echo -e " ${GREEN}[1] 添加端口转发${NC}" echo -e " ${YELLOW}[2] 启动端口转发${NC}" echo -e " ${ORANGE}[3] 停止端口转发${NC}" echo -e " ${RED}[4] 删除转发规则${NC}" echo safe_read "$(echo -e "${CYAN}请选择操作 (0或回车返回): ${NC}")" choice case $choice in 0|"") return ;; 1) echo if add_port_forward_rule; then safe_read "按回车键继续..." pause_key fi ;; 2) if [ -z "$forwards" ]; then echo -e "${ERROR_EMOJI} ${RED}暂无端口转发规则可启动!${NC}" safe_read "按回车键继续..." pause_key else safe_read "$(echo -e "${CYAN}输入要启动的规则编号: ${NC}")" start_choice if [ -n "$start_choice" ]; then start_background_forward "$start_choice" fi safe_read "按回车键继续..." pause_key fi ;; 3) local active_forwards active_forwards=$(get_section "active_forwards") if [ -z "$active_forwards" ]; then echo -e "${ERROR_EMOJI} ${RED}暂无活跃的端口转发可停止!${NC}" safe_read "按回车键继续..." pause_key else echo echo -e "${CYAN}当前活跃的端口转发:${NC}" local i=1 while IFS='|' read -r rule_name local_port pid user_host port server_alias remote_host remote_port; do [ -z "$rule_name" ] && continue format_forward_display "$i" "$rule_name" "$local_port" "$server_alias" "$remote_host" "$remote_port" "$pid" ((i++)) done <<< "$active_forwards" echo safe_read "$(echo -e "${CYAN}输入要停止的端口转发编号: ${NC}")" stop_choice if [ -n "$stop_choice" ]; then stop_background_forward "$stop_choice" fi safe_read "按回车键继续..." pause_key fi ;; 4) if [ -z "$forwards" ]; then echo -e "${ERROR_EMOJI} ${RED}暂无端口转发规则可删除!${NC}" safe_read "按回车键继续..." pause_key else safe_read "$(echo -e "${CYAN}输入要删除的规则编号: ${NC}")" del_choice if [ -n "$del_choice" ]; then delete_port_forward "$del_choice" fi safe_read "按回车键继续..." pause_key fi ;; *) echo -e "${ERROR_EMOJI} ${RED}无效选项!${NC}"; sleep 1 ;; esac done } # 连接服务器 connect_server() { local choice=$1 local servers servers=$(get_section "servers") local selected_server selected_server=$(echo "$servers" | sed -n "${choice}p") if [ -n "$selected_server" ]; then IFS='|' read -r alias_name user_host port key_path password <<< "$selected_server" # 解密密码 [ -n "$password" ] && password=$(decrypt_password "$password") echo -e "${CONNECT_EMOJI} ${GREEN}连接到: $alias_name ($user_host:$port)${NC}" local ssh_args=("-p" "$port" "$user_host") [ -n "$key_path" ] && ssh_args+=("-i" "$key_path") if [ -n "$password" ] && command -v sshpass >/dev/null 2>&1; then echo -e "${WARNING_EMOJI} ${YELLOW}使用保存的密码连接...${NC}" sshpass -p "$password" ssh "${ssh_args[@]}" else ssh "${ssh_args[@]}" fi else echo -e "${ERROR_EMOJI} ${RED}无效的服务器编号!${NC}" sleep 1 fi } # 主程序 check_sshpass init_config while true; do main_menu safe_read "" choice case $choice in [0-9]*) if [[ "$choice" =~ ^[0-9]+$ ]]; then connect_server "$choice" else echo -e "${ERROR_EMOJI} ${RED}无效的服务器编号!${NC}" sleep 1 fi ;; a|A) add_server ;; d|D) delete_server ;; p|P) list_port_forwards ;; q|Q) graceful_exit ;; *) echo -e "${ERROR_EMOJI} ${RED}无效选项!${NC}"; sleep 1 ;; esac done