安装 img/qcow2 镜像中的系统

此处以 Armbian x86_64 UEFI 还原到 Legacy Boot 的机器为例,恢复环境选用 Alpine Linux

为什么选择Alpine Linux?

Alpine Linux 镜像小,apk 安装、换源速度快,适合轻量化使用(最主要是没用过,第一次学习使用

当然可以使用其他 Live CD 环境,Arch Linux 的也行

模拟日常使用已经安装 Windows 系统的破电脑,我们要将 Armbian 安装到第三个分区(如图所示,分区相关事项前期已经处理好)

准备

Armbian 磁盘镜像文件:https://github.com/armbian/os/releases/latest

此处选择 Armbian_2x.xx.x_Uefi-x86_bookworm_current_6.x.xx_minimal.img.qcow2.xz

(可以提前下载好,此处打算进入 Alpine 后在线下载到第二个分区)

Alpine Linux 启动ISO镜像文件:https://mirrors.aliyun.com/alpine/latest-stable/releases/x86_64/

此处选择 alpine-standard-3.20.3-x86_64.iso

此处在虚拟机内演示,所以需要准备虚拟机,如果是实体机的话需要准备好 U 盘

启动环境

此处使用虚拟机演示,直接将 CD/DVD 指定到 ISO 镜像,如果是实机环境可以采取写盘或者 Ventoy

引导到 Alpine Linux ISO,如下图所示

此处输入用户名 root,无密码,登录

配置网络

输入下方命令,按照提示配置好即可上网

1
2
3
4
# 使用内置脚本配置网卡
setup-interfaces
# 重启网络服务
rc-service networking restart

配置SSH

如果您想更方便地复制粘贴,可以配置 ssh

1
2
3
4
5
6
# 设置密码
passwd
# 使用内置脚本配置ssh服务,务必允许 root 登录
setup-sshd
# 查看本机IP
ip a

然后在外部机器通过ssh连接

1
ssh root@123.123.123.123

配置镜像

默认情况下 apk 只会从 cdrom 中获取软件包,我们需要配置在线镜像,否则无法安装在线软件包

1
2
3
4
5
# 使用内置脚本配置在线镜像源
# 镜像列表:https://mirrors.alpinelinux.org/mirrors.txt
setup-apkrepos
# 根据提示自动选择/手动选择/输入镜像
# 注意需要启用 community 镜像
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
localhost:~# setup-apkrepos
(f) Find and use fastest mirror
(s) Show mirrorlist
(r) Use random mirror
(e) Edit /etc/apk/repositories with text editor
(c) Community repo enable
(skip) Skip setting up apk repositories

Enter mirror number or URL: [1] c

Community repository enabled

Community repository enabled
(f) Find and use fastest mirror
(s) Show mirrorlist
(r) Use random mirror
(e) Edit /etc/apk/repositories with text editor
(c) Community repo disable
(skip) Skip setting up apk repositories

Enter mirror number or URL: [1] s


Available mirrors:
1) dl-cdn.alpinelinux.org
......
49) mirrors.aliyun.com
(f) Find and use fastest mirror
(s) Show mirrorlist
(r) Use random mirror
(e) Edit /etc/apk/repositories with text editor
(c) Community repo disable
(skip) Skip setting up apk repositories

Enter mirror number or URL: [1] 49

Added mirror mirrors.aliyun.com
Updating repository indexes... done.
localhost:~# cat /etc/apk/repositories
/media/dm-0/apks
http://mirrors.aliyun.com/alpine/v3.20/main
http://mirrors.aliyun.com/alpine/v3.20/community

挂载下载分区

此处为下载到本机硬盘,如果您已经下载到 U盘,那么只需挂载 U盘即可

执行 fdisk -l 查看分区表

1
2
3
4
5
6
7
8
9
10
localhost:~# fdisk -l
Disk /dev/sda: 100 GB, 107374182400 bytes, 209715200 sectors
13054 cylinders, 255 heads, 63 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type
/dev/sda1 * 0,32,33 1023,254,63 2048 83890175 83888128 40.0G 7 HPFS/NTFS
/dev/sda2 1023,254,63 1023,254,63 83890176 209715199 125825024 59.9G f Win95 Ext'd (LBA)
/dev/sda5 0,32,33 1023,254,63 83892224 146808831 62916608 30.0G 7 HPFS/NTFS
/dev/sda6 0,32,33 1023,254,63 146810880 209715199 62904320 29.9G 7 HPFS/NTFS

根据我们的环境,需要先将镜像下载到第二个NTFS分区(/dev/sda5),然后写入到第三个分区(/dev/sda6

由于是 NTFS,通过内核挂载可能会出现只读的情况,需要使用 ntfs-3gntfs-3g - Alpine Linux packages)进行挂载

1
apk add --no-cache ntfs-3g

挂载/dev/sda5/mnt/dpan

1
2
mkdir -p /mnt/dpan
mount -t ntfs-3g /dev/sda5 /mnt/dpan

将.qcow2.gz格式的镜像用 wget 下载到该分区

1
wget https://mirrors.tuna.tsinghua.edu.cn/armbian-releases/uefi-x86/archive/Armbian_24.11.1_Uefi-x86_bookworm_current_6.6.60_minimal.img.qcow2.xz -O /mnt/dpan/image.qcow2.xz

不知道怎么回事,使用 wget 默认 UA 会报错HTTP/1.1 403 Forbidden,可能是被拦截了,这里我们指定一个curl的UA

1
2
3
4
5
localhost:~# wget -U curl https://mirrors.tuna.tsinghua.edu.cn/armbian-releases/uefi-x86/archive/Armbian_24.11.1_Uefi-x86_bookworm_current_6.6.60_minimal.img.qcow2.xz -O /mnt/dpan/image.qcow2.xz
Connecting to mirrors.tuna.tsinghua.edu.cn ([2402:f000:1:400::2]:443)
saving to '/mnt/dpan/image.qcow2.xz'
image.qcow2.xz 100% |************************************************************************| 687M 0:00:00 ETA
'/mnt/dpan/image.qcow2.xz' saved

先解压 xz

1
unxz /mnt/dpan/image.qcow2.xz

解压完后看到只剩下 /mnt/dpan/image.qcow2

1
2
3
4
localhost:~# ls -lh /mnt/dpan/
total 2G
drwxrwxrwx 1 root root 0 Nov 30 07:17 $RECYCLE.BIN
-rwxrwxrwx 1 root root 2.5G Nov 30 16:37 image.qcow2

挂载镜像

img 格式

使用 losetup 挂载 img 为 loop 设备

1
2
3
4
losetup -f -P /mnt/dpan/image.img

# 查看已经挂载的loop设备
losetup -a

可以看到我们的qcow2镜像已经挂载到了 /dev/loop1 ,我们可以将其视为一块硬盘

1
2
3
localhost:~# losetup -a
/dev/loop0: 0 /media/cdrom/boot/modloop-lts
/dev/loop1: 0 /mnt/dpan/image.img

此时我们还无法获取到分区,需要使用 partx 来使系统识别分区

1
2
apk add --no-cache partx
partx -l /dev/loop1
1
2
3
4
5
6
7
8
9
localhost:~# partx -l /dev/loop1
# 1: 8192- 16383 ( 8192 sectors, 4 MB)
# 2: 16384- 540671 ( 524288 sectors, 268 MB)
# 3: 540672- 9584606 ( 9043935 sectors, 4630 MB)
localhost:~# ls -l /dev/loop1*
brw------- 1 root root 7, 1 Nov 30 22:32 /dev/loop1
brw-rw---- 1 root root 259, 0 Nov 30 23:11 /dev/loop1p1
brw-rw---- 1 root root 259, 1 Nov 30 23:11 /dev/loop1p2
brw-rw---- 1 root root 259, 2 Nov 30 23:11 /dev/loop1p3

qcow2 格式

我们可以将 qcow2 转换成 img,但此处不进行转换

经过测试无法直接使用 losetup 挂载,我们需要使用 qemu-nbd (QEMU Disk Network Block Device)来挂载:

安装依赖 qemu-imgqemu-img - Alpine Linux packages

1
2
# 首先配置镜像,注意需要启用 community,完了安装qemu-img
apk add --no-cache qemu-img

加载 nbd 内核模块

1
modprobe nbd max_part=63

使用 qemu-nbd 挂载 qcow2 为 nbd 设备

1
qemu-nbd -c /dev/nbd0 /mnt/dpan/image.qcow2

可以看到我们的 qcow2 镜像已经挂载到了 /dev/nbd0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
localhost:~# ls -l /dev/nbd0*
brw-rw---- 1 root disk 43, 0 Nov 30 17:12 /dev/nbd0
brw-rw---- 1 root disk 43, 1 Nov 30 17:15 /dev/nbd0p1
brw-rw---- 1 root disk 43, 2 Nov 30 17:15 /dev/nbd0p2
brw-rw---- 1 root disk 43, 3 Nov 30 17:15 /dev/nbd0p3
localhost:~# fdisk -l /dev/nbd0
Found valid GPT with protective MBR; using GPT

Disk /dev/nbd0: 7184384 sectors, 3508M
Logical sector size: 512
Disk identifier (GUID): dd23ed05-9e0e-b640-81a8-d34a50763a10
Partition table holds up to 128 entries
First usable sector is 2048, last usable sector is 7184350

Number Start (sector) End (sector) Size Name
1 8192 16383 4096K bios
2 16384 540671 256M efi
3 540672 7184350 3243M rootfs

对拷分区

我们可以将任务简化为两块硬盘对拷分区,即:将 /dev/nbd0p3 对拷至 /dev/sda6

在对拷之前,我们最好格式化分区为目标格式,此处为 ext4,另外还需安装依赖 e2fsprogse2fsprogs - Alpine Linux packages),不然没有 mkfs、e2fsck 命令

1
2
3
apk add --no-cache e2fsprogs

mkfs.ext4 /dev/sda6

使用 dd 命令

1
dd if=/dev/nbd0p3 of=/dev/sda6

注意这里的 dd 是 busybox 的 dd,为精简版,不支持进度显示,如需完整dd需要安装 coreutilscoreutils - Alpine Linux packages

1
apk add --no-cache coreutils

可以加上 oflag=direct 参数实现 Direct I/O,从而提高速度(超级快)

1
dd if=/dev/nbd0p3 of=/dev/sda6 bs=4M oflag=direct status=progress

对拷完后,由于分区大小不一致,当前/dev/sda6的文件系统为损坏状态,我们需要使用对应文件系统的工具(此处为 e2fsck) 修复,否则后面无法挂载

1
e2fsck /dev/sda6

最好卸载掉我们刚才挂载的 loop / nbd 设备,不然会被 os-prober 找到并修复引导

1
2
3
losetup -d /dev/loop1

qemu-nbd -d /dev/nbd0

修复引导

此时我们不能直接重启进入系统,因为引导还未修复,当前的主引导还是 Windows,我们需要重新安装 GRUB,另外,由于硬盘的UUID已经发生改变,我们需要重新生成 GRUB 配置文件、fstab

由于我们是 Legacy Boot 启动,非 UEFI,所以我们需要执行适用于 Legacy Boot 的引导修复

挂载系统分区到 /mnt/sysroot

1
2
mkdir -p /mnt/sysroot
mount -t ext4 /dev/sda6 /mnt/sysroot

除了系统分区外,我们还要挂载其他目录(使用 –bind)

1
2
3
mount --bind /dev /mnt/sysroot/dev
mount --bind /proc /mnt/sysroot/proc
mount --bind /sys /mnt/sysroot/sys

chroot 到硬盘上的 Linux 系统(原系统默认不知道什么shell,超级难用,这里指定使用bash)

1
chroot /mnt/sysroot bash

重新安装 GRUB

1
grub-install /dev/sda

启用 os-prober 支持自动添加其他系统启动项(如果是单系统可忽略)

1
2
nano /etc/default/grub
# 取消注释 GRUB_DISABLE_OS_PROBER=false

重新生成引导文件

1
2
update-grub
# 或者 grub-mkconfig -o /boot/grub/grub.cfg

如果你忘记了密码,可以在这里重设

1
passwd

退出 chroot 环境

1
exit

卸载已经挂载的设备与目录

1
2
3
umount /mnt/sysroot/dev
umount /mnt/sysroot/proc
umount /mnt/sysroot/sys

修复 fstab

此时如果重启,发现可以启动内核,但无法进入系统,因为 fstab (挂载信息)中的 UUID 未更新

自动修复

此处使用了来自 arch-install-scriptsarch-install-scripts - Alpine Linux packages)的 genfstab 脚本,会引入其他依赖,且对于其他系统以及挂载比较复杂的情况不适用,不太推荐

1
2
apk add --no-cache arch-install-scripts    # 此包需要启用 community 镜像
genfstab -U /mnt/sysroot > /mnt/sysroot/etc/fstab

手动修复

您也可以手动编辑修复

1
2
3
4
# 查看每个分区的 UUID
blkid
# 手动编辑替换
vi /mnt/sysroot/etc/fstab
1
2
3
4
5
6
7
8
9
10
11
localhost:~# blkid
/dev/loop0: TYPE="squashfs"
/dev/loop/0: TYPE="squashfs"
/dev/sr0: LABEL="alpine-std 3.20.3 x86_64" TYPE="iso9660"
/dev/sda1: LABEL="Win10Pro X64" UUID="3E742B30742AEA7B" TYPE="ntfs"
/dev/sda5: UUID="AB1BF8EF1A7CBDE8" TYPE="ntfs"
/dev/sda6: LABEL="armbi_root" UUID="20853349-5763-4f08-86f5-ad6aafb84415" TYPE="ext4"
localhost:~# cat /mnt/sysroot/etc/fstab
UUID=20853349-5763-4f08-86f5-ad6aafb84415 / ext4 defaults,noatime,commit=120,errors=remount-ro 0 1
UUID=61C1-FFC3 /boot/efi vfat defaults 0 2
tmpfs /tmp tmpfs defaults,nosuid 0 0

由于我们这边是用 dd 克隆的分区,原UUID没有改变,但是引导类型由原镜像的 UEFI 改为了 Legacy Boot,所以不需要挂载 EFI 分区,将其删除即可

1
2
UUID=20853349-5763-4f08-86f5-ad6aafb84415 / ext4 defaults,noatime,commit=120,errors=remount-ro 0 1
tmpfs /tmp tmpfs defaults,nosuid 0 0

顺便给它增加1x2048M的交换文件

1
2
3
4
5
dd if=/dev/zero of=/mnt/sysroot/swapfile bs=1M count=2048
chmod 600 /mnt/sysroot/swapfile
chown root:root /mnt/sysroot/swapfile
mkswap /mnt/sysroot/swapfile
echo '/swapfile none swap sw 0 0' >> /mnt/sysroot/etc/fstab

卸载收尾

查看刚才手动挂载的分区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
localhost:~# mount
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
devtmpfs on /dev type devtmpfs (rw,nosuid,noexec,relatime,size=10240k,nr_inodes=499135,mode=755,inode64)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
shm on /dev/shm type tmpfs (rw,nosuid,nodev,noexec,relatime,inode64)
/dev/sr0 on /media/cdrom type iso9660 (ro,relatime,nojoliet,check=s,map=n,blocksize=2048,iocharset=utf8)
tmpfs on / type tmpfs (rw,relatime,mode=755,inode64)
tmpfs on /run type tmpfs (rw,nosuid,nodev,size=803448k,nr_inodes=819200,mode=755,inode64)
mqueue on /dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatime)
/dev/loop0 on /.modloop type squashfs (ro,relatime,errors=continue)
securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)
debugfs on /sys/kernel/debug type debugfs (rw,nosuid,nodev,noexec,relatime)
pstore on /sys/fs/pstore type pstore (rw,nosuid,nodev,noexec,relatime)
tracefs on /sys/kernel/debug/tracing type tracefs (rw,nosuid,nodev,noexec,relatime)
/dev/sda5 on /mnt/dpan type fuseblk (rw,relatime,user_id=0,group_id=0,allow_other,blksize=4096)
/dev/sda6 on /mnt/sysroot type ext4 (rw,relatime)

卸载手动挂载的分区

1
2
umount /mnt/dpan
umount /mnt/sysroot

重启并拔盘

1
reboot

大功告成

参考

Mounting Raw and qcow2 Images | Linux Unbound