#!/bin/bash set -eo pipefail # ================================================================= # fishtools (咸鱼工具箱) v1.0 # Author: 咸鱼银河 (Xianyu Yinhe) # Github: https://github.com/qqzhoufan/fishtools # # A powerful and modular toolkit for VPS management. # ================================================================= # --- 全局配置 --- AUTHOR_GITHUB_USER="qqzhoufan" MAIN_REPO_NAME="fishtools" VERSION="v1.2" SCRIPT_PATH="$(realpath "$0" 2>/dev/null || echo "$0")" # --- 依赖检查 --- check_dependencies() { local missing_deps=() local optional_deps=() # 必须依赖 for cmd in curl; do if ! command -v "$cmd" &>/dev/null; then missing_deps+=("$cmd") fi done # 可选依赖(用于特定功能) for cmd in bc jq dig; do if ! command -v "$cmd" &>/dev/null; then optional_deps+=("$cmd") fi done # 如果缺少必须依赖,尝试安装 if [[ ${#missing_deps[@]} -gt 0 ]]; then echo -e "${YELLOW} ⚠ 检测到缺少必要依赖: ${missing_deps[*]}${NC}" echo -e "${CYAN} ℹ 正在尝试自动安装...${NC}" if command -v apt-get &>/dev/null; then sudo apt-get update -qq && sudo apt-get install -y "${missing_deps[@]}" 2>/dev/null elif command -v yum &>/dev/null; then sudo yum install -y "${missing_deps[@]}" 2>/dev/null elif command -v dnf &>/dev/null; then sudo dnf install -y "${missing_deps[@]}" 2>/dev/null fi fi # 可选依赖提示 if [[ ${#optional_deps[@]} -gt 0 ]]; then : # 静默处理,不影响正常使用 fi } # --- 更新检查 --- check_update() { local remote_version remote_version=$(curl -s --max-time 3 "https://raw.githubusercontent.com/${AUTHOR_GITHUB_USER}/${MAIN_REPO_NAME}/main/fishtools.sh" 2>/dev/null | grep -oP 'VERSION="v\K[0-9.]+' | head -1) local current_version="${VERSION#v}" if [[ -n "$remote_version" && "$remote_version" != "$current_version" ]]; then echo "" echo -e "${YELLOW} ╭───────────────────────────────────────────╮${NC}" echo -e "${YELLOW} │${NC} ${WHITE}${BOLD}发现新版本 ${GREEN}v${remote_version}${NC} ${DIM}(当前 ${VERSION})${NC} ${YELLOW}│${NC}" echo -e "${YELLOW} │${NC} 运行以下命令更新: ${YELLOW}│${NC}" echo -e "${YELLOW} │${NC} ${CYAN}curl -sL bit.ly/fishtools | bash${NC} ${YELLOW}│${NC}" echo -e "${YELLOW} ╰───────────────────────────────────────────╯${NC}" echo "" fi } # --- 帮助信息 --- show_help() { echo "" echo -e "${CYAN}fishtools${NC} - 咸鱼工具箱 ${VERSION}" echo "" echo -e "${WHITE}用法:${NC}" echo " fish [选项] # 安装后可直接使用" echo " ./fishtools.sh [选项] # 或直接运行脚本" echo "" echo -e "${WHITE}选项:${NC}" echo " -h, --help 显示帮助信息" echo " -v, --version 显示版本信息" echo " -u, --update 检查并更新脚本" echo " --install 安装 fish 命令到系统" echo " --uninstall 卸载 fish 命令" echo " --info 显示系统信息" echo " --bbr 一键开启 BBR" echo " --docker 进入 Docker 管理" echo " --test 进入性能测试菜单" echo "" echo -e "${WHITE}示例:${NC}" echo " fish --info # 快速查看系统信息" echo " fish --bbr # 一键开启 BBR" echo "" echo -e "${WHITE}首次安装:${NC}" echo " ./fishtools.sh --install # 安装后即可使用 fish 命令" echo "" } # --- 命令行参数处理 --- handle_args() { case "$1" in -h|--help) show_help exit 0 ;; -v|--version) echo "fishtools ${VERSION}" exit 0 ;; -u|--update) echo -e "${CYAN} ℹ 正在检查更新...${NC}" local tmp_file="/tmp/fishtools_new.sh" if curl -sL "https://raw.githubusercontent.com/${AUTHOR_GITHUB_USER}/${MAIN_REPO_NAME}/main/fishtools.sh" -o "$tmp_file" 2>/dev/null; then local remote_ver=$(grep -oP 'VERSION="v\K[0-9.]+' "$tmp_file" | head -1) local current_ver="${VERSION#v}" if [[ "$remote_ver" != "$current_ver" ]]; then echo -e "${GREEN} ✓ 发现新版本 v${remote_ver},正在更新...${NC}" chmod +x "$tmp_file" mv "$tmp_file" "$SCRIPT_PATH" echo -e "${GREEN} ✓ 更新完成!请重新运行脚本。${NC}" else echo -e "${GREEN} ✓ 已是最新版本 ${VERSION}${NC}" rm -f "$tmp_file" fi else echo -e "${RED} ✗ 检查更新失败${NC}" fi exit 0 ;; --info) show_machine_info exit 0 ;; --bbr) echo -e "${CYAN} ℹ 正在开启 BBR...${NC}" if grep -q "net.core.default_qdisc" /etc/sysctl.conf 2>/dev/null; then sudo sed -i 's/net.core.default_qdisc.*/net.core.default_qdisc=fq/' /etc/sysctl.conf else echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf >/dev/null fi if grep -q "net.ipv4.tcp_congestion_control" /etc/sysctl.conf 2>/dev/null; then sudo sed -i 's/net.ipv4.tcp_congestion_control.*/net.ipv4.tcp_congestion_control=bbr/' /etc/sysctl.conf else echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf >/dev/null fi sudo sysctl -p >/dev/null 2>&1 if sysctl net.ipv4.tcp_congestion_control 2>/dev/null | grep -q bbr; then echo -e "${GREEN} ✓ BBR 已成功开启!${NC}" else echo -e "${RED} ✗ BBR 开启失败,请检查内核版本${NC}" fi exit 0 ;; --docker) check_dependencies install_docker_menu exit 0 ;; --test) check_dependencies show_test_menu exit 0 ;; --install) echo "" echo -e "${CYAN} ℹ 正在安装 fish 命令...${NC}" local install_path="/usr/local/bin/fish" # 复制脚本到目标位置 if sudo cp "$SCRIPT_PATH" "$install_path" && sudo chmod +x "$install_path"; then echo -e "${GREEN} ✓ 安装成功!${NC}" echo "" echo -e " 现在可以使用以下命令:" echo -e " ${CYAN}fish${NC} # 启动工具箱" echo -e " ${CYAN}fish --help${NC} # 查看帮助" echo -e " ${CYAN}fish --info${NC} # 查看系统信息" echo -e " ${CYAN}fish --bbr${NC} # 一键开启 BBR" echo "" else echo -e "${RED} ✗ 安装失败,请使用 sudo 运行${NC}" fi exit 0 ;; --uninstall) echo "" echo -e "${CYAN} ℹ 正在卸载 fish 命令...${NC}" local removed=0 if [[ -f "/usr/local/bin/fish" ]]; then sudo rm -f "/usr/local/bin/fish" && removed=1 fi if [[ -f "/usr/local/bin/fishtool" ]]; then sudo rm -f "/usr/local/bin/fishtool" && removed=1 fi if [[ $removed -eq 1 ]]; then echo -e "${GREEN} ✓ 卸载成功!${NC}" else echo -e "${YELLOW} ⚠ 未找到已安装的命令${NC}" fi exit 0 ;; "") # 无参数,正常启动 return 0 ;; *) echo -e "${RED}未知选项: $1${NC}" echo "使用 --help 查看帮助" exit 1 ;; esac } # --- 颜色和样式定义 --- RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' MAGENTA='\033[0;35m' WHITE='\033[1;37m' GRAY='\033[0;90m' BOLD='\033[1m' DIM='\033[2m' NC='\033[0m' # --- Unicode 边框字符 --- # 使用简单的 ASCII 字符以确保兼容性 LINE_H="─" LINE_V="│" CORNER_TL="┌" CORNER_TR="┐" CORNER_BL="└" CORNER_BR="┘" T_LEFT="├" T_RIGHT="┤" # --- 基础日志函数 --- log_info() { echo -e "${CYAN} ℹ ${NC}$1" } log_success() { echo -e "${GREEN} ✓ ${NC}$1" } log_warning() { echo -e "${YELLOW} ⚠ ${NC}$1" } log_error() { echo -e "${RED} ✗ ${NC}$1" } # --- 绘制工具函数 --- # 绘制水平线 draw_line() { local width=${1:-50} local color=${2:-$CYAN} local line="" for ((i=0; i/dev/null; then virt_type=$(systemd-detect-virt 2>/dev/null || echo "未知") [[ "$virt_type" == "none" ]] && virt_type="物理机" elif [[ -f /proc/vz/veinfo ]]; then virt_type="OpenVZ" elif grep -q "hypervisor" /proc/cpuinfo 2>/dev/null; then virt_type="虚拟机" fi echo -e " ${CYAN}虚拟化${NC} │ ${virt_type}" echo "" echo -e " ${WHITE}${BOLD}系统信息${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" echo -e " ${CYAN}操作系统${NC} │ $(. /etc/os-release && echo $PRETTY_NAME)" echo -e " ${CYAN}内核版本${NC} │ $(uname -r)" echo -e " ${CYAN}运行时间${NC} │ $(uptime -p 2>/dev/null | sed 's/up //' || uptime | awk -F'up ' '{print $2}' | awk -F',' '{print $1}')" # 负载 local load_avg=$(cat /proc/loadavg | awk '{print $1, $2, $3}') echo -e " ${CYAN}系统负载${NC} │ ${load_avg} (1/5/15分钟)" echo "" echo -e " ${WHITE}${BOLD}网络信息${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" # 获取公网 IPv4 local ipv4=$(curl -s4 --connect-timeout 3 ip.sb 2>/dev/null || curl -s4 --connect-timeout 3 ifconfig.me 2>/dev/null || echo "获取失败") echo -e " ${CYAN}公网 IPv4${NC} │ ${ipv4}" # 获取公网 IPv6 local ipv6=$(curl -s6 --connect-timeout 3 ip.sb 2>/dev/null || echo "无/获取失败") echo -e " ${CYAN}公网 IPv6${NC} │ ${ipv6}" # 主机名 echo -e " ${CYAN}主机名${NC} │ $(hostname)" echo "" draw_footer 50 } show_live_performance() { clear draw_title_line "VPS 实时性能" 50 echo "" echo -e " ${WHITE}${BOLD}CPU & 内存${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" # CPU 使用率 local cpu_usage cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}') # IO 等待 local iowait iowait=$(top -bn1 | grep "Cpu(s)" | awk '{for(i=1;i<=NF;i++) if($i ~ /wa/) print $(i-1)}' | tr -d ',') [[ -z "$iowait" ]] && iowait="0.0" # CPU 使用率颜色 local cpu_color=$GREEN if (( $(echo "$cpu_usage > 70" | bc -l) )); then cpu_color=$RED elif (( $(echo "$cpu_usage > 40" | bc -l) )); then cpu_color=$YELLOW fi echo -e " ${CYAN}CPU 使用率${NC} │ ${cpu_color}${cpu_usage}%${NC} ${DIM}(IO等待: ${iowait}%)${NC}" # 内存使用 local mem_total=$(free -m | awk 'NR==2{print $2}') local mem_used=$(free -m | awk 'NR==2{print $3}') local mem_percent=$((mem_used * 100 / mem_total)) local mem_color=$GREEN if (( mem_percent > 80 )); then mem_color=$RED elif (( mem_percent > 50 )); then mem_color=$YELLOW fi echo -e " ${CYAN}内存使用${NC} │ ${mem_color}${mem_used}MB${NC} / ${mem_total}MB (${mem_color}${mem_percent}%${NC})" # SWAP 使用 local swap_total=$(free -m | awk 'NR==3{print $2}') local swap_used=$(free -m | awk 'NR==3{print $3}') if [[ "$swap_total" -gt 0 ]]; then local swap_percent=$((swap_used * 100 / swap_total)) local swap_color=$GREEN if (( swap_percent > 80 )); then swap_color=$RED elif (( swap_percent > 50 )); then swap_color=$YELLOW fi echo -e " ${CYAN}SWAP 使用${NC} │ ${swap_color}${swap_used}MB${NC} / ${swap_total}MB (${swap_color}${swap_percent}%${NC})" else echo -e " ${CYAN}SWAP 使用${NC} │ ${GRAY}未配置${NC}" fi echo "" echo -e " ${WHITE}${BOLD}磁盘 & 网络${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" # 磁盘使用 local disk_info=$(df -h / | awk 'NR==2{printf "%s / %s (%s)", $3, $2, $5}') local disk_percent=$(df -h / | awk 'NR==2{print $5}' | tr -d '%') local disk_color=$GREEN if (( disk_percent > 80 )); then disk_color=$RED elif (( disk_percent > 60 )); then disk_color=$YELLOW fi echo -e " ${CYAN}磁盘空间${NC} │ ${disk_color}${disk_info}${NC}" # 网络连接数 local tcp_conn=$(ss -t state established 2>/dev/null | wc -l) local tcp_listen=$(ss -tln 2>/dev/null | grep -c LISTEN || echo 0) echo -e " ${CYAN}网络连接${NC} │ TCP 已建立: ${tcp_conn} 监听端口: ${tcp_listen}" # 系统负载 local load_avg=$(cat /proc/loadavg | awk '{print $1, $2, $3}') echo -e " ${CYAN}系统负载${NC} │ ${load_avg}" echo "" echo -e " ${DIM}(此为快照信息,非持续刷新)${NC}" echo "" draw_footer 50 } # 网络流量监控(实时刷新) show_network_traffic() { # 获取所有活动网卡(排除 lo) local interfaces=$(ip -o link show | awk -F': ' '{print $2}' | grep -v lo | tr '\n' ' ') # 如果没有找到网卡,使用默认的 eth0 if [[ -z "$interfaces" ]]; then interfaces="eth0" fi # 获取默认网关所在的网卡(公网网卡) local default_iface=$(ip route | grep default | awk '{print $5}' | head -1) # 初始化上一次的采样数据 declare -A rx_prev tx_prev for iface in $interfaces; do rx_prev[$iface]=$(cat /proc/net/dev 2>/dev/null | grep -w "$iface" | awk '{print $2}') tx_prev[$iface]=$(cat /proc/net/dev 2>/dev/null | grep -w "$iface" | awk '{print $10}') done # 实时刷新循环 while true; do clear draw_title_line "网络流量监控 (实时)" 50 echo "" echo -e " ${WHITE}${BOLD}网卡流量统计${NC} ${DIM}(每2秒刷新,按 q 退出)${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" for iface in $interfaces; do # 获取当前数据 local rx_curr=$(cat /proc/net/dev 2>/dev/null | grep -w "$iface" | awk '{print $2}') local tx_curr=$(cat /proc/net/dev 2>/dev/null | grep -w "$iface" | awk '{print $10}') # 跳过无效数据 if [[ -z "$rx_curr" || -z "$tx_curr" || "$rx_curr" == "0" ]]; then continue fi # 获取上次数据 local rx_last=${rx_prev[$iface]:-$rx_curr} local tx_last=${tx_prev[$iface]:-$tx_curr} # 计算速率 (bytes/2s -> KB/s) local rx_diff=$((rx_curr - rx_last)) local tx_diff=$((tx_curr - tx_last)) local rx_rate=$((rx_diff / 2 / 1024)) local tx_rate=$((tx_diff / 2 / 1024)) # 更新上次数据 rx_prev[$iface]=$rx_curr tx_prev[$iface]=$tx_curr # 计算总流量 (使用 awk 进行浮点运算) local rx_total=$(awk "BEGIN {printf \"%.2f\", $rx_curr / 1024 / 1024 / 1024}") local tx_total=$(awk "BEGIN {printf \"%.2f\", $tx_curr / 1024 / 1024 / 1024}") # 判断是公网还是内网网卡 local iface_type="" if [[ "$iface" == "$default_iface" ]]; then iface_type="${MAGENTA}[公网]${NC}" else iface_type="${GRAY}[内网]${NC}" fi # 速率单位自动调整 local rx_display tx_display if [[ $rx_rate -ge 1024 ]]; then rx_display=$(awk "BEGIN {printf \"%.2f MB/s\", $rx_rate / 1024}") else rx_display="${rx_rate} KB/s" fi if [[ $tx_rate -ge 1024 ]]; then tx_display=$(awk "BEGIN {printf \"%.2f MB/s\", $tx_rate / 1024}") else tx_display="${tx_rate} KB/s" fi echo "" echo -e " ${CYAN}${BOLD}$iface${NC} $iface_type" echo -e " ${GREEN}↓ 下载${NC} ${rx_display} │ 累计 ${rx_total} GB" echo -e " ${YELLOW}↑ 上传${NC} ${tx_display} │ 累计 ${tx_total} GB" done echo "" draw_footer 50 # 等待2秒,期间检测是否按下 q 键退出 read -t 2 -n 1 key /dev/null || true if [[ "$key" == "q" || "$key" == "Q" ]]; then break fi done } # 进程管理 show_process_manager() { while true; do clear draw_title_line "进程管理" 50 echo "" # 显示CPU占用前10的进程 echo -e " ${WHITE}${BOLD}CPU 占用 TOP 10${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" echo -e " ${CYAN}PID CPU% MEM% 命令${NC}" ps aux --sort=-%cpu | head -11 | tail -10 | awk '{printf " %-8s %-6s %-6s %s\n", $2, $3, $4, $11}' echo "" echo -e " ${WHITE}${BOLD}内存 占用 TOP 10${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" echo -e " ${CYAN}PID CPU% MEM% 命令${NC}" ps aux --sort=-%mem | head -11 | tail -10 | awk '{printf " %-8s %-6s %-6s %s\n", $2, $3, $4, $11}' echo "" draw_separator 50 echo -e " ${YELLOW}输入 PID 杀死进程,或输入 0 返回${NC}" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入${NC}): " pid_input /dev/null; then log_success "进程 $pid_input 已终止" else log_error "无法终止进程 $pid_input(可能需要 sudo 权限)" fi press_any_key fi fi done } # 端口查看 show_open_ports() { clear draw_title_line "开放端口查看" 50 echo "" echo -e " ${WHITE}${BOLD}TCP 监听端口${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" echo -e " ${CYAN}端口 状态 进程${NC}" if command -v ss &>/dev/null; then ss -tlnp 2>/dev/null | grep LISTEN | awk '{ split($4, a, ":") port = a[length(a)] proc = $6 gsub(/users:\(\("/, "", proc) gsub(/".*/, "", proc) if (proc == "") proc = "-" printf " %-10s %-10s %s\n", port, "LISTEN", proc }' | sort -t' ' -k1 -n | uniq || true else netstat -tlnp 2>/dev/null | grep LISTEN | awk '{ split($4, a, ":") port = a[length(a)] proc = $7 printf " %-10s %-10s %s\n", port, "LISTEN", proc }' | sort -t' ' -k1 -n | uniq || true fi echo "" echo -e " ${WHITE}${BOLD}UDP 监听端口${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" if command -v ss &>/dev/null; then ss -ulnp 2>/dev/null | grep -v "State" | awk '{ split($4, a, ":") port = a[length(a)] proc = $6 gsub(/users:\(\("/, "", proc) gsub(/".*/, "", proc) if (proc == "") proc = "-" if (port != "*") printf " %-10s %-10s %s\n", port, "UDP", proc }' | sort -t' ' -k1 -n | uniq || true else netstat -ulnp 2>/dev/null | awk '{ split($4, a, ":") port = a[length(a)] proc = $6 if (NR > 2) printf " %-10s %-10s %s\n", port, "UDP", proc }' | sort -t' ' -k1 -n | uniq || true fi echo "" draw_footer 50 } # ================== 系统服务管理 ================== show_service_manager() { while true; do clear draw_title_line "系统服务管理" 50 echo "" draw_menu_item "1" "📋" "查看运行中的服务" draw_menu_item "2" "🔍" "搜索服务" draw_menu_item "3" "▶️" "启动服务" draw_menu_item "4" "⏹️" "停止服务" draw_menu_item "5" "🔄" "重启服务" draw_menu_item "6" "📊" "查看服务状态" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回上级菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入选择${NC} [0-6]: )" svc_choice /dev/null | head -35 || \ service --status-all 2>/dev/null | grep '\[ + \]' | head -30 press_any_key ;; 2) clear draw_title_line "搜索服务" 50 echo "" read -p "请输入服务名关键词: " keyword /dev/null | grep -i "$keyword" || \ echo " 未找到匹配的服务" fi press_any_key ;; 3) clear draw_title_line "启动服务" 50 echo "" read -p "请输入要启动的服务名: " svc_name /dev/null; then log_success "服务 $svc_name 已启动" else log_error "启动服务 $svc_name 失败" fi fi press_any_key ;; 4) clear draw_title_line "停止服务" 50 echo "" read -p "请输入要停止的服务名: " svc_name /dev/null; then log_success "服务 $svc_name 已停止" else log_error "停止服务 $svc_name 失败" fi fi fi press_any_key ;; 5) clear draw_title_line "重启服务" 50 echo "" read -p "请输入要重启的服务名: " svc_name /dev/null; then log_success "服务 $svc_name 已重启" else log_error "重启服务 $svc_name 失败" fi fi press_any_key ;; 6) clear draw_title_line "查看服务状态" 50 echo "" read -p "请输入服务名: " svc_name /dev/null || \ log_error "无法获取服务 $svc_name 的状态" fi press_any_key ;; 0) break ;; *) log_error "无效输入。" press_any_key ;; esac done } # ================== Docker 安装子菜单 ================== install_docker_menu() { while true; do clear draw_title_line "Docker 管理" 50 echo "" # 显示当前安装状态 if command -v docker &>/dev/null; then local docker_ver=$(docker --version 2>/dev/null | awk '{print $3}' | tr -d ',') echo -e " ${GREEN}✓${NC} Docker 已安装 (${docker_ver})" if systemctl is-active --quiet docker 2>/dev/null; then echo -e " ${GREEN}●${NC} Docker 状态: ${GREEN}运行中${NC}" else echo -e " ${RED}●${NC} Docker 状态: ${RED}已停止${NC}" fi # 显示容器和镜像数量 local container_count=$(docker ps -aq 2>/dev/null | wc -l) local running_count=$(docker ps -q 2>/dev/null | wc -l) local image_count=$(docker images -q 2>/dev/null | wc -l) echo -e " ${CYAN}容器:${NC} ${running_count}/${container_count} 运行中 ${CYAN}镜像:${NC} ${image_count} 个" else echo -e " ${GRAY}○${NC} Docker 未安装" fi if docker compose version &>/dev/null 2>&1; then local compose_ver=$(docker compose version 2>/dev/null | awk '{print $4}') echo -e " ${GREEN}✓${NC} Docker Compose 已安装 (${compose_ver})" else echo -e " ${GRAY}○${NC} Docker Compose 未安装" fi echo "" echo -e " ${WHITE}${BOLD}【安装与卸载】${NC}" draw_menu_item "1" "🇨🇳" "使用腾讯云源安装 (国内首选)" draw_menu_item "2" "🇨🇳" "使用阿里云源安装" draw_menu_item "3" "🇨🇳" "使用中科大源安装" draw_menu_item "4" "🌍" "使用官方源安装 (国外推荐)" draw_menu_item "5" "🗑️" "卸载 Docker" echo "" echo -e " ${WHITE}${BOLD}【容器管理】${NC}" draw_menu_item "6" "📋" "查看容器列表" draw_menu_item "7" "▶️" "启动/停止/重启容器" draw_menu_item "8" "📝" "查看容器日志" echo "" echo -e " ${WHITE}${BOLD}【镜像与清理】${NC}" draw_menu_item "9" "🖼️" "查看镜像列表" draw_menu_item "10" "🧹" "清理 Docker 空间" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回上级菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入选择${NC} [0-10]: )" docker_choice /dev/null; then log_warning "Docker 已安装,是否重新安装?" read -p "输入 y 继续,其他键取消: " confirm /dev/null || true echo "" log_success "Docker 安装完成!" docker --version docker compose version 2>/dev/null || true echo "" echo -e " ${YELLOW}提示: 如需使用当前用户运行 Docker,请重新登录终端${NC}" press_any_key ;; 2) clear draw_title_line "使用阿里云源安装 Docker" 50 echo "" if command -v docker &>/dev/null; then log_warning "Docker 已安装,是否重新安装?" read -p "输入 y 继续,其他键取消: " confirm /dev/null || true echo "" log_success "Docker 安装完成!" docker --version docker compose version 2>/dev/null || true echo "" echo -e " ${YELLOW}提示: 如需使用当前用户运行 Docker,请重新登录终端${NC}" press_any_key ;; 3) clear draw_title_line "使用中科大源安装 Docker" 50 echo "" if command -v docker &>/dev/null; then log_warning "Docker 已安装,是否重新安装?" read -p "输入 y 继续,其他键取消: " confirm /dev/null || true echo "" log_success "Docker 安装完成!" docker --version docker compose version 2>/dev/null || true echo "" echo -e " ${YELLOW}提示: 如需使用当前用户运行 Docker,请重新登录终端${NC}" press_any_key ;; 4) clear draw_title_line "使用官方源安装 Docker" 50 echo "" if command -v docker &>/dev/null; then log_warning "Docker 已安装,是否重新安装?" read -p "输入 y 继续,其他键取消: " confirm /dev/null || true echo "" log_success "Docker 安装完成!" docker --version docker compose version 2>/dev/null || true echo "" echo -e " ${YELLOW}提示: 如需使用当前用户运行 Docker,请重新登录终端${NC}" press_any_key ;; 5) clear draw_title_line "卸载 Docker" 50 echo "" if ! command -v docker &>/dev/null; then log_warning "Docker 未安装,无需卸载。" press_any_key continue fi echo -e " ${RED}${BOLD}⚠ 警告:此操作将完全删除 Docker!${NC}" echo "" echo -e " 将会删除以下内容:" echo -e " • Docker 引擎和 CLI" echo -e " • Docker Compose 插件" echo -e " • 所有容器、镜像、卷、网络" echo "" read -p "请输入 'yes' 确认卸载: " confirm /dev/null || true sudo docker rm $(docker ps -aq) 2>/dev/null || true log_info "正在卸载 Docker..." # 卸载所有可能的 Docker 相关包 sudo apt-get purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker.io docker-compose docker-doc podman-docker 2>/dev/null || true sudo apt-get autoremove -y --purge log_info "正在清理 Docker 数据..." sudo rm -rf /var/lib/docker sudo rm -rf /var/lib/containerd sudo rm -rf /etc/docker echo "" log_success "Docker 已完全卸载!" press_any_key ;; 6) clear draw_title_line "容器列表" 50 echo "" if ! command -v docker &>/dev/null; then log_error "Docker 未安装!" press_any_key continue fi echo -e " ${WHITE}${BOLD}运行中的容器${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo " 暂无运行中的容器" echo "" echo -e " ${WHITE}${BOLD}所有容器${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" 2>/dev/null || echo " 暂无容器" press_any_key ;; 7) clear draw_title_line "容器操作" 50 echo "" if ! command -v docker &>/dev/null; then log_error "Docker 未安装!" press_any_key continue fi docker ps -a --format "table {{.Names}}\t{{.Status}}" 2>/dev/null echo "" read -p "请输入容器名称: " container_name /dev/null; then log_error "Docker 未安装!" press_any_key continue fi docker ps --format "{{.Names}}" 2>/dev/null echo "" read -p "请输入容器名称: " container_name /dev/null || log_error "无法获取日志" fi press_any_key ;; 9) clear draw_title_line "镜像列表" 50 echo "" if ! command -v docker &>/dev/null; then log_error "Docker 未安装!" press_any_key continue fi echo -e " ${WHITE}${BOLD}本地镜像${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" docker images --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}" 2>/dev/null || echo " 暂无镜像" echo "" # 显示磁盘占用 local disk_usage=$(docker system df 2>/dev/null | grep -E "^(Images|Containers|Volumes)" || true) if [[ -n "$disk_usage" ]]; then echo -e " ${WHITE}${BOLD}磁盘占用${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" echo "$disk_usage" | while read line; do echo " $line"; done fi press_any_key ;; 10) clear draw_title_line "清理 Docker 空间" 50 echo "" if ! command -v docker &>/dev/null; then log_error "Docker 未安装!" press_any_key continue fi # 显示当前占用 echo -e " ${WHITE}${BOLD}当前 Docker 磁盘占用${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" docker system df 2>/dev/null || true echo "" echo -e " ${CYAN}1.${NC} 清理悬空镜像 (无标签镜像)" echo -e " ${CYAN}2.${NC} 清理已停止的容器" echo -e " ${CYAN}3.${NC} 清理未使用的网络" echo -e " ${CYAN}4.${NC} 全部清理 (推荐)" echo -e " ${RED}5.${NC} 深度清理 (包括未使用的卷,谨慎!)" echo "" read -p "请选择清理方式: " clean_choice /dev/null || true press_any_key ;; 0) break ;; *) log_error "无效输入。" press_any_key ;; esac done } # ================== Nginx 管理子菜单 ================== install_nginx_menu() { while true; do clear draw_title_line "Nginx 管理" 50 echo "" # 显示当前状态 if command -v nginx &>/dev/null; then local nginx_ver=$(nginx -v 2>&1 | awk -F'/' '{print $2}') echo -e " ${GREEN}✓${NC} Nginx 已安装 (${nginx_ver})" if systemctl is-active --quiet nginx 2>/dev/null; then echo -e " ${GREEN}●${NC} 运行状态: ${GREEN}运行中${NC}" else echo -e " ${RED}●${NC} 运行状态: ${RED}已停止${NC}" fi else echo -e " ${GRAY}○${NC} Nginx 未安装" fi echo "" draw_menu_item "1" "📦" "安装 Nginx" draw_menu_item "2" "🔀" "配置反向代理" draw_menu_item "3" "🔒" "申请 HTTPS 证书 (Certbot)" draw_menu_item "4" "▶️" "启动 Nginx" draw_menu_item "5" "⏹️" "停止 Nginx" draw_menu_item "6" "🔄" "重启 Nginx" draw_menu_item "7" "📊" "查看状态" draw_menu_item "8" "🗑️" "卸载 Nginx" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回上级菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入选择${NC} [0-8]: )" nginx_choice /dev/null; then log_error "Nginx 未安装,请先安装!" press_any_key continue fi read -p "请输入域名 (如 example.com): " domain /dev/null </dev/null; then log_error "Nginx 未安装,请先安装!" press_any_key continue fi # 检测 Certbot if ! command -v certbot &>/dev/null; then log_info "Certbot 未安装,正在自动安装..." sudo apt-get update sudo apt-get install -y certbot python3-certbot-nginx log_success "Certbot 安装完成!" echo "" fi read -p "请输入域名 (如 example.com): " domain /dev/null; then log_warning "Nginx 未安装,无需卸载。" press_any_key continue fi echo -e " ${RED}${BOLD}⚠ 警告:将卸载 Nginx 及其配置文件!${NC}" echo "" read -p "请输入 'yes' 确认卸载: " confirm /dev/null || true log_info "正在卸载 Nginx..." sudo apt-get purge -y nginx nginx-common nginx-full nginx-core 2>/dev/null || true sudo apt-get autoremove -y --purge log_info "正在清理配置..." sudo rm -rf /etc/nginx sudo rm -rf /var/log/nginx echo "" log_success "Nginx 已完全卸载!" press_any_key ;; 0) break ;; *) log_error "无效输入。" press_any_key ;; esac done } # ================== Caddy 管理子菜单 ================== install_caddy_menu() { while true; do clear draw_title_line "Caddy 管理" 50 echo "" # 显示当前状态 if command -v caddy &>/dev/null; then local caddy_ver=$(caddy version 2>/dev/null | awk '{print $1}') echo -e " ${GREEN}✓${NC} Caddy 已安装 (${caddy_ver})" if systemctl is-active --quiet caddy 2>/dev/null; then echo -e " ${GREEN}●${NC} 运行状态: ${GREEN}运行中${NC}" else echo -e " ${RED}●${NC} 运行状态: ${RED}已停止${NC}" fi else echo -e " ${GRAY}○${NC} Caddy 未安装" fi echo "" draw_menu_item "1" "📦" "安装 Caddy" draw_menu_item "2" "🔀" "配置反向代理 (自动 HTTPS)" draw_menu_item "3" "▶️" "启动 Caddy" draw_menu_item "4" "⏹️" "停止 Caddy" draw_menu_item "5" "🔄" "重启 Caddy" draw_menu_item "6" "📊" "查看状态" draw_menu_item "7" "🗑️" "卸载 Caddy" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回上级菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入选择${NC} [0-7]: )" caddy_choice /dev/null curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg --yes curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list >/dev/null sudo apt-get update && sudo apt-get install -y caddy log_success "Caddy 安装完成!" caddy version echo "" echo -e " ${CYAN}配置文件:${NC} /etc/caddy/Caddyfile" echo -e " ${GREEN}特性: 自动 HTTPS 证书申请与续期${NC}" press_any_key ;; 2) clear draw_title_line "配置 Caddy 反向代理" 50 echo "" if ! command -v caddy &>/dev/null; then log_error "Caddy 未安装,请先安装!" press_any_key continue fi read -p "请输入域名 (如 example.com): " domain /dev/null echo "${domain} {" | sudo tee -a /etc/caddy/Caddyfile >/dev/null echo " reverse_proxy ${backend}" | sudo tee -a /etc/caddy/Caddyfile >/dev/null echo "}" | sudo tee -a /etc/caddy/Caddyfile >/dev/null sudo systemctl reload caddy log_success "反向代理配置完成!" echo -e " ${CYAN}域名:${NC} ${domain}" echo -e " ${CYAN}后端:${NC} ${backend}" echo -e " ${GREEN}Caddy 将自动为该域名申请 HTTPS 证书${NC}" press_any_key ;; 3) sudo systemctl start caddy log_success "Caddy 已启动" press_any_key ;; 4) sudo systemctl stop caddy log_success "Caddy 已停止" press_any_key ;; 5) sudo systemctl restart caddy log_success "Caddy 已重启" press_any_key ;; 6) clear draw_title_line "Caddy 状态" 50 echo "" sudo systemctl status caddy --no-pager || true press_any_key ;; 7) clear draw_title_line "卸载 Caddy" 50 echo "" if ! command -v caddy &>/dev/null; then log_warning "Caddy 未安装,无需卸载。" press_any_key continue fi echo -e " ${RED}${BOLD}⚠ 警告:将卸载 Caddy 及其配置文件!${NC}" echo "" read -p "请输入 'yes' 确认卸载: " confirm /dev/null || true log_info "正在卸载 Caddy..." sudo apt-get purge -y caddy 2>/dev/null || true sudo apt-get autoremove -y --purge log_info "正在清理配置..." sudo rm -rf /etc/caddy sudo rm -rf /var/lib/caddy sudo rm -rf /var/log/caddy sudo rm -f /etc/apt/sources.list.d/caddy-stable.list sudo rm -f /usr/share/keyrings/caddy-stable-archive-keyring.gpg echo "" log_success "Caddy 已完全卸载!" press_any_key ;; 0) break ;; *) log_error "无效输入。" press_any_key ;; esac done } # ================== 反代工具子菜单 ================== show_proxy_menu() { while true; do clear draw_title_line "反代工具" 50 echo "" echo -e " ${WHITE}${BOLD}选择您需要的反向代理工具${NC}" echo "" echo -e " ${CYAN}Nginx${NC} - 经典高性能,需手动配置 HTTPS" echo -e " ${CYAN}Caddy${NC} - 现代化,自动 HTTPS 证书" echo "" draw_menu_item "1" "🌐" "Nginx 管理" draw_menu_item "2" "🔒" "Caddy 管理" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回上级菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入选择${NC} [0-2]: )" proxy_choice /dev/null; then echo -e " ${GREEN}✓${NC} fail2ban 已安装" if systemctl is-active --quiet fail2ban 2>/dev/null; then echo -e " ${GREEN}●${NC} 运行状态: ${GREEN}运行中${NC}" local banned=$(sudo fail2ban-client status sshd 2>/dev/null | grep "Currently banned" | awk '{print $NF}') echo -e " ${CYAN}当前封禁:${NC} ${banned:-0} 个 IP" else echo -e " ${RED}●${NC} 运行状态: ${RED}已停止${NC}" fi else echo -e " ${GRAY}○${NC} fail2ban 未安装" fi echo "" draw_menu_item "1" "📦" "安装 fail2ban" draw_menu_item "2" "📋" "查看封禁列表" draw_menu_item "3" "🔓" "解封指定 IP" draw_menu_item "4" "📊" "查看状态" draw_menu_item "5" "🗑️" "卸载 fail2ban" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回上级菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入选择${NC} [0-5]: )" f2b_choice /dev/null </dev/null; then log_error "fail2ban 未安装!" press_any_key continue fi log_info "当前被封禁的 IP 列表:" echo "" sudo fail2ban-client status sshd 2>/dev/null || echo " 暂无封禁记录" press_any_key ;; 3) clear draw_title_line "解封 IP" 50 echo "" if ! command -v fail2ban-client &>/dev/null; then log_error "fail2ban 未安装!" press_any_key continue fi read -p "请输入要解封的 IP: " unban_ip /dev/null; then log_warning "fail2ban 未安装,无需卸载。" press_any_key continue fi read -p "确认卸载 fail2ban? (y/n): " confirm /dev/null || true sudo apt-get purge -y fail2ban sudo apt-get autoremove -y --purge log_success "fail2ban 已卸载!" fi press_any_key ;; 0) break ;; *) log_error "无效输入。" press_any_key ;; esac done } # ================== 系统监控工具子菜单 ================== install_monitor_menu() { while true; do clear draw_title_line "系统监控工具" 50 echo "" # 显示当前状态 if command -v htop &>/dev/null; then echo -e " ${GREEN}✓${NC} htop 已安装" else echo -e " ${GRAY}○${NC} htop 未安装" fi if command -v btop &>/dev/null; then echo -e " ${GREEN}✓${NC} btop 已安装" else echo -e " ${GRAY}○${NC} btop 未安装" fi echo "" echo -e " ${CYAN}htop${NC} - 经典轻量,兼容性好" echo -e " ${CYAN}btop${NC} - 现代美观,功能丰富" echo "" draw_menu_item "1" "📦" "安装 htop" draw_menu_item "2" "📦" "安装 btop" draw_menu_item "3" "▶️" "运行 htop" draw_menu_item "4" "▶️" "运行 btop" draw_menu_item "5" "🗑️" "卸载 htop/btop" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回上级菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入选择${NC} [0-5]: )" mon_choice /dev/null || { log_warning "apt 源中无 btop,尝试 snap 安装..." sudo snap install btop 2>/dev/null || { log_error "btop 安装失败,您的系统可能不支持" } } command -v btop &>/dev/null && log_success "btop 安装完成!运行命令: btop" press_any_key ;; 3) if command -v htop &>/dev/null; then htop else log_error "htop 未安装,请先安装!" press_any_key fi ;; 4) if command -v btop &>/dev/null; then btop else log_error "btop 未安装,请先安装!" press_any_key fi ;; 5) clear draw_title_line "卸载监控工具" 50 echo "" echo -e " ${CYAN}1.${NC} 卸载 htop" echo -e " ${CYAN}2.${NC} 卸载 btop" echo -e " ${CYAN}3.${NC} 全部卸载" echo "" read -p "请选择: " uninstall_choice /dev/null; sudo snap remove btop 2>/dev/null; log_success "btop 已卸载" ;; 3) sudo apt-get purge -y htop btop 2>/dev/null; sudo snap remove btop 2>/dev/null; log_success "已全部卸载" ;; esac press_any_key ;; 0) break ;; *) log_error "无效输入。" press_any_key ;; esac done } # ================== tmux 管理子菜单 ================== install_tmux_menu() { while true; do clear draw_title_line "tmux 终端复用" 50 echo "" # 显示当前状态 if command -v tmux &>/dev/null; then local tmux_ver=$(tmux -V 2>/dev/null | awk '{print $2}') echo -e " ${GREEN}✓${NC} tmux 已安装 (${tmux_ver})" local sessions=$(tmux ls 2>/dev/null | wc -l) echo -e " ${CYAN}活跃会话:${NC} ${sessions} 个" else echo -e " ${GRAY}○${NC} tmux 未安装" fi echo "" draw_menu_item "1" "📦" "安装 tmux" draw_menu_item "2" "➕" "新建会话" draw_menu_item "3" "📋" "列出会话" draw_menu_item "4" "🔗" "连接会话" draw_menu_item "5" "❓" "使用帮助" draw_menu_item "6" "🗑️" "卸载 tmux" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回上级菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入选择${NC} [0-6]: )" tmux_choice /dev/null; then log_error "tmux 未安装,请先安装!" press_any_key continue fi read -p "请输入会话名称: " session_name /dev/null; then log_error "tmux 未安装!" else tmux ls 2>/dev/null || echo " 暂无活跃会话" fi press_any_key ;; 4) if ! command -v tmux &>/dev/null; then log_error "tmux 未安装,请先安装!" press_any_key continue fi echo "" tmux ls 2>/dev/null || { echo " 暂无活跃会话"; press_any_key; continue; } echo "" read -p "请输入要连接的会话名称: " attach_name /dev/null; then log_warning "tmux 未安装,无需卸载。" press_any_key continue fi read -p "确认卸载 tmux? (y/n): " confirm /dev/null; then echo -e " ${GREEN}✓${NC} ufw 已安装" local status=$(sudo ufw status 2>/dev/null | head -1) if echo "$status" | grep -q "active"; then echo -e " ${GREEN}●${NC} 防火墙状态: ${GREEN}已启用${NC}" else echo -e " ${RED}●${NC} 防火墙状态: ${RED}未启用${NC}" fi else echo -e " ${GRAY}○${NC} ufw 未安装" fi echo "" draw_menu_item "1" "📦" "安装 ufw" draw_menu_item "2" "✅" "启用防火墙" draw_menu_item "3" "❌" "禁用防火墙" draw_menu_item "4" "➕" "开放端口" draw_menu_item "5" "➖" "关闭端口" draw_menu_item "6" "📋" "查看规则" draw_menu_item "7" "🔄" "重置规则" draw_menu_item "8" "🗑️" "卸载 ufw" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回上级菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入选择${NC} [0-8]: )" ufw_choice /dev/null; then log_error "ufw 未安装!" press_any_key continue fi echo -e " ${YELLOW}⚠ 警告:启用防火墙前请确保已开放 SSH 端口!${NC}" echo "" read -p "是否先开放 SSH 端口 22? (y/n): " open_ssh /dev/null; then log_error "ufw 未安装!" press_any_key continue fi read -p "请输入要开放的端口 (如 80 或 80/tcp): " port /dev/null; then log_error "ufw 未安装!" press_any_key continue fi read -p "请输入要关闭的端口 (如 80 或 80/tcp): " port /dev/null; then log_error "ufw 未安装!" else sudo ufw status numbered fi press_any_key ;; 7) clear draw_title_line "重置 ufw 规则" 50 echo "" echo -e " ${RED}${BOLD}⚠ 警告:将删除所有防火墙规则!${NC}" echo "" read -p "请输入 'yes' 确认重置: " confirm /dev/null; then log_warning "ufw 未安装,无需卸载。" press_any_key continue fi read -p "确认卸载 ufw? (y/n): " confirm /dev/null || true sudo apt-get purge -y ufw sudo apt-get autoremove -y --purge log_success "ufw 已卸载!" fi press_any_key ;; 0) break ;; *) log_error "无效输入。" press_any_key ;; esac done } # ================== 安全工具子菜单 ================== # ================== SSH 安全配置 ================== ssh_security_menu() { while true; do clear draw_title_line "SSH 安全配置" 50 echo "" # 显示当前状态 local pass_auth=$(grep -E "^PasswordAuthentication" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}') local pubkey_auth=$(grep -E "^PubkeyAuthentication" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}') echo -e " ${WHITE}${BOLD}当前 SSH 配置状态${NC}" if [[ "$pass_auth" == "no" ]]; then echo -e " ${RED}●${NC} 密码登录: ${RED}已禁用${NC}" else echo -e " ${GREEN}●${NC} 密码登录: ${GREEN}已启用${NC}" fi if [[ "$pubkey_auth" == "no" ]]; then echo -e " ${RED}●${NC} 密钥登录: ${RED}已禁用${NC}" else echo -e " ${GREEN}●${NC} 密钥登录: ${GREEN}已启用${NC}" fi if [[ -f ~/.ssh/authorized_keys ]]; then local key_count=$(wc -l < ~/.ssh/authorized_keys 2>/dev/null || echo 0) echo -e " ${CYAN}已授权密钥:${NC} ${key_count} 个" else echo -e " ${CYAN}已授权密钥:${NC} 0 个" fi echo "" draw_menu_item "1" "🔑" "生成 SSH 密钥对" draw_menu_item "2" "📥" "添加公钥到授权列表" draw_menu_item "3" "🔒" "禁用密码登录 (仅密钥)" draw_menu_item "4" "🔓" "恢复密码登录" draw_menu_item "5" "📋" "查看当前公钥" draw_menu_item "6" "📋" "查看当前私钥" draw_menu_item "7" "🗑️" "删除密钥文件" draw_menu_item "8" "❓" "使用帮助" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回上级菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入选择${NC} [0-8]: )" ssh_choice > ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys echo "" echo -e " ${GREEN}✓ 公钥已自动添加到 authorized_keys${NC}" # 自动启用 sshd 公钥认证配置 log_info "正在配置 sshd 以启用公钥认证..." # 备份 sshd_config sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d%H%M%S) 2>/dev/null || true # 启用 PubkeyAuthentication sudo sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config grep -q "^PubkeyAuthentication" /etc/ssh/sshd_config || echo "PubkeyAuthentication yes" | sudo tee -a /etc/ssh/sshd_config > /dev/null # 确保 AuthorizedKeysFile 配置正确 sudo sed -i 's/^#*AuthorizedKeysFile.*/AuthorizedKeysFile .ssh\/authorized_keys/' /etc/ssh/sshd_config grep -q "^AuthorizedKeysFile" /etc/ssh/sshd_config || echo "AuthorizedKeysFile .ssh/authorized_keys" | sudo tee -a /etc/ssh/sshd_config > /dev/null # 重启 sshd 服务使配置生效 sudo systemctl restart sshd 2>/dev/null || sudo service ssh restart 2>/dev/null || true echo -e " ${GREEN}✓ sshd 公钥认证已启用并重启服务${NC}" echo "" if [[ -n "$passphrase" ]]; then echo -e " ${GREEN}✓ 私钥已设置密码保护${NC}" else echo -e " ${YELLOW}○ 私钥无密码保护${NC}" fi # 获取服务器 IP 和当前用户 local server_ip=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "服务器IP") local current_user=$(whoami) local key_name="id_ed25519" [[ "$key_type" == "2" ]] && key_name="id_rsa" echo "" echo -e " ${WHITE}${BOLD}═══════════════════════════════════════════${NC}" echo -e " ${WHITE}${BOLD}下一步操作 (必读):${NC}" echo -e " ${WHITE}${BOLD}═══════════════════════════════════════════${NC}" echo "" echo -e " ${CYAN}步骤 1:${NC} 复制下方私钥内容到本地文件" echo -e " 保存为: ${YELLOW}~/.ssh/${key_name}_server${NC}" echo "" echo -e " ${CYAN}步骤 2:${NC} 在本地终端设置私钥权限" echo -e " ${WHITE}chmod 600 ~/.ssh/${key_name}_server${NC}" echo "" echo -e " ${CYAN}步骤 3:${NC} 测试密钥登录 (在本地执行)" echo -e " ${WHITE}ssh -i ~/.ssh/${key_name}_server ${current_user}@${server_ip}${NC}" echo "" echo -e " ${CYAN}步骤 4:${NC} 确认登录成功后,可禁用密码登录" echo -e " 使用菜单选项 3「禁用密码登录」" echo "" echo -e " ${RED}${BOLD}⚠ 重要提示:${NC}" echo -e " ${YELLOW}• 私钥必须下载到本地才能使用!${NC}" echo -e " ${YELLOW}• 请妥善保管私钥,丢失后无法恢复!${NC}" echo -e " ${YELLOW}• 禁用密码登录前请务必测试密钥登录!${NC}" echo "" read -p "是否立即显示私钥内容? (y/n): " show_key > ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys log_success "公钥已添加到授权列表!" press_any_key ;; 3) clear draw_title_line "禁用密码登录" 50 echo "" echo -e " ${RED}${BOLD}⚠ 警告:禁用密码登录后只能用密钥登录!${NC}" echo "" echo -e " ${YELLOW}请确保:${NC}" echo -e " 1. 已配置密钥登录并测试成功" echo -e " 2. 已保存私钥到本地" echo "" if [[ ! -f ~/.ssh/authorized_keys ]] || [[ ! -s ~/.ssh/authorized_keys ]]; then log_error "未检测到已授权的公钥!请先添加公钥。" press_any_key continue fi read -p "请输入 'yes' 确认禁用密码登录: " confirm /dev/null log_success "私钥已删除" ;; 2) rm -f ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub ~/.ssh/id_rsa ~/.ssh/id_rsa.pub 2>/dev/null log_success "密钥对已删除" ;; 3) rm -f ~/.ssh/authorized_keys 2>/dev/null log_success "授权公钥列表已清空" ;; 4) echo "" read -p "请输入 'yes' 确认删除所有密钥文件: " confirm /dev/null log_success "所有密钥文件已删除" else log_info "操作已取消" fi ;; esac press_any_key ;; 8) clear draw_title_line "SSH 密钥登录帮助" 50 echo "" echo -e " ${WHITE}${BOLD}什么是密钥登录?${NC}" echo -e " 使用密钥对(公钥+私钥)代替密码进行 SSH 认证" echo -e " 更安全,不怕暴力破解" echo "" echo -e " ${WHITE}${BOLD}配置步骤:${NC}" echo -e " 1. 生成密钥对(本菜单选项 1)" echo -e " 2. 复制私钥到本地电脑保存" echo -e " 3. 测试密钥登录是否成功" echo -e " 4. 确认无误后禁用密码登录(选项 3)" echo "" echo -e " ${WHITE}${BOLD}本地使用私钥登录:${NC}" echo -e " ${CYAN}ssh -i ~/.ssh/id_ed25519 user@server${NC}" echo "" echo -e " ${WHITE}${BOLD}Windows 用户:${NC}" echo -e " 使用 PuTTY 或 Xshell 导入私钥文件" press_any_key ;; 0) break ;; *) log_error "无效输入。" press_any_key ;; esac done } # ================== 安全工具子菜单 ================== show_security_menu() { while true; do clear draw_title_line "安全工具" 50 echo "" echo -e " ${WHITE}${BOLD}VPS 安全防护工具${NC}" echo "" echo -e " ${CYAN}fail2ban${NC} - 自动封禁暴力破解 IP" echo -e " ${CYAN}ufw${NC} - 简化版防火墙管理" echo -e " ${CYAN}SSH 安全${NC} - 密钥登录配置" echo "" draw_menu_item "1" "🛡️" "fail2ban (防暴力破解)" draw_menu_item "2" "🔥" "ufw (防火墙)" draw_menu_item "3" "🔑" "SSH 安全 (密钥登录)" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回上级菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入选择${NC} [0-3]: )" sec_choice /dev/null; then log_success "下载成功,开始执行..." echo "" chmod +x backtrace.sh && bash backtrace.sh || true rm -f backtrace.sh else log_error "脚本下载失败!" fi press_any_key ;; 2) clear draw_title_line "去程路由测试" 50 echo "" log_info "去程路由测试说明:" log_info "去程 = 从中国访问您的 VPS 时经过的路由" log_info "需要在中国的设备上安装 NextTrace 并追踪到您的 VPS IP" echo "" # 显示当前VPS的IP local vps_ip=$(curl -4 -s --max-time 5 ip.sb 2>/dev/null || curl -4 -s --max-time 5 ifconfig.me 2>/dev/null) if [[ -n "$vps_ip" ]]; then echo -e " ${WHITE}${BOLD}您的 VPS IP: ${CYAN}${vps_ip}${NC}" echo "" fi log_info "正在安装 NextTrace 路由追踪工具..." echo "" # 使用官方安装脚本 if curl -sL https://raw.githubusercontent.com/nxtrace/NTrace-core/main/nt_install.sh -o nt_install.sh 2>/dev/null; then bash nt_install.sh || true rm -f nt_install.sh echo "" log_success "NextTrace 安装完成!" echo "" echo -e " ${WHITE}${BOLD}使用方法:${NC}" echo -e " ${CYAN}nexttrace ${vps_ip:-<目标IP>}${NC} - 从本机追踪到目标" echo -e " ${CYAN}nexttrace -T <域名>${NC} - TCP 模式追踪" echo -e " ${CYAN}nexttrace -M${NC} - 交互式菜单" echo "" echo -e " ${YELLOW}提示: 在中国的设备上运行 nexttrace ${vps_ip:-<您的VPS IP>} 可测试去程${NC}" else log_error "脚本下载失败!" fi press_any_key ;; 0) break ;; *) log_error "无效输入。" press_any_key ;; esac done } # 子菜单: 性能/网络测试脚本 show_test_menu() { while true; do clear draw_title_line "性能/网络测试" 50 echo "" draw_menu_item "1" "🚀" "融合怪 (ecs.sh) 综合测试" draw_menu_item "2" "🐟" "咸鱼 IP 检测 (原创)" draw_menu_item "3" "🛤️" "路由测试 (回程/去程)" draw_menu_item "4" "📡" "Speedtest 测速" draw_menu_item "5" "🌐" "三网测速 (电信/联通/移动)" draw_menu_item "6" "💾" "磁盘 IO 测试" draw_menu_item "7" "📺" "流媒体解锁检测" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回主菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入选择${NC} [0-7]: )" test_choice /dev/null; then log_success "下载成功,开始执行..." echo "" bash fish_ipcheck.sh || true rm -f fish_ipcheck.sh else log_error "脚本下载失败!" fi fi press_any_key ;; 3) show_route_menu ;; 4) clear draw_title_line "Speedtest 测速" 50 echo "" # 检查 speedtest 是否已安装 if ! command -v speedtest &>/dev/null; then log_info "正在安装 Speedtest CLI..." # 尝试使用官方安装脚本 if curl -s https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh | sudo bash 2>/dev/null; then sudo apt-get install -y speedtest 2>/dev/null else # 备用方案:使用 speedtest-cli (Python 版本) log_warning "官方安装失败,尝试安装 Python 版本..." if command -v pip3 &>/dev/null; then sudo pip3 install speedtest-cli 2>/dev/null elif command -v pip &>/dev/null; then sudo pip install speedtest-cli 2>/dev/null else sudo apt-get install -y speedtest-cli 2>/dev/null || \ sudo apt-get install -y python3-pip && sudo pip3 install speedtest-cli fi fi fi echo "" if command -v speedtest &>/dev/null; then log_info "开始测速..." echo "" speedtest --accept-license --accept-gdpr 2>/dev/null || speedtest 2>/dev/null elif command -v speedtest-cli &>/dev/null; then log_info "开始测速..." echo "" speedtest-cli else log_error "Speedtest 安装失败,请手动安装" fi press_any_key ;; 5) clear draw_title_line "三网测速" 50 echo "" log_info "正在下载三网测速脚本..." log_info "将测试电信、联通、移动三大运营商的速度" echo "" # 使用 bench.sh 的三网测速 if curl -sL https://raw.githubusercontent.com/uxh/superspeed/master/superspeed.sh -o superspeed.sh 2>/dev/null; then log_success "下载成功,开始执行..." echo "" bash superspeed.sh || true rm -f superspeed.sh else # 备用方案 log_warning "主脚本下载失败,尝试备用方案..." bash <(curl -Lso- https://bench.im/hyperspeed) || \ log_error "三网测速脚本下载失败!" fi press_any_key ;; 6) clear draw_title_line "磁盘 IO 测试" 50 echo "" log_info "开始磁盘 IO 测试..." echo "" echo -e " ${WHITE}${BOLD}顺序写入测试 (1GB)${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" sync local write_result=$(dd if=/dev/zero of=./test_io_file bs=1M count=1024 conv=fdatasync 2>&1) local write_speed=$(echo "$write_result" | grep -oP '\d+\.?\d*\s*(MB|GB)/s' | tail -1) echo -e " ${GREEN}写入速度:${NC} ${write_speed:-解析失败}" echo "" echo -e " ${WHITE}${BOLD}顺序读取测试${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" # 清除缓存 sync && echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null 2>&1 || true local read_result=$(dd if=./test_io_file of=/dev/null bs=1M 2>&1) local read_speed=$(echo "$read_result" | grep -oP '\d+\.?\d*\s*(MB|GB)/s' | tail -1) echo -e " ${GREEN}读取速度:${NC} ${read_speed:-解析失败}" # 清理测试文件 rm -f ./test_io_file echo "" echo -e " ${WHITE}${BOLD}4K 随机读写测试${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" if command -v fio &>/dev/null; then local fio_result=$(fio --name=random-rw --ioengine=sync --rw=randrw --bs=4k --size=64m --numjobs=1 --time_based --runtime=10 --group_reporting --filename=./fio_test 2>&1) local read_iops=$(echo "$fio_result" | grep "read:" | grep -oP 'IOPS=\K[\d.]+[kKmM]?' | head -1) local write_iops=$(echo "$fio_result" | grep "write:" | grep -oP 'IOPS=\K[\d.]+[kKmM]?' | head -1) echo -e " ${GREEN}4K 随机读 IOPS:${NC} ${read_iops:-N/A}" echo -e " ${GREEN}4K 随机写 IOPS:${NC} ${write_iops:-N/A}" rm -f ./fio_test else echo -e " ${YELLOW}fio 未安装,跳过 4K 随机读写测试${NC}" echo -e " ${DIM}可通过 apt install fio 安装${NC}" fi echo "" press_any_key ;; 7) clear draw_title_line "流媒体解锁检测" 50 echo "" log_info "正在下载流媒体解锁检测脚本..." log_info "将检测 Netflix, Disney+, YouTube Premium 等平台解锁状态" echo "" # 使用 lmc999/RegionRestrictionCheck if bash <(curl -L -s https://raw.githubusercontent.com/lmc999/RegionRestrictionCheck/main/check.sh) 2>/dev/null; then : else log_error "流媒体检测脚本执行失败!" fi press_any_key ;; 0) break ;; *) log_error "无效输入。" press_any_key ;; esac done } # 子菜单: DD系统脚本 show_dd_menu() { while true; do clear draw_title_line "DD系统/重装系统" 50 echo "" echo -e " ${RED}${BOLD}⚠ 警告:DD系统风险极高,会清空磁盘!${NC}" echo "" draw_menu_item "1" "💿" "reinstall (通用系统重装)" draw_menu_item "2" "🐣" "LXD小鸡DD (NS酒神脚本)" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回主菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请输入选择${NC}): " dd_choice /dev/null || ! docker compose version &>/dev/null; then log_error "Docker 或 Compose 未安装。" return 1 fi # 选择部署方式 echo -e " ${WHITE}${BOLD}请选择部署方式:${NC}" echo -e " ${CYAN}1.${NC} 使用默认配置 ${DIM}(推荐)${NC}" echo -e " ${CYAN}2.${NC} 自定义配置" echo "" read -p "请选择 [1-2]: " deploy_mode /dev/null || true sudo sed -i "s/- ${default_port}:/- ${custom_port}:/g" "${dest_file}" 2>/dev/null || true sudo sed -i "s/'${default_port}:/'${custom_port}:/g" "${dest_file}" 2>/dev/null || true sudo sed -i "s/\"${default_port}:/\"${custom_port}:/g" "${dest_file}" 2>/dev/null || true # 替换时区 sudo sed -i "s|Asia/Shanghai|${custom_tz}|g" "${dest_file}" 2>/dev/null || true sudo sed -i "s|TZ=.*|TZ=${custom_tz}|g" "${dest_file}" 2>/dev/null || true fi log_info "启动项目中..." cd "$project_dir" || return 1 sudo docker compose up -d if [[ $? -eq 0 ]]; then echo "" log_success "项目 '$project_name' 已成功部署!" echo "" echo -e " ${WHITE}${BOLD}访问地址${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" echo -e " http://服务器IP:${custom_port}" else log_error "项目部署失败!" return 1 fi } # 新增功能:安装后提示信息 show_post_install_message() { local project_name="$1" echo "" case $project_name in "qbittorrent") echo -e " ${YELLOW}╭───────────────────────────────────────╮${NC}" echo -e " ${YELLOW}│${NC} ${WHITE}${BOLD}qBittorrent 默认登录信息${NC} ${YELLOW}│${NC}" echo -e " ${YELLOW}├───────────────────────────────────────┤${NC}" echo -e " ${YELLOW}│${NC} 用户名: ${CYAN}admin${NC} ${YELLOW}│${NC}" echo -e " ${YELLOW}│${NC} 查看密码: ${CYAN}sudo docker logs qbittorrent${NC}${YELLOW}│${NC}" echo -e " ${YELLOW}╰───────────────────────────────────────╯${NC}" ;; "moontv") echo -e " ${YELLOW}╭───────────────────────────────────────╮${NC}" echo -e " ${YELLOW}│${NC} ${WHITE}${BOLD}MoonTV 默认登录信息${NC} ${YELLOW}│${NC}" echo -e " ${YELLOW}├───────────────────────────────────────┤${NC}" echo -e " ${YELLOW}│${NC} 用户名: ${CYAN}admin${NC} ${YELLOW}│${NC}" echo -e " ${YELLOW}│${NC} 密 码: ${CYAN}admin_password${NC} ${YELLOW}│${NC}" echo -e " ${YELLOW}╰───────────────────────────────────────╯${NC}" ;; "nginx-proxy-manager") echo -e " ${YELLOW}╭───────────────────────────────────────╮${NC}" echo -e " ${YELLOW}│${NC} ${WHITE}${BOLD}Nginx Proxy Manager 默认登录信息${NC} ${YELLOW}│${NC}" echo -e " ${YELLOW}├───────────────────────────────────────┤${NC}" echo -e " ${YELLOW}│${NC} 邮 箱: ${CYAN}admin@example.com${NC} ${YELLOW}│${NC}" echo -e " ${YELLOW}│${NC} 密 码: ${CYAN}changeme${NC} ${YELLOW}│${NC}" echo -e " ${YELLOW}│${NC} ${RED}首次登录后请立即修改!${NC} ${YELLOW}│${NC}" echo -e " ${YELLOW}╰───────────────────────────────────────╯${NC}" ;; *) # 其他项目没有特殊提示 ;; esac } # 子菜单:显示预设项目 show_preset_deployment_menu() { while true; do clear draw_title_line "一键部署精选项目" 50 echo -e " ${DIM}by 咸鱼银河${NC}" echo "" echo -e " ${WHITE}${BOLD}【常用服务】${NC}" draw_menu_item "1" "🏠" "Homepage (精美起始页)" draw_menu_item "2" "🔀" "Nginx-Proxy-Manager (反代神器)" draw_menu_item "3" "🐳" "Portainer (Docker 可视化管理)" draw_menu_item "4" "📁" "Alist (网盘聚合)" draw_menu_item "5" "📊" "Uptime Kuma (服务监控)" echo "" echo -e " ${WHITE}${BOLD}【媒体娱乐】${NC}" draw_menu_item "6" "🎵" "Navidrome (音乐服务器)" draw_menu_item "7" "📥" "qBittorrent (下载器)" draw_menu_item "8" "📺" "MoonTV (观影聚合)" draw_menu_item "9" "🎬" "Jellyfin (媒体服务器)" draw_menu_item "10" "📷" "PhotoPrism (AI 照片管理)" echo "" echo -e " ${WHITE}${BOLD}【工具应用】${NC}" draw_menu_item "11" "🔐" "Vaultwarden (密码管理器)" draw_menu_item "12" "📂" "FileBrowser (文件管理器)" draw_menu_item "13" "☁️" "Nextcloud (私有云盘)" draw_menu_item "14" "🔧" "Gitea (Git 服务)" draw_menu_item "15" "📚" "Calibre-Web (电子书管理)" draw_menu_item "16" "🔄" "Syncthing (文件同步)" echo "" echo -e " ${WHITE}${BOLD}【网络工具】${NC}" draw_menu_item "17" "🌐" "AdGuard Home (DNS 广告过滤)" draw_menu_item "18" "⬇️" "Transmission (BT 下载)" echo "" draw_separator 50 draw_menu_item "0" "🔙" "返回上一级菜单" draw_footer 50 echo "" read -p "$(echo -e ${CYAN}请选择要部署的项目${NC} [0-18]: )" preset_choice /dev/null || ! docker compose version &>/dev/null; then log_error "Docker 或 Compose 未安装。" return 1 fi # 输入安装目录 local default_dir="/opt/${repo}" read -p "安装目录 [${default_dir}]: " project_dir /dev/null; then log_success "成功下载 ${path}" downloaded=1 break fi done # 尝试 master 分支 if [[ $downloaded -eq 0 ]]; then raw_base="https://raw.githubusercontent.com/${owner}/${repo}/master" for path in "docker-compose.yml" "docker-compose.yaml" "compose.yml" "compose.yaml"; do if sudo curl -sLf -o "${dest_file}" "${raw_base}/${path}" 2>/dev/null; then log_success "成功下载 ${path} (master 分支)" downloaded=1 break fi done fi if [[ $downloaded -eq 0 ]]; then log_error "未找到 docker-compose 配置文件!" log_warning "请确认仓库根目录存在 docker-compose.yml 或 compose.yml" sudo rm -rf "$project_dir" return 1 fi echo "" echo -e " ${WHITE}${BOLD}部署信息${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" echo -e " 仓库: ${CYAN}${owner}/${repo}${NC}" echo -e " 安装目录: ${CYAN}${project_dir}${NC}" echo "" read -p "确认部署? (y/n): " confirm /dev/null | awk '{print $1}' || echo "0") local journal=$(du -sh /var/log/journal 2>/dev/null | awk '{print $1}' || echo "0") local tmp_size=$(du -sh /tmp 2>/dev/null | awk '{print $1}' || echo "0") local old_kernels=$(dpkg -l 'linux-*' 2>/dev/null | grep -E '^ii' | wc -l) echo -e " ${WHITE}${BOLD}可清理项目${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" echo -e " ${CYAN}1.${NC} APT 缓存 约 ${apt_cache}" echo -e " ${CYAN}2.${NC} 系统日志 (Journal) 约 ${journal}" echo -e " ${CYAN}3.${NC} 临时文件 (/tmp) 约 ${tmp_size}" echo -e " ${CYAN}4.${NC} 旧内核 (保留当前) ${old_kernels} 个包" echo -e " ${CYAN}5.${NC} 一键清理全部" echo "" read -p "请选择要清理的项目 [1-5]: " clean_opt /dev/null || true log_success "日志已清理(保留7天)" ;; 3) log_info "清理临时文件..." sudo rm -rf /tmp/* 2>/dev/null || true log_success "临时文件已清理" ;; 4) log_info "清理旧内核..." sudo apt-get autoremove --purge -y 2>/dev/null || true log_success "旧内核已清理" ;; 5) log_info "执行一键清理..." sudo apt-get clean sudo apt-get autoremove --purge -y 2>/dev/null || true sudo journalctl --vacuum-time=7d 2>/dev/null || true sudo rm -rf /tmp/* 2>/dev/null || true # 清理用户缓存 rm -rf ~/.cache/* 2>/dev/null || true log_success "全部清理完成!" ;; esac echo "" echo -e " ${WHITE}${BOLD}清理后磁盘使用${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" df -h / | awk 'NR==2{print " "$0}' press_any_key ;; 2) clear draw_title_line "修改时区" 50 echo "" echo -e " ${WHITE}${BOLD}当前时区${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" echo -e " $(timedatectl 2>/dev/null | grep 'Time zone' | awk -F': ' '{print $2}' || date +%Z)" echo "" echo -e " ${WHITE}${BOLD}常用时区${NC}" echo -e " ${CYAN}1.${NC} Asia/Shanghai (中国-上海)" echo -e " ${CYAN}2.${NC} Asia/Hong_Kong (中国-香港)" echo -e " ${CYAN}3.${NC} Asia/Tokyo (日本-东京)" echo -e " ${CYAN}4.${NC} Asia/Singapore (新加坡)" echo -e " ${CYAN}5.${NC} America/New_York (美国-纽约)" echo -e " ${CYAN}6.${NC} America/Los_Angeles (美国-洛杉矶)" echo -e " ${CYAN}7.${NC} Europe/London (英国-伦敦)" echo -e " ${CYAN}8.${NC} 自定义输入" echo "" read -p "请选择时区 [1-8]: " tz_choice /dev/null || \ sudo ln -sf "/usr/share/zoneinfo/$new_tz" /etc/localtime log_success "时区已设置为: $new_tz" echo -e " 当前时间: $(date)" fi press_any_key ;; 3) clear draw_title_line "修改主机名" 50 echo "" echo -e " ${WHITE}${BOLD}当前主机名${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" echo -e " $(hostname)" echo "" read -p "请输入新主机名: " new_hostname /dev/null || \ echo "$new_hostname" | sudo tee /etc/hostname >/dev/null # 更新 /etc/hosts sudo sed -i "s/127.0.1.1.*/127.0.1.1\t$new_hostname/" /etc/hosts 2>/dev/null || true log_success "主机名已设置为: $new_hostname" echo -e " ${YELLOW}提示: 重新登录后生效${NC}" fi press_any_key ;; 4) clear draw_title_line "修改 SSH 端口" 50 echo "" local current_port=$(grep -E "^Port" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' || echo "22") [[ -z "$current_port" ]] && current_port="22" echo -e " ${WHITE}${BOLD}当前 SSH 端口${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" echo -e " ${current_port}" echo "" echo -e " ${YELLOW}⚠ 警告:修改端口前请确保新端口已在防火墙中开放!${NC}" echo "" read -p "请输入新 SSH 端口 (1024-65535): " new_port /dev/null fi # 尝试在防火墙中开放新端口 if command -v ufw &>/dev/null; then sudo ufw allow "$new_port"/tcp 2>/dev/null || true fi log_success "SSH 端口已修改为: $new_port" echo "" echo -e " ${RED}${BOLD}重要提示:${NC}" echo -e " 1. 请确保防火墙已开放端口 $new_port" echo -e " 2. 新开一个终端测试: ${CYAN}ssh -p $new_port user@ip${NC}" echo -e " 3. 确认能连接后再关闭当前终端" echo "" read -p "是否立即重启 SSH 服务? (y/n): " restart_ssh /dev/null || sudo service ssh restart log_success "SSH 服务已重启" fi else log_error "无效端口号!请输入 1024-65535 之间的数字" fi press_any_key ;; 5) clear draw_title_line "定时任务管理" 50 echo "" echo -e " ${WHITE}${BOLD}当前用户的 Cron 任务${NC}" echo -e " ${GRAY}──────────────────────────────────────────${NC}" crontab -l 2>/dev/null || echo " 暂无定时任务" echo "" draw_menu_item "1" "➕" "添加定时任务" draw_menu_item "2" "📝" "编辑定时任务" draw_menu_item "3" "🗑️" "清空所有任务" echo "" read -p "请选择操作 [1-3]: " cron_opt /dev/null; echo "$cron_expr $cron_cmd") | crontab - log_success "定时任务已添加" fi ;; 2) crontab -e ;; 3) read -p "确认清空所有定时任务? (y/n): " confirm /dev/null || true log_success "定时任务已清空" fi ;; esac press_any_key ;; 6) clear draw_title_line "系统重启/关机" 50 echo "" echo -e " ${RED}${BOLD}⚠ 警告:此操作将中断所有服务!${NC}" echo "" echo -e " ${CYAN}1.${NC} 立即重启" echo -e " ${CYAN}2.${NC} 立即关机" echo -e " ${CYAN}3.${NC} 定时重启 (分钟后)" echo "" read -p "请选择操作 [1-3]: " power_opt