#!/usr/bin/env bash # 在已建好 Android 分区的盘上,补完 mkfs/super/boot/ESP 四步。 # 不建分区、不动 Windows 分区、不改 p1 的 PARTLABEL。 # # 用法: sudo ./install-alongside.sh # # 前提:已用 installer-lib.sh 的 gk3_apply --mode alongside 建好 p5~p10。 set -euo pipefail # ── 工具函数 ────────────────────────────────────────────────────────────── die() { echo "!! $*" >&2; exit 1; } say() { echo; echo "══ $*"; } need_root() { [ "$(id -u)" = 0 ] || die "需要 root"; } need_cmd() { command -v "$1" >/dev/null || die "缺工具: $1"; } need_file() { [ -f "$REL/$1" ] || die "release 目录里缺 $1"; } # ── 参数 ────────────────────────────────────────────────────────────────── REL="${1:-}" [ -n "$REL" ] || { echo "用法: $0 " >&2; exit 1; } [ -d "$REL" ] || { echo "目录不存在: $REL" >&2; exit 1; } REL=$(realpath "$REL" 2>/dev/null || readlink -f "$REL" 2>/dev/null || echo "$REL") [ -d "$REL" ] || die "无法解析 release 目录路径: $REL" MID="${GK3_MACHINE_ID:-8a29534fa802480d9fbb71aa18c01d7b}" DISK="${DISK:-/dev/nvme0n1}" ESP="${ESP:-${DISK}p1}" ESP_MNT="${ESP_MNT:-/mnt/winesp}" # 根据 PARTLABEL 查找分区,找不到就报错退出 find_part() { local label="$1" local devs devs=$(blkid -t PARTLABEL="$label" -o device 2>/dev/null) local count count=$(echo "$devs" | grep -c .) [ "$count" -ge 1 ] || die "找不到 PARTLABEL 为 '$label' 的分区" [ "$count" -eq 1 ] || die "PARTLABEL '$label' 匹配到多个分区:$devs,请先清理" echo "$devs" } P_MISC=$(find_part misc) P_META=$(find_part metadata) P_BOOT_A=$(find_part boot_a) P_BOOT_B=$(find_part boot_b) P_SUPER=$(find_part super) P_USERDATA=$(find_part userdata) # 退出时保证 ESP 已卸载 cleanup() { if mountpoint -q "$ESP_MNT"; then sync umount "$ESP_MNT" 2>/dev/null || \ umount -l "$ESP_MNT" 2>/dev/null || \ echo "!! 无法卸载 $ESP_MNT" >&2 fi } trap cleanup EXIT # ── 前置检查 ────────────────────────────────────────────────────────────── need_root for t in sgdisk partprobe mkfs.ext4 simg2img dd od; do need_cmd "$t"; done say "release 目录: $REL" for f in super.img boot.img Image gaokun3.dtb ramdisk.img systemd-bootaa64.efi; do need_file "$f" printf " ✅ %-24s %s\n" "$f" "$(du -h "$REL/$f" | cut -f1)" done say "目标分区" for p in "$P_MISC" "$P_META" "$P_BOOT_A" "$P_BOOT_B" "$P_SUPER" "$P_USERDATA"; do [ -b "$p" ] || die "$p 不存在——先跑 gk3_apply --mode alongside 建分区" printf " ✅ %-20s %s\n" "$p" "$(lsblk -no SIZE "$p" | tr -d ' ')" done say "ESP" [ -b "$ESP" ] || die "$ESP 不存在" # 根文件系统不能在内置盘上 case "$(findmnt -n -o SOURCE / 2>/dev/null)" in "$DISK"*) die "根文件系统在 $DISK 上,必须从 U 盘/Live 启动" ;; esac say "即将执行以下破坏性操作,请核对目标设备:" echo " metadata : $P_META" echo " userdata : $P_USERDATA" echo " misc : $P_MISC" echo " super : $P_SUPER" echo " boot_a : $P_BOOT_A" echo " boot_b : $P_BOOT_B" echo " ESP : $ESP" echo echo " ⚠️ metadata / userdata 将被格式化,misc 将被清零,boot_a/b 和 super 将被覆盖。" read -r -p "确认继续?(输入 yes 继续,其他任意键退出) " ans [ "$ans" = "yes" ] || die "已取消" # ── 步骤 1: mkfs + misc 清零(幂等,但会清 userdata,先确认)────────────── say "步骤 1/4: 文件系统" # metadata 和 userdata 是否已经格式化 META_FS=$(lsblk -no FSTYPE "$P_META" | tr -d ' ') DATA_FS=$(lsblk -no FSTYPE "$P_USERDATA" | tr -d ' ') if [ "$META_FS" = "ext4" ] && [ "$DATA_FS" = "ext4" ]; then echo " p6/p10 已是 ext4。" read -r -p " 重新 mkfs 会清空 userdata,继续?(yes/no) " ans [ "$ans" = "yes" ] || die "已取消" fi mkfs.ext4 -q -F -L metadata "$P_META" mkfs.ext4 -q -F -L userdata "$P_USERDATA" dd if=/dev/zero of="$P_MISC" bs=1M count=4 status=none sync echo " ✅ metadata / userdata / misc" # ── 步骤 2: super.img ───────────────────────────────────────────────────── say "步骤 2/4: super.img" MAGIC=$(od -A n -t x1 -N 4 "$REL/super.img" | tr -d ' \n') if [ "$MAGIC" = "3aff26ed" ]; then echo " sparse → simg2img" # simg2img 分支 simg2img "$REL/super.img" "$P_SUPER" # 验证:simg2img 的输出大小应该等于 super 分区的容量(或至少不小于某个下限) SUPER_SIZE=$(blockdev --getsize64 "$P_SUPER") IMG_SIZE=$(wc -c < "$REL/super.img") echo " super 分区容量 ${SUPER_SIZE} 字节,源镜像 ${IMG_SIZE} 字节" else echo " raw → dd" dd if="$REL/super.img" of="$P_SUPER" bs=4M conv=fsync status=progress fi sync LP=$(dd if="$P_SUPER" bs=1 skip=4096 count=4 2>/dev/null | od -An -tx1 | tr -d ' \n') [ "$LP" = "67446c61" ] || die "super 的 LP 元数据不对(得到 '$LP')" echo " ✅ LP 元数据 67446c61" # ── 步骤 3: boot_a / boot_b ─────────────────────────────────────────────── H_SRC=$(dd if="$REL/boot.img" bs=8 count=1 2>/dev/null) [ "$H_SRC" = "ANDROID!" ] || die "源 boot.img 头部不是 ANDROID!(得到 '$H_SRC')" echo " ✅ 源 boot.img 头部验证通过" for p in "$P_BOOT_A" "$P_BOOT_B"; do dd if="$REL/boot.img" of="$p" bs=4M conv=fsync status=none # 写入后读回验证 H=$(dd if="$p" bs=8 count=1 2>/dev/null) [ "$H" = "ANDROID!" ] || die "$p 头部不是 ANDROID!(得到 '$H')" echo " ✅ $p" done sync # ── 步骤 4: ESP ─────────────────────────────────────────────────────────── say "步骤 4/4: ESP" mkdir -p "$ESP_MNT" mount "$ESP" "$ESP_MNT" || die "挂载 $ESP 失败" findmnt "$ESP_MNT" >/dev/null || die "$ESP_MNT 没挂上" echo " 已挂载 $ESP → $ESP_MNT" # 挂载后立即计算所需空间 REQUIRED_MIB=0 for f in systemd-bootaa64.efi Image gaokun3.dtb ramdisk.img; do size=$(wc -c < "$REL/$f") # 所有文件都写两份(systemd-boot 写两份,内核相关写 slot_a/b 两份) REQUIRED_MIB=$(( REQUIRED_MIB + (size * 2 + 1048575) / 1048576 )) done # 再加 10 MiB 余量 REQUIRED_MIB=$(( REQUIRED_MIB + 10 )) FREE_MIB=$(df -m "$ESP_MNT" | awk 'NR==2{print $4}') echo " ESP 空闲 ${FREE_MIB} MiB,预计需要 ${REQUIRED_MIB} MiB" [ "$FREE_MIB" -ge "$REQUIRED_MIB" ] || die "ESP 空间不足" # 目录 mkdir -p "$ESP_MNT/EFI/systemd" "$ESP_MNT/loader/entries" mkdir -p "$ESP_MNT/$MID/android/slot_a" "$ESP_MNT/$MID/android/slot_b" # systemd-boot 本体 cp "$REL/systemd-bootaa64.efi" "$ESP_MNT/EFI/systemd/systemd-bootaa64.efi" echo " ✅ EFI/systemd/systemd-bootaa64.efi" # 回落路径:只在备份不存在时备份 if [ -f "$ESP_MNT/EFI/Boot/bootaa64.efi" ] && \ [ ! -f "$ESP_MNT/EFI/Boot/bootaa64.efi.bak-windows" ]; then cp "$ESP_MNT/EFI/Boot/bootaa64.efi" "$ESP_MNT/EFI/Boot/bootaa64.efi.bak-windows" echo " ✅ 已备份 Windows 回落路径 → bootaa64.efi.bak-windows" elif [ -f "$ESP_MNT/EFI/Boot/bootaa64.efi.bak-windows" ]; then echo " ⏭ 备份已存在,跳过" fi cp "$REL/systemd-bootaa64.efi" "$ESP_MNT/EFI/Boot/bootaa64.efi" echo " ✅ EFI/Boot/bootaa64.efi ← systemd-boot" # 内核/DTB/ramdisk for slot in a b; do cp "$REL/Image" "$ESP_MNT/$MID/android/slot_$slot/Image" cp "$REL/gaokun3.dtb" "$ESP_MNT/$MID/android/slot_$slot/gaokun3.dtb" cp "$REL/ramdisk.img" "$ESP_MNT/$MID/android/slot_$slot/ramdisk.img" done echo " ✅ 内核/DTB/ramdisk → slot_a, slot_b" # BLS 条目 for slot in a b; do cat > "$ESP_MNT/loader/entries/$MID-android-$slot.conf" < "$ESP_MNT/loader/loader.conf" <<'EOF' default @saved timeout 5 console-mode keep editor no EOF echo " ✅ loader.conf" sync umount "$ESP_MNT" trap - EXIT echo echo "完成。重启后 systemd-boot 菜单会列出 slot_a / slot_b / Windows。"