侧边栏壁纸
博主昵称
Yi

Termux的Qemu启动脚本

2025年08月09日 12阅读 0评论 0点赞

123456888.webp

#!/data/data/com.termux/files/usr/bin/bash
# QCOW2镜像管理器

# ====================== #
#   ANSI标准色编码
# ====================== #
RED='\033[1;31m'         # 错误/警告
GREEN='\033[1;32m'       # 成功
YELLOW='\033[1;33m'      # 提示
BLUE='\033[1;34m'        # 标题
PURPLE='\033[1;35m'      # 高亮
CYAN='\033[1;36m'        # 数据
NC='\033[0m'             # 重置颜色

# 安全分隔线
HR_LINE="================================================================"

# ====================== #
#   全局配置
# ====================== #
selected_image=""
EFI_FIRMWARE="./QEMU_EFI.fd"
EXPANDED_FIRMWARE="./flash.img"
QEMU_BINARY="./qemu-system-aarch64"
KEYMAP_PATH="/usr/share/qemu/keymaps/en-us"
DEFAULT_IMAGE_URL="https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-arm64.qcow2"  # 官方ARM64镜像[7](@ref)

# ====================== #
#   依赖检查
# ====================== #
check_dependencies() {
    # 新增wget依赖检查
    if ! command -v wget &> /dev/null; then
        echo -e "${YELLOW}▶ 安装wget下载工具...${NC}"
        pkg install wget -y
    fi
    
    if ! command -v qemu-img &> /dev/null; then
        echo -e "${RED}✗ 错误: 未找到 qemu-img 命令!${NC}" >&2
        echo -e "${YELLOW}请执行: pkg install qemu-utils${NC}" >&2
        exit 1
    fi
    
    if [ ! -f "$QEMU_BINARY" ]; then
        echo -e "${YELLOW}⚠ 警告: QEMU可执行文件未找到${NC}"
        echo -e "${CYAN}请确保 ${PURPLE}$QEMU_BINARY ${CYAN}存在于当前目录${NC}"
    fi
    
    if [ ! -f "$KEYMAP_PATH" ]; then
        echo -e "${YELLOW}⚠ 警告: 键盘映射文件缺失 (路径: $KEYMAP_PATH)${NC}"
        echo -e "${YELLOW}   这可能导致启动时出现键盘映射警告${NC}"
    fi
}

# ====================== #
#   镜像自动下载功能
# ====================== #
download_default_image() {
    local image_name="debian-12-arm64.qcow2"
    
    echo -e "\n${YELLOW}▶ 未找到可用镜像,开始下载Debian 12 ARM64官方镜像...${NC}"
    echo -e "${CYAN}镜像源: ${PURPLE}$DEFAULT_IMAGE_URL${NC}"
    echo -e "${YELLOW}此过程可能需要5-10分钟,请保持网络连接...${NC}"
    
    # 显示下载进度条
    wget --progress=dot:giga "$DEFAULT_IMAGE_URL" -O "$image_name"
    
    if [ $? -eq 0 ]; then
        echo -e "\n${GREEN}✓ 镜像下载成功: ${CYAN}$image_name${NC}"
        echo -e "${YELLOW}镜像大小: ${CYAN}$(du -h $image_name | awk '{print $1}')${NC}"
        return 0
    else
        echo -e "${RED}✗ 镜像下载失败!${NC}"
        echo -e "${YELLOW}可能原因:"
        echo "  1. 网络连接不稳定"
        echo "  2. 磁盘空间不足"
        echo "  3. 镜像源不可用"
        return 1
    fi
}

# ====================== #
#   镜像选择器
# ====================== #
select_qcow2_image() {
    local images=()
    echo -e "\n${BLUE}=== 发现镜像文件 ===${NC}"
    echo -e "${HR_LINE}"
    
    while IFS= read -r file; do
        images+=("$file")
    done < <(find . -maxdepth 1 -type f -name '*.qcow2' | sed 's|^\./||')

    if [ ${#images[@]} -eq 0 ]; then
        echo -e "${YELLOW}⚠ 未找到.qcow2镜像文件!${NC}"
        
        # 自动下载默认镜像
        download_default_image
        if [ $? -ne 0 ]; then
            return 1
        fi
        
        # 重新扫描下载的镜像
        while IFS= read -r file; do
            images+=("$file")
        done < <(find . -maxdepth 1 -type f -name '*.qcow2' | sed 's|^\./||')
        
        if [ ${#images[@]} -eq 0 ]; then
            echo -e "${RED}✗ 下载后仍未找到镜像文件!${NC}"
            return 1
        fi
    fi

    for i in "${!images[@]}"; do
        printf "${PURPLE}%2d) ${CYAN}%s${NC}\n" $((i+1)) "${images[$i]}"
    done

    read -p "$(echo -e "${BLUE}➤ 请选择镜像 [1-${#images[@]}]:${NC} ")" choice
    if [[ $choice =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#images[@]} ]; then
        selected_image="${images[$((choice-1))]}"
        echo -e "${GREEN}✓ 已选择: ${CYAN}$selected_image${NC}"
        return 0
    else
        echo -e "${RED}✗ 无效选择!${NC}"
        return 1
    fi
}

# ====================== #
#   准备UEFI固件
# ====================== #
prepare_firmware() {
    if [ -f "$EXPANDED_FIRMWARE" ]; then
        echo -e "${GREEN}✓ 使用已存在的扩展固件镜像${NC}"
        return 0
    fi

    echo -e "${YELLOW}▶ 创建扩展固件镜像...${NC}"
    
    if [ ! -f "$EFI_FIRMWARE" ]; then
        echo -e "${YELLOW}│ 下载UEFI固件...${NC}"
        wget -q https://releases.linaro.org/components/kernel/uefi-linaro/latest/release/qemu64/QEMU_EFI.fd -O "$EFI_FIRMWARE"
    fi
    
    # 创建64MB镜像
    dd if=/dev/zero of="$EXPANDED_FIRMWARE" bs=1M count=64 status=none
    dd if="$EFI_FIRMWARE" of="$EXPANDED_FIRMWARE" conv=notrunc status=none
}

# ====================== #
#   快照管理功能
# ====================== #
create_snapshot() {
    select_qcow2_image || return 1
    local image="$selected_image"

    snapshot_name="snap_$(date +"%Y%m%d_%H%M%S")"
    read -p "$(echo -e "${YELLOW}➤ 输入快照描述 ${CYAN}(可选):${NC} ")" description
    [ -n "$description" ] && snapshot_name+="_$description"

    qemu-img snapshot -c "$snapshot_name" "$image"
    if [ $? -eq 0 ]; then
        echo -e "${GREEN}✓ 时间点快照创建成功: ${CYAN}$snapshot_name${NC}"
    else
        echo -e "${RED}✗ 创建失败!${YELLOW}可能原因:${NC}"
        echo "  1. 镜像非qcow2格式"
        echo "  2. 磁盘空间不足"
    fi
}

list_snapshots() {
    select_qcow2_image || return 1
    local image="$selected_image"
    
    echo -e "\n${BLUE}=== ${CYAN}$image 的快照链 ===${NC}"
    echo -e "${HR_LINE}"
    
    snap_list=$(qemu-img snapshot -l "$image" 2>/dev/null)
    if [ -z "$snap_list" ]; then
        echo -e "${YELLOW}⚠ 未找到快照!${NC}"
        return
    fi
    
    echo -e "${PURPLE}序号  ID  标签                创建时间${NC}"
    counter=1
    while IFS= read -r line; do
        if [[ $line =~ ^[0-9] ]]; then
            snap_id=$(echo "$line" | awk '{print $1}')
            snap_tag=$(echo "$line" | awk '{print $2}')
            snap_date=$(echo "$line" | awk '{print $3, $4}')
            printf "${CYAN}%-4s %-4s %-20s %s${NC}\n" "$counter" "$snap_id" "$snap_tag" "$snap_date"
            ((counter++))
        fi
    done <<< "$snap_list"

    echo -e "\n${BLUE}=== 操作选项 ===${NC}"
    echo -e "${YELLOW}1) 回滚到快照${NC}"
    echo -e "${YELLOW}2) 删除快照${NC}"
    echo -e "${YELLOW}3) 返回主菜单${NC}"
    read -p "$(echo -e "${BLUE}➤ 请选择:${NC} ")" action

    case $action in
        1) revert_snapshot "$image" ;;
        2) delete_snapshot "$image" ;;
        *) return ;;
    esac
}

revert_snapshot() {
    local image="$1"
    read -p "$(echo -e "${YELLOW}➤ 输入要回滚的快照序号:${NC} ")" snap_num

    if ! [[ $snap_num =~ ^[0-9]+$ ]]; then
        echo -e "${RED}✗ 错误: 请输入数字序号!${NC}" >&2
        return 1
    fi

    snap_tag=$(qemu-img snapshot -l "$image" 2>/dev/null | awk "NR==$((snap_num+1)) {print \$2}")
    if [ -z "$snap_tag" ]; then
        echo -e "${RED}✗ 错误: 无效的快照序号!${NC}" >&2
        return 1
    fi

    if lsof "$image" &> /dev/null; then
        echo -e "${RED}✗ 错误: 镜像正在使用中,请先停止虚拟机!${NC}" >&2
        return 1
    fi

    qemu-img snapshot -a "$snap_tag" "$image"
    if [ $? -eq 0 ]; then
        echo -e "${GREEN}✓ 已回滚到快照: ${CYAN}$snap_tag${NC}"
    else
        echo -e "${RED}✗ 回滚失败!${YELLOW}可能原因:${NC}"
        echo "  1. 快照元数据损坏"
        echo "  2. 磁盘权限不足"
    fi
}

delete_snapshot() {
    local image="$1"
    
    echo -e "\n${RED}=== ${CYAN}$image 的快照管理 ===${NC}"
    echo -e "${HR_LINE}"
    
    snap_list=$(qemu-img snapshot -l "$image" 2>/dev/null)
    if [ -z "$snap_list" ]; then
        echo -e "${YELLOW}⚠ 此镜像没有快照!${NC}" >&2
        return 1
    fi
    
    echo -e "${PURPLE}序号  ID  标签                创建时间${NC}"
    counter=1
    declare -a snap_tags
    
    while IFS= read -r line; do
        if [[ $line =~ ^[0-9] ]]; then
            snap_id=$(echo "$line" | awk '{print $1}')
            snap_tag=$(echo "$line" | awk '{print $2}')
            snap_date=$(echo "$line" | awk '{print $3, $4}')
            printf "${CYAN}%-4s %-4s %-20s %s${NC}\n" "$counter" "$snap_id" "$snap_tag" "$snap_date"
            snap_tags[$counter]="$snap_tag"
            ((counter++))
        fi
    done <<< "$snap_list"

    if [ $counter -eq 1 ]; then
        echo -e "${YELLOW}⚠ 未找到有效快照!${NC}" >&2
        return 1
    fi

    read -p "$(echo -e "${YELLOW}➤ 输入要删除的快照序号:${NC} ")" snap_num
    if ! [[ $snap_num =~ ^[0-9]+$ ]] || [ "$snap_num" -lt 1 ] || [ "$snap_num" -ge $counter ]; then
        echo -e "${RED}✗ 错误: 无效序号!${NC}" >&2
        return 1
    fi

    snap_tag="${snap_tags[$snap_num]}"
    if [ -z "$snap_tag" ]; then
        echo -e "${RED}✗ 错误: 无法解析快照标签!${NC}" >&2
        return 1
    fi

    # 危险操作二次确认
    echo -e "\n${RED}=== 危险操作警告 ===${NC}"
    echo -e "${RED}您将要删除快照: ${PURPLE}$snap_tag${NC}"
    read -p "$(echo -e "${RED}❗ 确认删除? (输入${CYAN}CONFIRM${RED}全大写确认):${NC} ")" confirm
    [[ "$confirm" != "CONFIRM" ]] && return

    qemu-img snapshot -d "$snap_tag" "$image"
    if [ $? -eq 0 ]; then
        echo -e "${GREEN}✓ 快照已删除: ${CYAN}$snap_tag${NC}"
    else
        echo -e "${RED}✗ 删除失败!${YELLOW}可能原因:${NC}"
        echo "  1. 快照被虚拟机锁定"
        echo "  2. 磁盘空间不足"
        echo "  3. 快照链损坏"
    fi
}

# ====================== #
#   镜像扩容
# ====================== #
resize_image() {
    select_qcow2_image || return 1
    local image="$selected_image"

    read -p "$(echo -e "${YELLOW}➤ 输入扩容大小 ${CYAN}(示例: +10G 或 20G):${NC} ")" size
    qemu-img resize "$image" "$size"
    if [ $? -eq 0 ]; then
        echo -e "\n${GREEN}✓ 镜像已扩容!${YELLOW}请在虚拟机内执行:${NC}"
        echo -e "${CYAN}  1. 扩展分区: ${PURPLE}growpart /dev/sda 1"
        echo -e "${CYAN}  2. 调整文件系统: ${PURPLE}resize2fs /dev/sda1${NC}"
    else
        echo -e "${RED}✗ 扩容失败!${YELLOW}可能原因:${NC}"
        echo "  1. 磁盘空间不足"
        echo "  2. 镜像格式限制"
    fi
}

# ====================== #
#   启动虚拟机
# ====================== #
start_vm() {
    select_qcow2_image || return 1
    local image="$selected_image"
    
    prepare_firmware
    
    # 严格验证文件存在性
    if [ ! -f "$EXPANDED_FIRMWARE" ]; then
        echo -e "${RED}✗ 错误: 固件文件 $EXPANDED_FIRMWARE 不存在!${NC}"
        return 1
    fi
    if [ ! -f "$image" ]; then
        echo -e "${RED}✗ 错误: 镜像文件 $image 不存在!${NC}"
        return 1
    fi

    # 显示配置信息
    echo -e "\n${BLUE}=== 启动虚拟机 ===${NC}"
    echo -e "${HR_LINE}"
    echo -e "${GREEN}镜像: ${CYAN}$image${NC}"
    echo -e "${GREEN}固件: ${CYAN}$EXPANDED_FIRMWARE${NC}"
    echo -e "${YELLOW}端口映射:${NC}"
    echo -e "  ${CYAN}SSH:   22 → 22${NC}"
    echo -e "  ${CYAN}HTTP:   80 → 80${NC}"
    echo -e "  ${CYAN}HTTPS: 443 → 443${NC}"
    echo -e "${HR_LINE}"
    echo -e "${YELLOW}按 Ctrl+C 停止虚拟机${NC}\n"
    
    "$QEMU_BINARY" \
        -global virtio-blk.device_activation_delay_ms=1500 \
        -machine virt,accel=kvm,gic-version=3 \
        -m 4096 \
        -cpu host \
        -smp 4 \
        -drive if=pflash,format=raw,file="$EXPANDED_FIRMWARE" \
        -drive file="$image",if=virtio,format=qcow2 \
        -netdev user,id=net0,hostfwd=tcp::22-:22,hostfwd=tcp::80-:80,hostfwd=tcp::443-:443,hostfwd=tcp::9876-:9876 \
        -device virtio-net-device,netdev=net0 \
        -nographic \
        -serial mon:stdio
}

# ====================== #
#   主菜单
# ====================== #
main_menu() {
    clear
    echo -e "${BLUE}${HR_LINE}"
    echo -e " QCOW2 镜像管理器 "
    echo -e "${HR_LINE}${NC}"
    echo -e "${GREEN}1) 创建内部快照${NC}"
    echo -e "${GREEN}2) 列出/管理快照${NC}"
    echo -e "${GREEN}3) 镜像扩容${NC}"
    echo -e "${GREEN}4) 启动虚拟机${NC}"
    echo -e "${RED}5) 退出${NC}"
    echo -e "${HR_LINE}"
    read -p "$(echo -e "${BLUE}➤ 选择操作 [1-5]:${NC} ")" choice

    case $choice in
        1) create_snapshot ;;
        2) list_snapshots ;;
        3) resize_image ;;
        4) start_vm ;;
        5) exit 0 ;;
        *) echo -e "${RED}✗ 无效选项!${NC}"; sleep 1 ;;
    esac

    read -p "$(echo -e "${YELLOW}⏎ 按回车继续...${NC}")"
    main_menu
}

check_dependencies
main_menu

注意:脚本自动开启kvm虚拟化,如果手机不支持kvm内核手动删除virt,accel=kvm,参数

主要功能:

  1. 镜像自动下载功能

    • 当检测不到可用镜像时,自动下载Debian 12 ARM64官方镜像
    • 支持断点续传和进度显示
    • 提供镜像源验证和下载失败处理
  2. 镜像管理核心功能

    • 智能镜像选择器:自动扫描当前目录的.qcow2文件
    • 详细的镜像信息展示(文件名、大小等)
  3. 快照管理系统

    • 创建带时间戳和描述的快照
    • 可视化快照列表(显示ID、标签、创建时间)
    • 快照回滚与删除功能(含危险操作二次确认)
    • 快照链完整性检查
  4. 镜像扩容功能

    • 支持灵活指定扩容大小(如+10G或20G)
    • 提供扩容后的操作指南(分区和文件系统扩展命令)
  5. 虚拟机启动功能

    • 自动准备UEFI固件(支持下载和扩展)
    • 预配置端口映射(SSH 22、HTTP 80、HTTPS 443)
    • 优化的QEMU启动参数(4核CPU/4GB内存/KVM加速)
    • 详细的启动配置预览
  6. 增强特性

    • ANSI彩色终端界面(6种状态颜色)
    • 全面的依赖检查(qemu-img/wget等)
    • 用户友好的错误处理和建议
    • 响应式菜单界面
    • 兼容性优化(Termux/ARM64环境)
  7. 安全机制

    • 镜像使用状态检测(防止操作被占用的镜像)
    • 输入验证(数字选择/危险操作确认)
    • 磁盘空间检查
  8. 维护功能

    • 固件自动下载和准备
    • 键盘映射检测
    • QEMU二进制验证
0

—— 评论区 ——

昵称
邮箱
网址
取消
博主栏壁纸
博主头像 Yi

14 文章数
9 标签数
2 评论量