找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
广告投放联系QQ68610888广告投放联系QQ68610888
广告投放联系QQ68610888广告投放联系QQ68610888
查看: 922|回复: 9

OEC armbian系统迁移到硬盘脚本,可双系统启动

[复制链接]
发表于 2025-12-10 15:08 | 显示全部楼层 |阅读模式
本帖最后由 clxfire 于 2025-12-10 15:14 编辑

https://www.right.com.cn/forum/forum.php?mod=attachment&aid=NzY3NTQxfDBmOWEyMmYzfDE3ODc0ODI1OTd8MHw%3D

AI写的,注意备份硬盘数据后再使用!




#!/bin/bash
# armbian系统迁移到SSD脚本 (可双启动)
# 警告:此操作可能会修改目标磁盘分区,请先备份重要数据!
# 优先使用≈32GiB分区;额外分区按UUID自动写入fstab

set -euo pipefail

# 检查root权限
if [ "$(id -u)" -ne 0 ]; then
        echo "错误:必须使用sudo或root权限运行此脚本"
        exit 1
fi

# 智能设备检测
detect_devices() {
        CURRENT_ROOT=$(findmnt / -o SOURCE -n)
        CURRENT_UUID=$(findmnt / -o UUID -n)

        if [[ $CURRENT_ROOT =~ "mmcblk" ]]; then
                EMMC_DEVICE="/dev/$(lsblk -no NAME ${CURRENT_ROOT%p*})"
                EMMC_ROOT_PART="$CURRENT_ROOT"
                EMMC_ROOT_UUID="$CURRENT_UUID"
                echo "检测到eMMC根分区: $EMMC_ROOT_PART (UUID: $EMMC_ROOT_UUID)"
        else
                echo "错误:当前系统不是从eMMC启动!"
                exit 1
        fi

        # 获取候选SSD设备,排除 mmcblk 和 zram,并显示磁盘容量
        mapfile -t SSD_CANDIDATES < <(
                lsblk -d -n -o NAME,ROTA,SIZE |
                        awk '$2==0 && $1 !~ /^mmcblk/ && $1 !~ /^zram/ {print $1 ":" $3}'
        )

        if [ ${#SSD_CANDIDATES[@]} -eq 0 ]; then
                echo "错误:未检测到SSD设备!"
                exit 1
        fi

        local i=0

        if [ ${#SSD_CANDIDATES[@]} -gt 1 ]; then
                echo "检测到多个SSD设备:"
                for i in "${!SSD_CANDIDATES[@]}"; do
                        echo "$((i + 1))). /dev/$(echo ${SSD_CANDIDATES[$i]} | cut -d: -f1)  容量: $(echo "${SSD_CANDIDATES[$i]}" | cut -d: -f2)"
                done

                while true; do
                        read -r -p "请选择要迁移的目标SSD编号: " choice
                        # 检查是否为正整数且在范围内
                        if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$i" ]; then
                                i=$((choice - 1))
                                break
                        else
                                echo "输入无效,请输入 1 到 $((i + 1)) 之间的数字。"
                        fi
                done
        fi

        SIZE=$(echo "${SSD_CANDIDATES[$i]}" | cut -d: -f2)
        TARGET_DISK="/dev/$(echo "${SSD_CANDIDATES[$i]}" | cut -d: -f1)"
        echo "检测到SSD设备: $TARGET_DISK  $SIZE"
        MOUNT_DIR="/mnt/ssd_migrate"

        if mountpoint -q "$MOUNT_DIR"; then
                umount "$MOUNT_DIR"
        fi
}

# 显示警告信息
show_warning() {
        echo "========================================"
        echo "      网心云 OEC armbian系统迁移SSD脚本"
        echo "========================================"
        echo "当前系统根分区: $EMMC_ROOT_PART (UUID: $EMMC_ROOT_UUID)"
        echo "目标磁盘: $TARGET_DISK 容量: $SIZE"
        echo "挂载目录: $MOUNT_DIR"
        echo
        echo "脚本行为:"
        echo "  - 优先查找目标磁盘上已有约32GiB的分区并格式化它作为主分区"
        echo "  - 若无此分区,则在尾部创建32GiB主分区并格式化"
        echo "  - 若空闲空间不足32GiB则退出,不会修改其他分区"
        echo "  - 自动为目标盘其它已有文件系统分区按UUID添加 fstab 挂载配置"
        echo "  - 如果仍有剩余空间,提示是否新建分区格式化添加 fstab 挂载配置"
        echo "警告:此操作将格式化选定的分区,目标分区上的数据将被清除!"
        echo "========================================"

        read -p "确认继续操作? (输入大写的 'YES' 继续): " confirm
        if [ "$confirm" != "YES" ]; then
                echo "操作已取消"
                exit 0
        fi
}

# 将GiB转换为字节
gib_to_bytes() { echo $(($1 * 1024 * 1024 * 1024)); }

# 准备目标磁盘(优先从磁盘头部的空闲区创建 32GiB 分区;若无分区则从 1MiB 开始)
prepare_disk() {
        echo -e "\n[1/8] 检查目标磁盘是否已有约32GiB分区..."
        TARGET_SIZE_BYTES=$(gib_to_bytes 32)
        LOWER_BOUND=$((TARGET_SIZE_BYTES - $(gib_to_bytes 1)))
        UPPER_BOUND=$((TARGET_SIZE_BYTES + $(gib_to_bytes 1)))

        FOUND_PART=""
        while read -r name size type; do
                if [ "$type" = "part" ]; then
                        if [ "$size" -ge "$LOWER_BOUND" ] && [ "$size" -le "$UPPER_BOUND" ]; then
                                FOUND_PART="$name"
                                break
                        fi
                fi
        done < <(lsblk -b -r -n -o NAME,SIZE,TYPE -p "$TARGET_DISK")

        if [ -n "$FOUND_PART" ]; then
                echo "找到符合大小的分区: $FOUND_PART (约32GiB),将格式化此分区作为主分区"
                TARGET_PART="$FOUND_PART"
                NEW_UUID=$(format_target_part "$TARGET_PART" "ROOTFS") || {
                        echo "格式化 $TARGET_PART 失败或被取消,脚本退出。"
                        exit 1
                }
                return 0
        fi

        echo "未找到约32GiB的现有分区,扫描空闲区以寻找从头部开始的连续32GiB空间..."
        DISK_SIZE_BYTES=$(blockdev --getsize64 "$TARGET_DISK")
        PART_SUM=0
        while read -r name size type; do
                if [ "$type" = "part" ]; then
                        PART_SUM=$((PART_SUM + size))
                fi
        done < <(lsblk -b -r -n -o NAME,SIZE,TYPE -p "$TARGET_DISK")

        FREE_BYTES=$((DISK_SIZE_BYTES - PART_SUM))
        echo "磁盘总大小: $DISK_SIZE_BYTES 字节, 已分区总和: $PART_SUM 字节, 估算空闲: $FREE_BYTES 字节"

        if [ "$FREE_BYTES" -lt "$TARGET_SIZE_BYTES" ]; then
                echo "错误:目标磁盘空闲空间不足 32GiB,无法创建分区。脚本退出。"
                exit 1
        fi

        # 若无分区表则创建 GPT(避免 mkpart 失败)
        parted_output=$(parted -s "$TARGET_DISK" print 2>/dev/null || true)
        if echo "$parted_output" | grep -qi "Partition Table: unknown"; then
                echo "检测到无分区表,正在为 $TARGET_DISK 创建 GPT 分区表(mklabel gpt)..."
                parted -s "$TARGET_DISK" mklabel gpt
                partprobe "$TARGET_DISK"
                sleep 1
        fi

        # 以 MiB 为单位
        size_mib=$((32 * 1024))
        disk_size_mib=$((DISK_SIZE_BYTES / 1024 / 1024))

        # 解析 parted 的 free 段(兼容多数输出),收集 Start/End
        mapfile -t free_ranges < <(parted -s "$TARGET_DISK" unit MiB print free 2>/dev/null |
                awk '/[Ff]ree/ && /MiB/ {
            start=$2; end=$3;
            gsub("MiB","",start); gsub("MiB","",end);
            if (start+0==start && end+0==end) {
                printf "%d %d\n", int(start), int(end)
            }
        }')

        chosen_start=0
        chosen_end=0

        # 特殊情况:磁盘上没有任何分区(PART_SUM==0 或 free_ranges 为空)
        if [ "$PART_SUM" -eq 0 ] || [ "${#free_ranges[@]}" -eq 0 ]; then
                # 从头部开始:保留前 1MiB(对齐),起点设为 1MiB
                start_mib=1
                end_mib=$((start_mib + size_mib))
                # 校验是否越界
                if [ "$end_mib" -gt "$disk_size_mib" ]; then
                        echo "错误:磁盘太小,无法从头部创建 32GiB 分区"
                        exit 1
                fi
                chosen_start=$start_mib
                chosen_end=$end_mib
                echo "磁盘无分区,选择从头部创建分区: ${chosen_start}MiB - ${chosen_end}MiB (约32GiB)"
        else
                # 在 free_ranges 中选择最早出现且长度 >= size_mib 的区间(从头部优先)
                for fr in "${free_ranges[@]:-}"; do
                        start_mib=$(echo "$fr" | awk '{print $1}')
                        end_mib=$(echo "$fr" | awk '{print $2}')
                        seg_size=$((end_mib - start_mib))
                        if [ "$seg_size" -ge "$size_mib" ]; then
                                chosen_start=$start_mib
                                chosen_end=$((chosen_start + size_mib))
                                echo "找到从头部优先的空闲区: ${start_mib}MiB - ${end_mib}MiB (大小 ${seg_size}MiB),将在其起点创建 ${size_mib}MiB 分区"
                                break
                        fi
                done

                if [ "$chosen_start" -eq 0 ]; then
                        echo "错误:未在磁盘任意空闲区(从头部开始扫描)找到连续 32GiB 空间。脚本不会在尾部回退创建,已退出。"
                        exit 1
                fi
        fi

        # 创建分区
        parted -s "$TARGET_DISK" mkpart primary ext4 "${chosen_start}MiB" "${chosen_end}MiB"
        partprobe "$TARGET_DISK"
        sleep 2

        # 识别新分区(重试以等待内核识别)
        local NEW_PART=""
        for attempt in 1 2 3 4 5; do
                while read -r name size type; do
                        if [ "$type" = "part" ]; then
                                if [ "$size" -ge "$LOWER_BOUND" ] && [ "$size" -le "$UPPER_BOUND" ]; then
                                        NEW_PART="$name"
                                        break 2
                                fi
                        fi
                done < <(lsblk -b -r -n -o NAME,SIZE,TYPE -p "$TARGET_DISK")
                sleep 1
                partprobe "$TARGET_DISK" 2>/dev/null || true
        done

        if [ -z "$NEW_PART" ]; then
                echo "错误:创建分区后未能找到对应的32GiB分区"
                exit 1
        fi

        TARGET_PART="$NEW_PART"
        echo "创建并识别到新分区: $TARGET_PART"
        NEW_UUID=$(format_target_part "$TARGET_PART" "ROOTFS") || {
                echo "格式化 $TARGET_PART 失败或被取消,脚本退出。"
                exit 1
        }
}

# 格式化指定分区并返回新 UUID
# 用法: NEW_UUID=$(format_target_part "/dev/sda2" "ROOTFS") || handle_error
format_target_part() {
        local part="$1"
        local label="${2:-ROOTFS}"

        if [ -z "$part" ]; then
                echo "format_target_part: 缺少分区参数" >&2
                return 1
        fi

        if [ ! -b "$part" ]; then
                echo "format_target_part: 未找到设备 $part" >&2
                return 1
        fi

        # 显示信息并确认(提示信息走 stderr)
        local size_hr
        size_hr=$(lsblk -no SIZE -p "$part" 2>/dev/null || true)
        echo -e "\n准备格式化: $part" >&2
        echo "大小: ${size_hr:-未知}  标签: $label" >&2
        echo "警告:此操作将清除 $part 上的所有数据!" >&2

        read -r -p "确认格式化 (输入大写 YES 或再次输入分区路径以确认): " confirm
        if [ "$confirm" != "YES" ] && [ "$confirm" != "$part" ]; then
                echo "format_target_part: 用户取消格式化" >&2
                return 2
        fi

        # 执行格式化
        if ! mkfs.ext4 -F -L "$label" "$part" >/dev/null 2>&1; then
                echo "format_target_part: mkfs.ext4 失败 $part" >&2
                return 1
        fi
        sync

        # 等待并读取 UUID
        local retries=6
        local new_uuid=""
        while [ $retries -gt 0 ]; do
                new_uuid=$(blkid -s UUID -o value "$part" 2>/dev/null || true)
                if [ -n "$new_uuid" ]; then
                        # 仅输出 UUID 到 stdout(便于调用方捕获)
                        printf "%s" "$new_uuid"
                        return 0
                fi
                sleep 1
                retries=$((retries - 1))
                partprobe "$(dirname "$part")" 2>/dev/null || true
                udevadm settle 2>/dev/null || true
        done

        echo "format_target_part: 无法获取新分区 UUID" >&2
        return 1
}

# 复制系统文件
copy_system() {
        echo -e "\n[4/8] 挂载目标分区..."
        mkdir -p "$MOUNT_DIR"
        mount "$TARGET_PART" "$MOUNT_DIR"

        echo -e "\n[5/8] 复制系统文件(可能需要较长时间)..."
        rsync -aAXv --progress \
                --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found"} \
                / "$MOUNT_DIR"

        echo -e "\n[6/8] 处理boot目录..."
        if [ -d "$MOUNT_DIR/boot" ]; then
                rsync -aAXv /boot/ "$MOUNT_DIR/boot/"
        else
                mkdir -p "$MOUNT_DIR/boot"
                rsync -aAXv /boot/ "$MOUNT_DIR/boot/"
        fi
}

# 自动为目标盘剩余已有分区添加 fstab;若无分区则使用全部剩余空间创建新分区并格式化
setup_extra_mounts() {
        echo -e "\n[7/8] 配置剩余分区的自动挂载 (按分区名),若无分区则使用全部剩余空间创建新分区并格式化..."

        mkdir -p "$MOUNT_DIR/mnt/storage"

        # 列出目标磁盘的分区(绝对路径),排除主系统分区
        mapfile -t parts < <(lsblk -b -r -n -o NAME,TYPE -p "$TARGET_DISK" | awk '$2=="part" {print $1}')

        filtered_parts=()
        for p in "${parts[@]:-}"; do
                if [ -n "${TARGET_PART:-}" ] && [ "$p" = "$TARGET_PART" ]; then
                        continue
                fi
                filtered_parts+=("$p")
        done

        # helper: 解析 parted free 段并选择第一个有效 free 段(返回 start end MiB 或空)
        find_free_range() {
                local disk="$1"
                local disk_size_bytes
                disk_size_bytes=$(blockdev --getsize64 "$disk")
                local disk_size_mib=$((disk_size_bytes / 1024 / 1024))

                mapfile -t free_ranges < <(parted -s "$disk" unit MiB print free 2>/dev/null |
                        grep -i "free" | sed -E 's/,//g' | awk '{
                n=0;
                for(i=1;i<=NF;i++){
                    if($i ~ /[0-9]+(\.[0-9]+)?MiB/){
                        gsub("MiB","",$i);
                        printf "%s ", $i;
                        n++;
                        if(n==2) break;
                    }
                }
                if(n==2) print "";
            }')
                for fr in "${free_ranges[@]:-}"; do
                        start_mib=$(echo "$fr" | awk '{print $1}')
                        end_mib=$(echo "$fr" | awk '{print $2}')
                        if ! [[ "$start_mib" =~ ^[0-9]+([.][0-9]+)?$ ]] || ! [[ "$end_mib" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
                                continue
                        fi
                        start_mib=${start_mib%.*}
                        end_mib=${end_mib%.*}
                        if [ "$start_mib" -gt "$end_mib" ]; then
                                tmp=$start_mib
                                start_mib=$end_mib
                                end_mib=$tmp
                        fi
                        if [ "$end_mib" -le "$start_mib" ]; then
                                continue
                        fi
                        if [ "$start_mib" -lt 1 ]; then
                                start_mib=1
                        fi
                        if [ "$end_mib" -le "$start_mib" ]; then
                                continue
                        fi
                        # 返回第一个有效段
                        printf "%s %s" "$start_mib" "$end_mib"
                        return 0
                done

                # 回退:从 1MiB 到磁盘尾部
                if [ "$disk_size_mib" -gt 1 ]; then
                        printf "1 %s" "$disk_size_mib"
                        return 0
                fi
                return 1
        }

        # helper: 创建分区、格式化并写入 fstab(参数: disk start_mib end_mib)
        create_and_register_partition() {
                local disk="$1"
                local start_mib="$2"
                local end_mib="$3"
                echo "将在 ${disk} 上创建新分区: ${start_mib}MiB - ${end_mib}MiB"

                read -r -p "确认创建新分区 (输入大写 YES 继续): " confirm

                if [ "$confirm" != "YES" ]; then
                        echo "format_target_part: 用户取消格式化" >&2
                        return 2
                fi

                parted -s "$disk" mkpart primary ext4 "${start_mib}MiB" "${end_mib}MiB"

                partprobe "$disk" 2>/dev/null || true
                udevadm settle 2>/dev/null || true
                sleep 2

                local segment_bytes=$(((end_mib - start_mib) * 1024 * 1024))
                local NEW_PART=""
                for attempt in 1 2 3 4 5; do
                        lower=$((segment_bytes - 1024 * 1024))
                        upper=$((segment_bytes + 1024 * 1024))
                        while read -r name size type; do
                                if [ "$type" = "part" ]; then
                                        if [ "$size" -ge "$lower" ] && [ "$size" -le "$upper" ]; then
                                                NEW_PART="$name"
                                                break 2
                                        fi
                                fi
                        done < <(lsblk -b -r -n -o NAME,SIZE,TYPE -p "$disk")
                        partprobe "$disk" 2>/dev/null || true
                        udevadm settle 2>/dev/null || true
                        sleep 1
                done

                if [ -z "$NEW_PART" ]; then
                        echo "警告:创建分区后未能识别新分区,跳过格式化与挂载。"
                        echo "parted free 输出:"
                        parted -s "$disk" unit MiB print free 2>/dev/null || true
                        echo "lsblk 输出:"
                        lsblk -b -o NAME,SIZE,TYPE -p "$disk"
                        return 1
                fi

                echo "识别到新分区: $NEW_PART,准备格式化(将触发确认)"
                # 假设 format_target_part 函数存在并返回 UUID(与你原逻辑一致)

                local PART_UUID PART_FSTYPE

                PART_UUID=$(format_target_part "$NEW_PART" "DATA") || {
                        echo "格式化 $NEW_PART 失败或被取消,脚本退出。"
                        exit 1
                }

                PART_UUID=$(blkid -s UUID -o value "$NEW_PART" || true)
                PART_FSTYPE=$(blkid -s TYPE -o value "$NEW_PART" || true)
                if [ -z "$PART_UUID" ] || [ -z "$PART_FSTYPE" ]; then
                        echo "格式化后无法获取分区 UUID 或 文件系统类型,跳过写入 fstab。"
                        return 1
                fi

                local ident_name mount_point opts fstab_file
                ident_name="$(basename "$NEW_PART")"
                mount_point="/mnt/storage/$ident_name"
                mkdir -p "$MOUNT_DIR$mount_point"

                opts="nofail,x-systemd.automount,x-systemd.mount-timeout=10s"
                case "$PART_FSTYPE" in
                ext4 | ext3 | ext2) opts="defaults,$opts" ;;
                xfs) opts="noatime,$opts" ;;
                btrfs) opts="ssd,noatime,compress-force=zstd:3,$opts" ;;
                f2fs) opts="noatime,compress_algorithm=lz4,$opts" ;;
                ntfs | exfat | vfat) opts="uid=0,gid=0,umask=0022,$opts" ;;
                *) opts="defaults,$opts" ;;
                esac

                fstab_file="$MOUNT_DIR/etc/fstab"
                if grep -q "UUID=$PART_UUID" "$fstab_file"; then
                        echo "fstab 已存在 UUID=$PART_UUID 的条目,跳过添加"
                else
                        echo "添加 fstab: UUID=$PART_UUID $mount_point $PART_FSTYPE $opts 0 2"
                        printf "UUID=%s %s %s %s 0 2\n" "$PART_UUID" "$mount_point" "$PART_FSTYPE" "$opts" >>"$fstab_file"
                fi
                return 0
        }

        # 如果没有剩余分区,检查磁盘是否还有未使用空间(原有逻辑)
        if [ "${#filtered_parts[@]}" -eq 0 ]; then
                echo "未检测到除主分区外的其他分区,检查是否存在未使用空间以创建新分区..."
                DISK_SIZE_BYTES=$(blockdev --getsize64 "$TARGET_DISK")
                PART_SUM=0
                while read -r name size type; do
                        if [ "$type" = "part" ]; then
                                PART_SUM=$((PART_SUM + size))
                        fi
                done < <(lsblk -b -r -n -o NAME,SIZE,TYPE -p "$TARGET_DISK")

                FREE_BYTES=$((DISK_SIZE_BYTES - PART_SUM))
                echo "磁盘总大小: $DISK_SIZE_BYTES 字节, 已分区总和: $PART_SUM 字节, 估算空闲: $FREE_BYTES 字节"

                MIN_CREATE_BYTES=$((1 * 1024 * 1024))
                if [ "$FREE_BYTES" -lt "$MIN_CREATE_BYTES" ]; then
                        echo "未检测到足够的未使用空间用于创建新分区,跳过创建。"
                else
                        parted_output=$(parted -s "$TARGET_DISK" print 2>/dev/null || true)
                        if echo "$parted_output" | grep -qi "Partition Table: unknown"; then
                                echo "检测到无分区表,正在为 $TARGET_DISK 创建 GPT 分区表(mklabel gpt)..."
                                parted -s "$TARGET_DISK" mklabel gpt
                                partprobe "$TARGET_DISK"
                                sleep 1
                        fi

                        if free_pair=$(find_free_range "$TARGET_DISK"); then
                                start_mib=$(echo "$free_pair" | awk '{print $1}')
                                end_mib=$(echo "$free_pair" | awk '{print $2}')
                                echo "选择空闲区: ${start_mib}MiB - ${end_mib}MiB (使用全部剩余空间)"
                                create_and_register_partition "$TARGET_DISK" "$start_mib" "$end_mib" || true
                        else
                                echo "无法确定可用空闲区,跳过创建。"
                        fi
                fi
        else
                # 有剩余分区,按原逻辑为每个分区添加 fstab
                for name in "${filtered_parts[@]}"; do
                        PART_UUID=$(blkid -s UUID -o value "$name" || true)
                        PART_FSTYPE=$(blkid -s TYPE -o value "$name" || true)

                        if [ -z "$PART_FSTYPE" ] || [ -z "$PART_UUID" ]; then
                                echo "跳过不可挂载分区: $name (TYPE='$PART_FSTYPE', UUID='$PART_UUID')"
                                continue
                        fi

                        ident_name="$(basename "$name")"
                        mount_point="/mnt/storage/$ident_name"
                        mkdir -p "$MOUNT_DIR$mount_point"

                        fstab_file="$MOUNT_DIR/etc/fstab"
                        if grep -q "UUID=$PART_UUID" "$fstab_file"; then
                                echo "fstab 已存在 UUID=$PART_UUID 的条目,跳过"
                                continue
                        fi

                        opts="nofail,x-systemd.automount,x-systemd.mount-timeout=10s"
                        case "$PART_FSTYPE" in
                        ext4 | ext3 | ext2) opts="defaults,$opts" ;;
                        xfs) opts="noatime,$opts" ;;
                        btrfs) opts="ssd,noatime,compress-force=zstd:3,$opts" ;;
                        f2fs) opts="noatime,compress_algorithm=lz4,$opts" ;;
                        ntfs | exfat | vfat) opts="uid=0,gid=0,umask=0022,$opts" ;;
                        *) opts="defaults,$opts" ;;
                        esac

                        echo "添加 fstab: UUID=$PART_UUID $mount_point $PART_FSTYPE $opts 0 2"
                        printf "UUID=%s %s %s %s 0 2\n" "$PART_UUID" "$mount_point" "$PART_FSTYPE" "$opts" >>"$fstab_file"
                done

                # 处理:已有分区但仍有未分配空间时,尝试创建一个新分区并格式化(与上面逻辑一致)
                DISK_SIZE_BYTES=$(blockdev --getsize64 "$TARGET_DISK")
                PART_SUM=0
                while read -r name size type; do
                        if [ "$type" = "part" ]; then
                                PART_SUM=$((PART_SUM + size))
                        fi
                done < <(lsblk -b -r -n -o NAME,SIZE,TYPE -p "$TARGET_DISK")

                FREE_BYTES=$((DISK_SIZE_BYTES - PART_SUM))
                MIN_CREATE_BYTES=$((1 * 1024 * 1024))
                if [ "$FREE_BYTES" -ge "$MIN_CREATE_BYTES" ]; then
                        echo "检测到磁盘仍有未分配空间,尝试使用全部剩余空间创建新分区..."
                        parted_output=$(parted -s "$TARGET_DISK" print 2>/dev/null || true)
                        if echo "$parted_output" | grep -qi "Partition Table: unknown"; then
                                echo "检测到无分区表,正在为 $TARGET_DISK 创建 GPT 分区表(mklabel gpt)..."
                                parted -s "$TARGET_DISK" mklabel gpt
                                partprobe "$TARGET_DISK"
                                sleep 1
                        fi

                        if free_pair=$(find_free_range "$TARGET_DISK"); then
                                start_mib=$(echo "$free_pair" | awk '{print $1}')
                                end_mib=$(echo "$free_pair" | awk '{print $2}')
                                echo "选择空闲区: ${start_mib}MiB - ${end_mib}MiB (使用全部剩余空间)"
                                create_and_register_partition "$TARGET_DISK" "$start_mib" "$end_mib" || true
                        else
                                echo "未解析到可用 free 段,跳过创建。"
                        fi
                else
                        echo "未检测到足够的未使用空间用于创建新分区,跳过创建。"
                fi
        fi

        echo "剩余分区的 fstab 挂载配置完成(按分区名)。"
}

# 更新配置文件(根fs和引导)
update_rootfs_uuid() {
        echo -e "\n[8/8] 更新SSD rootfs分区UUID..."
        if mountpoint -q "$MOUNT_DIR"; then
                echo -e "取消挂载 $TARGET_PART"
                umount "$MOUNT_DIR"
                sleep 3
        fi
        echo -e "将$TARGET_PART UUID $NEW_UUID 改为EMMC UUID $EMMC_ROOT_UUID"
        e2fsck -f -y "$TARGET_PART"
        tune2fs -U "$EMMC_ROOT_UUID" "$TARGET_PART"
}

# 一键删除所有分区(危险操作)
wipe_all_partitions() {
        echo "========================================"
        echo "警告:此操作将删除 $TARGET_DISK 上的所有分区!"
        echo "所有数据将不可恢复,请确认已备份!"
        echo "========================================"

        read -p "确认删除所有分区? (输入大写 'WIPE' 继续): " confirm
        if [ "$confirm" != "WIPE" ]; then
                echo "操作已取消"
                return 1
        fi

        # 使用 parted 清空分区表
        echo "正在清空分区表..."
        parted -s "$TARGET_DISK" mklabel gpt
        partprobe "$TARGET_DISK"
        echo "已删除所有分区,磁盘现在为空。"
}

# 一键删除所有分区(危险操作)
wipe_all_partitions() {
        # 红色提醒
        echo -e "\033[1;31m========================================\033[0m"
        echo -e "\033[1;31m警告:此操作将删除 $TARGET_DISK 上的所有分区!\033[0m"
        echo -e "\033[1;31m所有数据将不可恢复,请确认已备份!\033[0m"
        echo -e "\033[1;31m========================================\033[0m"

        read -p "确认删除所有分区? (输入大写 'WIPE' 继续): " confirm
        if [ "$confirm" != "WIPE" ]; then
                echo "操作已取消"
                return 1
        fi

        # 使用 parted 清空分区表
        echo "正在清空分区表..."
        parted -s "$TARGET_DISK" mklabel gpt
        partprobe "$TARGET_DISK"
        echo "已删除所有分区,磁盘现在为空。"
}

# 清理和完成
cleanup() {
        echo -e "\n清理环境..."
        if mountpoint -q "$MOUNT_DIR"; then
                umount "$MOUNT_DIR"
        fi
        rmdir "$MOUNT_DIR" 2>/dev/null || true

        echo "========================================"
        echo "系统迁移完成!"
        echo "1. 插入SSD时:从SSD启动 "
        echo "2. 无SSD时:从eMMC启动"
        echo
        echo "操作指南:"
        echo "1. 关闭计算机: shutdown -h now"
        echo "2. 确保SSD已正确连接"
        echo "3. 首次启动后运行: sudo update-initramfs -u"
        echo "========================================"
}

# 菜单函数
show_menu() {
        echo "========================================"
        echo "请选择操作模式:"
        echo "1) 正常迁移系统到SSD"
        echo "2) 一键删除SSD内所有分区 (慎用,只是用作清除SSD磁盘数据方便迁移)"
        echo "3) 退出脚本"
        echo "========================================"
        read -p "输入选项编号: " mode

        case "$mode" in
        1)
                detect_devices
                clear
                show_warning
                prepare_disk
                copy_system
                setup_extra_mounts
                update_rootfs_uuid
                cleanup
                # 完成迁移后退出脚本
                exit 0
                ;;
        2)
                clear
                detect_devices
                wipe_all_partitions
                # 删除完成后再次调用菜单函数
                show_menu
                ;;
        3)
                echo "已退出脚本。"
                exit 0
                ;;
        *)
                echo "无效选项,请重新输入。"
                show_menu
                ;;
        esac
}

# 主入口
show_menu



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
 楼主| 发表于 2025-12-10 15:13 | 显示全部楼层
下载附件解压,将ssd.sh传到root目录内,插入硬盘,连接ssh输入命令chmod a+x ssd.sh && bash ssd.sh,根据提示操作。完成后系统会迁移到硬盘内,插硬盘从硬盘启动,不插入则从emmc启动
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
回复

使用道具 举报

 楼主| 发表于 2025-12-10 15:14 | 显示全部楼层
我使用的是ophub最新的armbian系统
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
回复

使用道具 举报

发表于 2025-12-11 01:27 | 显示全部楼层
利害。谢谢分享
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
回复

使用道具 举报

发表于 2025-12-11 10:20 | 显示全部楼层
感谢分享
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
回复

使用道具 举报

发表于 2025-12-11 10:22 | 显示全部楼层
有没有什么方法可以把oec或者oesp的系统镜像备份出来,系统有问题时重新用备份文件刷机

点评

用DD命令可以备份和还原  详情 回复 发表于 2025-12-14 10:38
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
回复

使用道具 举报

发表于 2025-12-14 09:20 | 显示全部楼层
这个好,厉害
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
回复

使用道具 举报

 楼主| 发表于 2025-12-14 10:38 | 显示全部楼层
595914151 发表于 2025-12-11 10:22
有没有什么方法可以把oec或者oesp的系统镜像备份出来,系统有问题时重新用备份文件刷机 ...

用DD命令可以备份和还原
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
回复

使用道具 举报

发表于 2025-12-15 17:08 | 显示全部楼层
如果重装 不要用这个脚本,会把第二分区格式化的(所有资料都没了)。需要手动格式化32G这个分区然后挂载
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
回复

使用道具 举报

发表于 2025-12-30 21:06 | 显示全部楼层
运行不了,有错误
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

欢迎大家光临恩山无线论坛上一条 /2 下一条

有疑问请添加管理员QQ86788181|手机版|小黑屋|Archiver|恩山无线论坛(常州市恩山计算机开发有限公司版权所有) ( 苏ICP备05084872号 )

GMT+8, 2026-8-23 18:56

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

| 江苏省互联网有害信息举报中心 举报信箱:js12377 | @jischina.com.cn 举报电话:025-88802724 本站不良内容举报信箱:68610888@qq.com 电话:13506125623

快速回复 返回顶部 返回列表