Minecraft 基岩版 自建服务器

官方服务器端下载地址:

https://www.minecraft.net/en-us/download/server/bedrock

Ubuntu 版本 Minecraft 服务端管理脚本:

#!/bin/bash
set -u

# =====================================================================
# 工具名称: mc_ctl.sh
# 功能描述: Minecraft 基岩版 (BDS) 智能运维工具 (环境检测/扫描接管/智能配置/极速更新)
# 适用环境: Linux (Ubuntu/Debian/CentOS/Alibaba Cloud Linux)
# =====================================================================

# 默认全局配置
DEFAULT_SERVICE_NAME="mc"
DEFAULT_SCREEN_NAME="mc"
DEFAULT_SERVER_DIR="$HOME/mc-server"
DEFAULT_BACKUP_DIR="$HOME/mc_backups"
RUN_USER="$USER"

# 颜色定义
C_RED="\033[31m"
C_GREEN="\033[32m"
C_YELLOW="\033[33m"
C_BLUE="\033[34m"
C_CYAN="\033[36m"
C_BOLD="\033[1m"
C_RESET="\033[0m"

# ---------------------------------------------------------
# 1. 硬件环境检测
# ---------------------------------------------------------
detect_hardware() {
    echo -e "\n${C_CYAN}${C_BOLD}================ 🖥️  服务器硬件与网络环境 ================ ${C_RESET}"
    
    # CPU
    CPU_MODEL=$(lscpu | grep -i "Model name" | head -n 1 | awk -F: '{print $2}' | sed 's/^[ \t]*//')
    [ -z "$CPU_MODEL" ] && CPU_MODEL="Generic CPU"
    CPU_CORES=$(nproc)
    
    # 内存 (MB)
    TOTAL_MEM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}')
    TOTAL_MEM_MB=$((TOTAL_MEM_KB / 1024))
    AVAIL_MEM_KB=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
    AVAIL_MEM_MB=$((AVAIL_MEM_KB / 1024))
    
    # 磁盘空间 (根目录)
    DISK_AVAIL=$(df -h / | awk 'NR==2 {print $4}')
    
    # IP 地址探测
    PUBLIC_IP=$(curl -s --connect-timeout 3 http://ipv4.icanhazip.com || echo "未能检测到公网IP")

    echo -e "  🔹 CPU 型号: ${C_YELLOW}${CPU_MODEL}${C_RESET}"
    echo -e "  🔹 CPU 核心: ${C_GREEN}${CPU_CORES} 核心${C_RESET}"
    echo -e "  🔹 物理内存: ${C_GREEN}${TOTAL_MEM_MB} MB${C_RESET} (可用: ${AVAIL_MEM_MB} MB)"
    echo -e "  🔹 磁盘可用: ${C_GREEN}${DISK_AVAIL}${C_RESET}"
    echo -e "  🔹 公网地址: ${C_YELLOW}${PUBLIC_IP}${C_RESET}"
    echo -e "${C_CYAN}========================================================${C_RESET}\n"
}

# ---------------------------------------------------------
# 2. 依赖检查与补齐
# ---------------------------------------------------------
check_dependencies() {
    local missing_pkgs=()
    for pkg in curl unzip tar screen rsync; do
        if ! command -v "$pkg" &>/dev/null; then
            missing_pkgs+=("$pkg")
        fi
    done

    if ! ldconfig -p | grep -q "libcurl\.so\.4" 2>/dev/null; then
        missing_pkgs+=("libcurl4")
    fi

    if [ ${#missing_pkgs[@]} -gt 0 ]; then
        echo -e "${C_YELLOW}💡 检测到缺少必要依赖: ${missing_pkgs[*]}${C_RESET}"
        read -p "是否自动执行 apt install 安装?(y/N): " choice
        if [[ "$choice" =~ ^[Yy]$ ]]; then
            sudo apt-get update && sudo apt-get install -y "${missing_pkgs[@]}"
        else
            echo -e "${C_RED}❌ 缺少依赖,操作终止。${C_RESET}"
            exit 1
        fi
    fi
}

# ---------------------------------------------------------
# 3. 国内高可用版本探测
# ---------------------------------------------------------
get_latest_version() {
    echo -e "🔍 正在探测 Minecraft 基岩版最新版本号..."
    local ver=""
    
    # 途径 1:阿里云 npmmirror 镜像源
    local npm_data
    npm_data=$(curl -s --connect-timeout 5 -m 8 "https://registry.npmmirror.com/@minecraft/server" 2>/dev/null || true)
    if [ -n "$npm_data" ]; then
        ver=$(echo "$npm_data" | grep -o 'beta\.1\.[0-9.]\+-stable' | sed -E 's/beta\.1\.([0-9.]+)-stable/\1/' | sort -V | tail -n 1)
    fi

    # 途径 2:国内可用 GitHub 代理池
    if [ -z "$ver" ]; then
        local PROXIES=("https://ghp.ci/" "https://ghproxy.net/" "https://github.moeyy.xyz/")
        for p in "${PROXIES[@]}"; do
            local res
            res=$(curl -s --connect-timeout 4 -m 6 "${p}https://raw.githubusercontent.com/Bedrock-OSS/BDS-Versions/main/versions.json" 2>/dev/null || true)
            if [ -n "$res" ]; then
                ver=$(echo "$res" | grep -o '"linux": *"[^"]*"' | head -n 1 | cut -d '"' -f 4)
                [ -n "$ver" ] && break
            fi
        done
    fi

    # 途径 3:微软官方下载页直连
    if [ -z "$ver" ]; then
        ver=$(curl -s -L --connect-timeout 6 -m 10 \
          -H "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8" \
          -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" \
          "https://www.minecraft.net/zh-hans/download/server/bedrock" 2>/dev/null | \
          grep -o 'https://www.minecraft.net/bedrockdedicatedserver/bin-linux/bedrock-server-[^"]*\.zip' | \
          head -n 1 | \
          sed -E 's/.*bedrock-server-([0-9.]+)\.zip/\1/')
    fi

    echo "$ver"
}

# ---------------------------------------------------------
# 4. 根据硬件生成最优 server.properties
# ---------------------------------------------------------
optimize_server_properties() {
    local target_file="$1"
    local cores="$2"
    local mem_mb="$3"
    
    echo -e "⚙️  正在根据服务器硬件配置 (${cores}核 / ${mem_mb}MB) 调优参数..."

    local view_dist=16
    local tick_dist=4
    local max_threads="$cores"
    local max_players=10

    if [ "$mem_mb" -le 2048 ] || [ "$cores" -le 1 ]; then
        # 1核 / 2G 以内低配机型
        view_dist=10
        tick_dist=4
        max_players=5
        max_threads=2
    elif [ "$mem_mb" -ge 12000 ] && [ "$cores" -ge 8 ]; then
        # 8核 / 16G 以上高配机型
        view_dist=24
        tick_dist=6
        max_players=20
        max_threads=0 # 0 代表全核心自适应
    fi

    # 更新或追加 server.properties 配置
    declare -A configs=(
        ["view-distance"]="$view_dist"
        ["tick-distance"]="$tick_dist"
        ["max-threads"]="$max_threads"
        ["max-players"]="$max_players"
        ["server-authoritative-movement"]="server-auth"
        ["player-idle-timeout"]="30"
    )

    for key in "${!configs[@]}"; do
        local val="${configs[$key]}"
        if grep -q "^${key}=" "$target_file" 2>/dev/null; then
            sed -i "s/^${key}=.*/${key}=${val}/" "$target_file"
        else
            echo "${key}=${val}" >> "$target_file"
        fi
    done

    echo -e "   ✅ 视距(view-distance): ${C_GREEN}${view_dist}${C_RESET} | 模拟距离(tick-distance): ${C_GREEN}${tick_dist}${C_RESET} | 线程上限: ${C_GREEN}${max_threads}${C_RESET}"
}

# ---------------------------------------------------------
# 5. 生成 systemd 服务文件
# ---------------------------------------------------------
create_systemd_service() {
    local s_name="$1"
    local scr_name="$2"
    local s_dir="$3"
    local s_user="$4"

    local service_path="/etc/systemd/system/${s_name}.service"
    echo -e "👉 正在配置守护服务: ${C_YELLOW}${service_path}${C_RESET} ..."

    sudo bash -c "cat <<EOF > $service_path
[Unit]
Description=Minecraft Bedrock Server ($s_name)
After=network.target

[Service]
Type=forking
User=$s_user
WorkingDirectory=$s_dir
ExecStart=/usr/bin/screen -dmS $scr_name /bin/bash -c 'LD_LIBRARY_PATH=. ./bedrock_server'
ExecStop=/usr/bin/screen -S $scr_name -p 0 -X stuff \"\$(printf 'stop\\\r')\"
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF"

    sudo systemctl daemon-reload
    sudo systemctl enable "$s_name"
}

# ---------------------------------------------------------
# 6. 下载核心包(优先微软 Azure CDN)
# ---------------------------------------------------------
download_server_zip() {
    local ver="$1"
    local dest_file="$2"
    local cdn_url="https://minecraft.azureedge.net/bin-linux/bedrock-server-${ver}.zip"
    local off_url="https://www.minecraft.net/bedrockdedicatedserver/bin-linux/bedrock-server-${ver}.zip"

    echo "👉 正在下载 Bedrock 服务端 ($ver) ..."
    if curl -fL -A "Mozilla/5.0" --connect-timeout 8 -m 120 -o "$dest_file" "$cdn_url"; then
        return 0
    fi

    echo "⚠️ 官方 CDN 波动,尝试直连备用节点..."
    if curl -fL -A "Mozilla/5.0" --connect-timeout 8 -m 120 -o "$dest_file" "$off_url"; then
        return 0
    fi

    return 1
}

# ---------------------------------------------------------
# 功能 A:全新一键安装
# ---------------------------------------------------------
action_install() {
    echo -e "\n${C_GREEN}${C_BOLD}=== 🚀 开始全新部署 Minecraft 基岩版 ===${C_RESET}"
    check_dependencies
    detect_hardware

    read -p "👉 请输入安装目录 [默认: $DEFAULT_SERVER_DIR]: " INPUT_DIR
    SERVER_DIR="${INPUT_DIR:-$DEFAULT_SERVER_DIR}"

    read -p "👉 请输入 systemd 服务名 [默认: $DEFAULT_SERVICE_NAME]: " INPUT_SVC
    SERVICE_NAME="${INPUT_SVC:-$DEFAULT_SERVICE_NAME}"
    SCREEN_NAME="$SERVICE_NAME"

    # 版本获取
    LATEST_VER=$(get_latest_version)
    if [ -n "$LATEST_VER" ]; then
        echo -e "💡 成功获取最新版本: ${C_GREEN}${LATEST_VER}${C_RESET}"
        read -p "👉 请输入目标版本号 (直接回车使用 $LATEST_VER): " INPUT_VER
        TARGET_VER="${INPUT_VER:-$LATEST_VER}"
    else
        read -p "👉 请手动输入版本号 (例如 1.21.50.07): " TARGET_VER
    fi

    TMP_ZIP="/tmp/mc_bds_${TARGET_VER}.zip"
    mkdir -p "$SERVER_DIR" "$DEFAULT_BACKUP_DIR"

    if ! download_server_zip "$TARGET_VER" "$TMP_ZIP"; then
        echo -e "${C_RED}❌ 下载失败,请检查网络后重试。${C_RESET}"
        rm -f "$TMP_ZIP"
        return
    fi

    echo "👉 正在解压核心程序..."
    unzip -q -o "$TMP_ZIP" -d "$SERVER_DIR"
    rm -f "$TMP_ZIP"

    # 权限
    chmod +x "$SERVER_DIR/bedrock_server"
    sudo chown -R "$RUN_USER:$RUN_USER" "$SERVER_DIR"

    # 硬件自适应优化
    optimize_server_properties "$SERVER_DIR/server.properties" "$CPU_CORES" "$TOTAL_MEM_MB"

    # 配置 systemd
    create_systemd_service "$SERVICE_NAME" "$SCREEN_NAME" "$SERVER_DIR" "$RUN_USER"

    echo "👉 正在启动服务..."
    sudo systemctl start "$SERVICE_NAME"
    sleep 3

    if systemctl is-active --quiet "$SERVICE_NAME"; then
        echo -e "\n${C_GREEN}🎉 恭喜!服务器已成功安装并运行!${C_RESET}"
        echo -e "🔹 默认端口: ${C_YELLOW}19132 (UDP)${C_RESET}"
        echo -e "🔹 服务管理: ${C_CYAN}sudo systemctl restart/stop $SERVICE_NAME${C_RESET}"
        echo -e "🔹 终端控制: ${C_CYAN}screen -r $SCREEN_NAME${C_RESET} (退出按 Ctrl+A 再按 D)"
    else
        echo -e "${C_RED}⚠️ 服务未能正常启动,请执行查看原因: sudo journalctl -u $SERVICE_NAME -n 30${C_RESET}"
    fi
}

# ---------------------------------------------------------
# 功能 B:全盘扫描现有服务端并接管
# ---------------------------------------------------------
action_scan_and_takeover() {
    echo -e "\n${C_YELLOW}${C_BOLD}=== 🔍 扫描现有 Minecraft 服务端 ===${C_RESET}"
    echo "正在扫描系统目录 ($HOME, /opt, /srv, /var, /root)..."
    
    local found_dirs=()
    while IFS= read -r line; do
        found_dirs+=("$(dirname "$line")")
    done < <(find "$HOME" /opt /srv /var 2>/dev/null -maxdepth 4 -type f -name "bedrock_server")

    if [ ${#found_dirs[@]} -eq 0 ]; then
        echo -e "${C_RED}❌ 未能扫描到任何包含 bedrock_server 的目录。${C_RESET}"
        return
    fi

    echo -e "💡 发现以下基岩版服务端目录:"
    local i=1
    for d in "${found_dirs[@]}"; do
        # 检查是否已被 systemd 纳管
        local status="未纳管"
        if grep -qs "$d" /etc/systemd/system/*.service 2>/dev/null; then
            status="${C_GREEN}已纳管${C_RESET}"
        fi
        echo -e "  [$i] $d ($status)"
        ((i++))
    done

    read -p "👉 请选择要接管/配置的序号 (输入 0 取消): " choice
    if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -le 0 ] || [ "$choice" -gt ${#found_dirs[@]} ]; then
        echo "已取消。"
        return
    fi

    local selected_dir="${found_dirs[$((choice-1))]}"
    echo -e "已选定目录: ${C_CYAN}$selected_dir${C_RESET}"

    read -p "👉 请为该服务指定一个名称 [默认: mc]: " INPUT_SVC
    local s_name="${INPUT_SVC:-mc}"

    # 1. 修复权限
    chmod +x "$selected_dir/bedrock_server"
    sudo chown -R "$RUN_USER:$RUN_USER" "$selected_dir"

    # 2. 硬件优化
    detect_hardware
    read -p "👉 是否根据当前硬件重新优化 server.properties 配置?(y/N): " opt_choice
    if [[ "$opt_choice" =~ ^[Yy]$ ]]; then
        optimize_server_properties "$selected_dir/server.properties" "$CPU_CORES" "$TOTAL_MEM_MB"
    fi

    # 3. 创建守护
    create_systemd_service "$s_name" "$s_name" "$selected_dir" "$RUN_USER"

    read -p "👉 是否立即启动该服务?(y/N): " start_choice
    if [[ "$start_choice" =~ ^[Yy]$ ]]; then
        sudo systemctl start "$s_name"
        sleep 2
        if systemctl is-active --quiet "$s_name"; then
            echo -e "${C_GREEN}✅ 服务 $s_name 已启动并纳管!${C_RESET}"
        else
            echo -e "${C_RED}⚠️ 启动遇到问题,请查看: sudo journalctl -u $s_name -n 30${C_RESET}"
        fi
    fi
}

# ---------------------------------------------------------
# 功能 C:无损更新现有服务
# ---------------------------------------------------------
action_update() {
    echo -e "\n${C_BLUE}${C_BOLD}=== 🔄 更新现有 Minecraft 服务 ===${C_RESET}"
    read -p "👉 请输入服务端所在目录 [默认: $DEFAULT_SERVER_DIR]: " INPUT_DIR
    local s_dir="${INPUT_DIR:-$DEFAULT_SERVER_DIR}"

    if [ ! -f "$s_dir/bedrock_server" ]; then
        echo -e "${C_RED}❌ 目录 $s_dir 下未找到 bedrock_server 程序!${C_RESET}"
        return
    fi

    read -p "👉 请输入 systemd 服务名 [默认: $DEFAULT_SERVICE_NAME]: " INPUT_SVC
    local s_name="${INPUT_SVC:-$DEFAULT_SERVICE_NAME}"
    local scr_name="$s_name"

    local latest_ver
    latest_ver=$(get_latest_version)
    if [ -n "$latest_ver" ]; then
        echo -e "💡 成功获取最新版本: ${C_GREEN}${latest_ver}${C_RESET}"
        read -p "👉 请输入目标版本号 (直接回车使用 $latest_ver): " INPUT_VER
        local target_ver="${INPUT_VER:-$latest_ver}"
    else
        read -p "👉 请手动输入目标版本号 (例如 1.21.50.07): " target_ver
    fi

    local was_active=false
    if systemctl is-active --quiet "$s_name"; then
        was_active=true
        echo "👉 正在向 screen 发送 'stop' 优雅停服保存地图..."
        screen -S "$scr_name" -p 0 -X stuff "$(printf 'stop\r')"
        local timeout=30
        while systemctl is-active --quiet "$s_name" && [ $timeout -gt 0 ]; do
            echo -n "."
            sleep 1
            ((timeout--))
        done
        echo
        if systemctl is-active --quiet "$s_name"; then
            echo "⚠️ 超时,强制停止 systemd 服务..."
            sudo systemctl stop "$s_name"
        fi
    fi

    # 备份
    mkdir -p "$DEFAULT_BACKUP_DIR"
    if [ -d "$s_dir/worlds" ]; then
        local bkp_file="$DEFAULT_BACKUP_DIR/mc_backup_$(date +%Y%m%d_%H%M%S).tar.gz"
        echo "📦 正在备份存档与配置到 $bkp_file ..."
        tar -czf "$bkp_file" -C "$s_dir" \
            worlds server.properties allowlist.json permissions.json valid_known_packs.json 2>/dev/null || true
    fi

    # 下载与增量替换
    local tmp_zip="/tmp/mc_update_${target_ver}.zip"
    local tmp_dir="/tmp/mc_update_extract_${target_ver}"
    
    if ! download_server_zip "$target_ver" "$tmp_zip"; then
        echo -e "${C_RED}❌ 下载失败!${C_RESET}"
        [ "$was_active" = true ] && sudo systemctl start "$s_name"
        rm -f "$tmp_zip"
        return
    fi

    mkdir -p "$tmp_dir"
    unzip -q -o "$tmp_zip" -d "$tmp_dir"

    echo "👉 增量更新文件(保留所有存档、配置与材质包)..."
    rsync -av --exclude='server.properties' \
              --exclude='allowlist.json' \
              --exclude='whitelist.json' \
              --exclude='permissions.json' \
              --exclude='valid_known_packs.json' \
              --exclude='worlds/' \
              --exclude='resource_packs/' \
              --exclude='behavior_packs/' \
              "$tmp_dir/" "$s_dir/"

    rm -f "$tmp_zip"
    rm -rf "$tmp_dir"

    chmod +x "$s_dir/bedrock_server"
    sudo chown -R "$RUN_USER:$RUN_USER" "$s_dir"

    echo "👉 重新启动服务..."
    sudo systemctl start "$s_name"
    sleep 3

    if systemctl is-active --quiet "$s_name"; then
        echo -e "${C_GREEN}✨ 更新成功!当前已更新至版本 $target_ver 并正常运行!${C_RESET}"
    else
        echo -e "${C_RED}⚠️ 服务重启失败,请查看日志: sudo journalctl -u $s_name -n 30${C_RESET}"
    fi
}

# ---------------------------------------------------------
# 主菜单交互界面
# ---------------------------------------------------------
main_menu() {
    clear
    echo -e "${C_BOLD}======================================================${C_RESET}"
    echo -e "${C_BOLD}${C_GREEN}     ⛏️  Minecraft 基岩版 (BDS) 智能管理运维箱${C_RESET}"
    echo -e "${C_BOLD}======================================================${C_RESET}"
    echo -e " ${C_CYAN}[1]${C_RESET} 🖥️  查看服务器硬件配置与环境"
    echo -e " ${C_CYAN}[2]${C_RESET} 🚀 一键全新搭建 (自动按硬件优化配置)"
    echo -e " ${C_CYAN}[3]${C_RESET} 🔍 扫描现有服务端并接管 (配置守护/调优)"
    echo -e " ${C_CYAN}[4]${C_RESET} 🔄 无损更新现有服务器核心"
    echo -e " ${C_CYAN}[5]${C_RESET} 🎮 进入当前正在运行的控制台 (screen)"
    echo -e " ${C_CYAN}[0]${C_RESET} 🚪 退出脚本"
    echo -e "${C_BOLD}======================================================${C_RESET}"
    read -p "👉 请选择操作编号 [0-5]: " OPTION

    case "$OPTION" in
        1)
            detect_hardware
            read -p "按回车键返回菜单..."
            main_menu
            ;;
        2)
            action_install
            ;;
        3)
            action_scan_and_takeover
            ;;
        4)
            action_update
            ;;
        5)
            read -p "请输入 screen 会话名称 [默认: mc]: " SNAME
            screen -r "${SNAME:-mc}"
            ;;
        0)
            echo "已退出。"
            exit 0
            ;;
        *)
            echo "无效选项。"
            sleep 1
            main_menu
            ;;
    esac
}

# 执行入口
main_menu