将pebuilder变成dibuilder.sh,将di tools集入boot层(5):为离线镜像安装引导

本文关键字:通过DD安装grub代替grub-install,grub离线安装技术,restore grub2 installation using dd,bootice工具原理

在《pebuilder变成dibuilder.sh,将di tools集入boot层4》的中间,我们提到过暂代方案的思路,这种思路的第一步,就是用grub作通用booter代替难实现的虚拟booter,为此我们在上文提出了初步的在dibuilder.sh中为离线镜像实现离线安装grub2的代码,----- 在整个《pebuilder变成dibuilder.sh,将di tools集入boot层》系列中,如果说(4)是(2)的增强继续,那么本文(5)就是对(3)的增强继续。前面准备好了所有debian dists(di,pve),我们最终的目的,是要在这些dists的基础上组装minstackos,并使dibuilder.sh中的生成raw镜像/localinstall生效,实现最终能安装在mbp实机上测试,这是后话

(为什么是di,pve这二者,因为我感觉debian installer的装机方案和pve的bearmetal hypervisor+lxc as app container越来越顺眼了,毕竟,用grub+dipe+pve作为多区段镜像第一个分区完全可以替换实现虚拟boot+payload的伪不死boot效果(这也可以理解为"在一块flash上装一个类似di的pe":类似白群将synope写在nand rom中),而debian pve也可以代替vm hypervisor inside boot to replace libos这些复杂目前还没用起来的东西),况且,我们也有dibuilder.sh对接),最后,过度虚拟化也真的不好用,这也是用grub代替虚拟boot作通用boot,及用lxc代替docker这种不好instant commit的原生云部署容器的又一原因。这些暂代方案的思路,贯穿了整个《minstackos的实践部分》

剩下的问题是考虑更好的dibuilder.sh对接,比如对接上文,在dibuilder.sh中继续实现为离线镜像生成grub2引导的问题提供方案。

好了,动工

测试研究mbr脚本定制,得到core_img

为什么要得到一个core_img,这个问题从何而来?先来看下grub2这个东西,它就像deb的动态control文件,我们要把离线镜像集成grub,必须要得到一个为此镜像预安装了的grub2,这绝非拷贝/boot/grub下的文件那么简单,

关于grub2 mbr安装
grub-install只是一个脚本,内部真正执行工作的是grub-mkimage和grub-setup,grub2-install 脚本的工作包括调用grub2-probe扫描计算机并收集磁盘和分区信息,调用grub2-mkimage构建一个基于/boot/grub下脚手架文件之上的新的new.img,即core.img,grub-mkimage的过程形如grub-mkimage -d x86_64-efi -p /boot/grub -o grubx64.efi -O x86_64-efi acpi all_video bitmap bitmap_scale blocklist exfat expr ....一般地,仅 fshelp ext2 part_msdos biosdisk就够了,最后grub2-setup把grub的boot.img写入MBR中,把core.img写进设备的第一个扇区
在BIOS平台下,boot.img是grub启动的第一个img文件,它被写入到MBR中或分区的boot sector中,因为boot sector的大小是512字节,所以该img文件的大小也是512字节。boot.img唯一的作用是读取属于core.img的第一个扇区并跳转到它身上,将控制权交给该扇区的img。由于体积大小的限制,boot.img无法理解文件系统的结构,因此grub2-install将会把core.img的位置硬编码到boot.img中(这并不一定就是物理扇区号,可能是虚拟offset),这样就一定能找到core.img的位置。
关于bios下写入booter的复杂性,其实我们在装黑苹果(1)处理clover的boot的biosdisk时候也遇到了。还有上次《普化群晖将其改造成正常磁盘布局及编译源码打开kernel message》说到grub-install在/dev/loop上失效,可能是在host上dev map string没有绑定https://itectec.com/superuser/how-to-install-grub-into-an-img-file/。可能需要mkdir -p /mnt/boot/grub,cat 进 /mnt/boot/grub/device.map:(hd0) /dev/loop0,(hd0,1) /dev/loop1等正确内容

这也就是说,(以上这是一个与具体磁盘绑定的动态配置过程)磁盘上的/boot/grub/i386-pc/*.img的这些文件全是脚手架文件,本质起作用的是具体磁盘环境下已经被通过grub-install嵌写入磁盘结构,我们的工作就是要将这个动态过程,变成静态的通过可恢复文件方式就能完成安装的grub-install-static。却要求能配合我们稍后以*方式生成的1g的boot区一起工作。(而且这又有限制和条件:在dibuilder.sh中我们基于加入了osx考虑不打算用grub-install)接下来你会看到,正确的boot.img容易生成,而core.img却难于得到。

所幸我们得到了一个脚本:

bootinfoscript,https://github.com/arvidjaar/bootinfoscript,它会显示一个主输出,同时在/tmp下保留大量有价值的临时文件(如解包的core.img),及其它输出(如Trash),它给出了一些bash算法,很为珍贵,以下是二个重要函数:

grub2_read_blocklist () 

这个函数整理hdd上存在于旧core.img->第一个sector中的diskboot.img未端部分->里的fragment信息,收集重组为新core.img上的fragments并写入,未来供grub2_info用
参数中的hdd也可能是硬盘(full core.img from blocklists,因为多个分区上都有可能有引导目录和core.img)也可能是单个普通core.img文件,后者情形我们不考虑,因为我们基本都是在读写磁盘嵌入区的这些文件区段,而不是文件系统中的这些文件。

grub2_info ()

这个函数用上一步得到的新core.img fragment信息,进一步,求得boot.img里core.img里的位置,以及更多信息(core.img中的文件夹信息,模块,配置文件),完成整个grub解析工作
stage1即是boot.img或/dev/sda mbr区,grub2的core.img是stage2,这种说法引用了grub1的stage1,2

但是bootinfoscript的实现比较理想化,依赖linux,比如它用到filefrag(apt-get install binutils for strings command),应该换成用string代替加判断才更为portable。

无论如何,我们从这个脚本和它的输出中希望得到的帮助是:它可以用于为我们的镜像输出core_img。供下一部分直接dd,而实际上,它也的确可以办到:我们可以在生成的虚拟镜像中安装一个linux,使之形成一个能启动的/dev/sda或其它需要的分区结构,在其中测试bootinfoscript,提取/tmp下的core_img。

2,实际修改dibuilder.sh并得到boot.img

得到core_img,我们接下来修改dibuilder.sh的对接部分,首先修改dibuilder.sh local remastering and install支持部分,修改得到一套较为规则的/tmp/tmpdown->/tmp/tmpremastering->/tmp/tmpmnt target or /boot target的文件夹逻辑结构:

首先,原来prepare misc中的net,grub部分中的grub归入接下来的remastering all up部分,整个脚本只有三部了,这是新的整个第三部分,


echo -e "\n \033[36m # Remastering all up... \033[0m \n"

remasteringdir='/tmp/tmpremastering';
[[ -d $remasteringdir ]] && rm -rf $remasteringdir;

sleep 2s && echo -en "\runpacking grub files ..."

export tmpMNT=/tmp/tmpmnt

[[ "$tmpMODE" == '1' && "$tmpTARGET" == 'minstackos' ]] && {
    
  prepareimg() {
    tmpDEV=$(mount | grep "$tmpMNT" | awk '{print $1}')

    [ -z "$tmpDEV" ] && {
      rm -rf /tmp/tmpimage.raw
      dd if=/dev/zero of=/tmp/tmpimage.raw bs=1024 count=1048576 >/dev/null 2>&1
      [[ "$tmpPLAT" == "0" ]] && tmpDEV=`losetup -fP --show /tmp/tmpimage.raw | awk '{print $1}'` || tmpDEV=`hdiutil attach -imagekey diskimage-class=CRawDiskImage -nomount /tmp/tmpimage.raw | awk '{print $1}'`

注意osx与linux在工具能力上的差别和分区控制方面的差别。通常我们必须在mbr和第一个分区开始前预留一定空间放core.img,一般grub-install会把core.img写入到第一个分区,但是我们放在这个预留一些空白区以更方便控制和定制,比如刻意向我们的离线grub2合成方向靠拢

      [ -n "$tmpDEV" ] && {
        [[ "$tmpPLAT" == "0" ]] && parted -s "$tmpDEV" mktable msdos >/dev/null 2>&1 && parted -s "$tmpDEV" mkpart primary ext3 2048s 100% >/dev/null 2>&1 && parted -s "$tmpDEV" set 1 boot on >/dev/null 2>&1 && mkfs.ext3 "$tmpDEV"p1 >/dev/null 2>&1
        [[ "$tmpPLAT" == "1" ]] && diskutil partitionDisk "$tmpDEV" MBR FREE %noformat% 1048576 "MS-DOS FAT16" TMPVOL 0 >/dev/null 2>&1
      }

      [ ! -d "$tmpMNT" ] && [[ "$tmpPLAT" == "0" ]] && mkdir "$tmpMNT" && mount "$tmpDEV"p1 "$tmpMNT" || tmpMNT=/Volumes/TMPVOL;[[ ! -d /tmp/tmpmnt ]] && ln -s /Volumes/TMPVOL /tmp/tmpmnt


      #echo "tmpdev is:""$tmpDEV"
      #echo "tmpmnt is:""$tmpMNT"

      #echo "- before installing/remastering grub,unzip it"
      mkdir -p /tmp/tmpremastering/grub
      tar -xf /tmp/tmpdown/debian/dists/jessie/main/binary-amd64/grub.gz -C /tmp/tmpremastering/grub

    (这里放接下来得到boot.img和离线镜像写grub的过程,这里的内容)

进生成core_img所在系统,针对/dev/sda得到其mbr(不要用脚本手架文件中的boot.img,开头字节是空的),得到原始字串并处理:sudo hexdump -v -n 446 /dev/sda | (手动处理掉换行,索引,并删掉最后一行) | sed -e "s/ //g" | sed -e "s/\([0-9a-z][0-9a-z]\)/\1 /g" |  sed -e "s/ /\\\x/g | sed -E 's/(\\x..)(\\x..)/\2\1/g'"  
(You can use xxd to dump binary files just like hexdump and od, but you can also use it to do the reverse: turn a hex dump back into binary. If y)

得到下列待处理字符串命名为grub202:


grub202='\xeb\x63\x90\x10\x8e\xd0\xbc\x00\xb0\xb8\x00\x00\x8e\xd8\x8e\xc0\xfb\xbe\x00\x7c\xbf\x00\x06\xb9\x00\x02\xf3\xa4\xea\x21\x06\x00\x00\xbe\xbe\x07\x38\x04\x75\x0b\x83\xc6\x10\x81\xfe\xfe\x07\x75\xf3\xeb\x16\xb4\x02\xb0\x01\xbb\x00\x7c\xb2\x80\x8a\x74\x01\x8b\x4c\x02\xcd\x13\xea\x00\x7c\x00\x00\xeb\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00\x00\xff\xfa\x90\x90\xf6\xc2\x80\x74\x05\xf6\xc2\x70\x74\x02\xb2\x80\xea\x79\x7c\x00\x00\x31\xc0\x8e\xd8\x8e\xd0\xbc\x00\x20\xfb\xa0\x64\x7c\x3c\xff\x74\x02\x88\xc2\x52\xbb\x17\x04\xf6\x07\x03\x74\x06\xbe\x88\x7d\xe8\x17\x01\xbe\x05\x7c\xb4\x41\xbb\xaa\x55\xcd\x13\x5a\x52\x72\x3d\x81\xfb\x55\xaa\x75\x37\x83\xe1\x01\x74\x32\x31\xc0\x89\x44\x04\x40\x88\x44\xff\x89\x44\x02\xc7\x04\x10\x00\x66\x8b\x1e\x5c\x7c\x66\x89\x5c\x08\x66\x8b\x1e\x60\x7c\x66\x89\x5c\x0c\xc7\x44\x06\x00\x70\xb4\x42\xcd\x13\x72\x05\xbb\x00\x70\xeb\x76\xb4\x08\xcd\x13\x73\x0d\x5a\x84\xd2\x0f\x83\xd0\x00\xbe\x93\x7d\xe9\x82\x00\x66\x0f\xb6\xc6\x88\x64\xff\x40\x66\x89\x44\x04\x0f\xb6\xd1\xc1\xe2\x02\x88\xe8\x88\xf4\x40\x89\x44\x08\x0f\xb6\xc2\xc0\xe8\x02\x66\x89\x04\x66\xa1\x60\x7c\x66\x09\xc0\x75\x4e\x66\xa1\x5c\x7c\x66\x31\xd2\x66\xf7\x34\x88\xd1\x31\xd2\x66\xf7\x74\x04\x3b\x44\x08\x7d\x37\xfe\xc1\x88\xc5\x30\xc0\xc1\xe8\x02\x08\xc1\x88\xd0\x5a\x88\xc6\xbb\x00\x70\x8e\xc3\x31\xdb\xb8\x01\x02\xcd\x13\x72\x1e\x8c\xc3\x60\x1e\xb9\x00\x01\x8e\xdb\x31\xf6\xbf\x00\x80\x8e\xc6\xfc\xf3\xa5\x1f\x61\xff\x26\x5a\x7c\xbe\x8e\x7d\xeb\x03\xbe\x9d\x7d\xe8\x34\x00\xbe\xa2\x7d\xe8\x2e\x00\xcd\x18\xeb\xfe\x47\x52\x55\x42\x20\x00\x47\x65\x6f\x6d\x00\x48\x61\x72\x64\x20\x44\x69\x73\x6b\x00\x52\x65\x61\x64\x00\x20\x45\x72\x72\x6f\x72\x0d\x0a\x00\xbb\x01\x00\xb4\x0e\xcd\x10\xac\x3c\x00\x75\xf4\xc3\xe1\x75\x0e\x09\x00\x00'
#(确认得到的显示正确,grub有特征码字串)
#printf $grub202 | hexdump -C

#虚拟offset号为1,无须处理
#printf $grub202 | hexdump -v -n 1 -s 92
#$grub202=`printf $grub202 | sed -e 's/\(x[0-9a-z][0-9a-z]\)/x01/93'`
#printf $grub202 | hexdump -v -n 1 -s 92

#添加可启动标识
#printf $grub202 | hexdump -v -n 1 -s 447
grub202+='\x80'
#printf $grub202 | hexdump -v -n 1 -s 447

diskutil unmountDisk "$tmpDEV"
printf $grub202 | dd of="$tmpDEV" bs=447 count=1
#确认已写入
hexdump -v -n 512 -C $tmpDEV

#core.img无特征码字串,前面10个字节判断
dd if=/tmp/tmpremastering/grub/i386-pc/core.img of="$tmpDEV" seek=512 bs=25760 count=1 conv=notrunc
hexdump -v -n 10 -C /tmp/tmpremastering/grub/i386-pc/core.img
hexdump -v -s 512 -n 10 -C $tmpDEV
diskutil mountDisk "$tmpDEV"

正式脚本中记得把上面调试输出适当注释掉



    }

    sleep 2s && echo -en "[ \033[32m /tmp/tmpremastering/grub/* \033[0m ]"

    # Automatically remove DISK on exit
    [[ "$tmpPLAT" == "0" ]] && trap 'echo; echo "- Ejecting tmpdev disk"; umount "$tmpMNT" && losetup -d "$tmpDEV"' EXIT || trap 'echo; echo "- Ejecting tmpdev disk"; diskutil eject "$tmpDEV";rm -rf /tmp/tmpmnt' EXIT
  }
  prepareimg
}


原grub remastering部分

LoaderMode='0'
setInterfaceName='0'
setIPv6='0'

......

    sed -i '$a\\n' /tmp/tmpremastering/grub/grub.new;
  fi

  sleep 2s && echo -en "[ \033[32m /tmp/tmpremastering/grub/grub.new \033[0m ]"

}

 在这里才写入脚手架文件,一切都是为了让down,remastering,copying to target这三部分清淅分开

sleep 2s && echo -en "\nprocessing grub ......"
[[ -d /Volumes/TMPVOL ]] && [[ "$tmpPLAT" == "1" ]] && mkdir -p "$tmpMNT"/boot/grub && cp -a /tmp/tmpremastering/grub/* "$tmpMNT"/boot/grub


[[ "$tmpINSTANTWITHOUTVNC" == '0' ]] && {

  GRUBPATCH='0';

  if [[ "$LoaderMode" == "0" && "$tmpPLAT" != "1" ]]; then

......

    [[ -f  $GRUBDIR/grubenv ]] && sed -i 's/saved_entry/#saved_entry/g' $GRUBDIR/grubenv;
  fi


开始remaster initrd部分的解压步骤

  sleep 2s && echo -en "\nunpacking initrd ......"

  mkdir -p /tmp/tmpremastering/initrd/usr/bin;
  cd /tmp/tmpremastering/initrd;

.....

        [[ "$tmpMODE" != "2" ]] && [[ "$tmpTARGET" == "minstackos" ]] && cp -f "$downdir/dists/jessie/main/binary-amd64/initrd.img" "/tmp/tmpremastering/$NewIMG"
          [[ "$tmpPLAT" != "1" ]] && [[ "$tmpMODE" == "2" ]] && cp -f "$downdir/dists/jessie/main/debian-installer/binary-amd64/initrd.img" "/tmp/tmpremastering/$NewIMG"

......

  [[ "$tmpPLAT" == '0' ]] && $UNCOMP < /tmp/tmpremastering/$NewIMG | cpio --extract --verbose --make-directories --no-absolute-filenames >>/dev/null 2>&1 || $UNCOMP < /tmp/tmpremastering/$NewIMG | cpio --extract --verbose --make-directories >>/dev/null 2>&1

  [[ -f '/boot/firmware.cpio.gz' ]] && {
    gzip -d < /boot/firmware.cpio.gz | cpio --extract --verbose --make-directories --no-absolute-filenames >>/dev/null 2>&1
  }


    sleep 2s &&   echo -en "\nprocessing initrd ...."

    find $downdir/dists/jessie -type f \( -name *.deb -o -name *.ldeb \) | while read line; do line2=${line##*/};echo -en "\033[s \033[K [ \033[32m ${line2:0:40} \033[0m ] \033[u";[[ $(ar -t ${line} | grep  -E -o data.tar.gz) == 'data.tar.gz' ]] && ar -p ${line} data.tar.gz |zcat|tar -xf - -C /tmp/tmpremastering/initrd || ar -p ${line} data.tar.xz |xzcat|tar -xf - -C /tmp/tmpremastering/initrd; done


......


    sleep 2s &&   echo -en "\nmake a safe wget wrapper to inc --no-check-certificate"

    mv /tmp/tmpremastering/initrd/usr/bin/wget /tmp/tmpremastering/initrd/usr/bin/wget2
    cat >/tmp/tmpremastering/initrd/usr/bin/wget<<EOF
#!/bin/sh
rdlkf() { [ -L "\$1" ] && (local lk="\$(readlink "\$1")"; local d="\$(dirname "$1")"; cd "\$d"; local l="\$(rdlkf "\$lk")"; ([[ "\$l" = /* ]] && echo "\$l" || echo "\$d/\$l")) || echo "\$1"; }
DIR="\$(dirname "\$(rdlkf "\$0")")"
exec /usr/bin/env wget2 --no-check-certificate "\$@"
EOF
    chmod +x /tmp/tmpremastering/initrd/usr/bin/wget
  fi


清楚的remasterin /copying分离

  echo -en "\ncopying vmlinuz to the target/mnt ......"
  [[ -d /boot ]] && [[ "$tmpMODE" != "2" ]] && [[ "$tmpTARGET" == "minstackos" ]] && cp -f "$downdir/dists/jessie/main/binary-amd64/vmlinuz" /boot/vmlinuz
  [[ -d /boot ]] && [[ "$tmpPLAT" != "1" ]] && [[ "$tmpMODE" == "2" ]] && cp -f "$downdir/dists/jessie/main/debian-installer/binary-amd64/vmlinuz" /boot/vmlinuz
  [[ -d /Volumes/TMPVOL ]] && [[ "$tmpPLAT" == "1" ]] && cp -f "$downdir/dists/jessie/main/binary-amd64/vmlinuz" /Volumes/TMPVOL/vmlinuz

(全程注意新脚本变动中的文件夹新命名变化)


除此之外,脚本其实在原先第一部分checkmirros部分把主体中检测1,2都注释了,直接返回提供的mirror url,相当于没检测,这是为了适应导致下到/tmp/tmpdown里的新结构debian文件夹的源镜像变化部分。

然后在osx上brew install qemu,cd /tmp/测试。
sudo qemu-system-x86_64 -machine pc-q35-2.4 -smp 4,cores=2 -cpu Penryn,kvm=off,vendor=GenuineIntel -m 4096 -device ide-hd,bus=ide.2,drive=MacHDD -drive id=MacHDD,if=none,format=raw,file=./buildpackage.raw -monitor stdio

成功进入。继续在脚本中处理硬盘镜像。


关于整合或分开使用这几个dists,除了前面“用di代替synope进行通用式dd安装镜像”“三个initramfs合为一个”等。还有一个思路,将di U盘安装代替multiboot unetbootin或ventoy,这样,比如用grub+dibuilder.sh面向的多img为基础打造这样一个类似的本地化多启U盘呢(不考虑它们的运行),我们可以以那个1G的boot区为启动区,mbr始终是现在最得到全面兼容的格式,数据区放各种img.gz。grub中写好各种安装条目。它就是dibuilder.sh的本地版了:在前面《云主机装黑果实践(1)》我们讲到过制造一个本地多启U盘的方案。它是以iso解压出的东西为内容基础的和efi方案为基础的,(后来我们发现了ventoy,它以iso为基础的,比如osx镜像全部弄为cdr/iso了,如经过hdiutil convert -format UDRW -o cdr.dmg common.iso之后,白苹果其实也可以用,不过这二者启动和对各种ISO的支持都不太稳定,继续深入一点,dibuilder.sh提供了一种更具替代性的稳定和兼容方案。

上一篇:基于注解的DI


下一篇:JavaScript计算两个文本框内数据的乘积(四舍五入保留两位小数)