创建你自己的定制Raspberry Pi图像

485 阅读9分钟

当我最近读到Alan Formy-Duval的文章时 用Cockpit管理你的Raspberry Pi的文章,我认为有一个已经预装了Cockpit的镜像是个好主意。幸运的是,至少有两种方法可以完成这项任务。

  • 适应树莓派操作系统图像构建工具链pi-gen的来源,它使你能够从头开始构建一个树莓派图像。
  • 将你正在运行的、经过修改的Raspberry Pi操作系统转换为别人可以使用的图像。

这篇文章涵盖了这两种方法。我将强调每种技术的优点和缺点。

Pi-gen

让我们从pi-gen开始。在我们开始之前,你需要考虑一些先决条件。

前提条件

为了成功运行构建过程,建议使用32位版本的Debian Buster或Ubuntu Xenial。它也可能在其他系统上运行,但为了避免不必要的麻烦,我建议用推荐的系统之一设置一个虚拟机。如果你对虚拟机不熟悉,可以看看我的文章《用VirtualBox在任何操作系统上尝试Linux》。当你把一切都准备好并运行时,还要安装软件库描述中提到的依赖项。还要考虑到你需要在虚拟机中接入互联网和足够的可用磁盘空间。我为我的虚拟机设置了一个40GB的硬盘,这似乎已经足够了。

为了遵循本文的说明,请克隆pi-gen仓库,如果你想开始开发你自己的镜像,请分叉它。

仓库概述

整个构建过程被分成几个阶段。每个阶段都是一个普通的文件夹,代表了一个完整的Raspberry Pi操作系统镜像的逻辑中间环节。

  • 阶段0:Bootstrap-创建一个可用的文件系统
  • 阶段1:最小的系统--创建一个绝对最小的系统
  • 阶段2:精简系统-对应于Raspberry Pi OS Lite。
  • 阶段3:桌面系统-安装X11、LXDE、网络浏览器等。
  • 阶段4: 对应于普通的树莓派操作系统
  • 阶段5。对应于Raspberry Pi OS Full

这些阶段是相互建立的。如果不建立低级阶段,就不可能建立高级阶段。你也不能遗漏中间的一个阶段。例如,要建立一个Raspberry Pi OS Lite,你必须建立0、1和2阶段。要建立一个带桌面的Raspberry Pi操作系统,你必须建立0、1、2、3、4和5阶段。

构建过程

构建过程是由build.sh ,它可以在根仓库中找到。如果你已经知道如何读和写bash脚本,那么理解那里定义的过程就不会有什么障碍。如果没有,阅读build.sh ,并试图理解正在发生的事情,确实是一个很好的做法。但是,即使没有bash脚本技能,你也能用预装的Cockpit创建自己的镜像。

一般来说,构建过程由几个嵌套的for-loop组成。

  • **阶段性循环。**按升序循环处理所有阶段性目录
    • 如果发现一个名为_SKIP_的文件,则跳过进一步处理
    • 运行脚本prerun.sh
    • **子循环的变量。**按升序循环每个子目录,如果有以下文件,就处理这些文件。
        • 00-run-sh :预先运行的任意指令
        • 00-run-chroot.sh: 在镜像的chroot目录下运行该脚本
        • 00-debconfs:变量为 [debconf-set-selection](https://wiki.debian.org/PackageManagement/Preseed)
        • 00-packages: 一个要安装的软件包的列表
        • 00-packages-nr: 类似于_00-packages_,除了这将导致安装时使用 --no-install-recommends -y 参数给_apt-get_
      • 00-patches: 一个包含要应用的补丁文件的目录,使用quilt
      • 回到阶段性循环中,如果发现一个名为EXPORT_IMAGE 的文件,为这个阶段生成一个镜像
      • 如果发现一个名为SKIP_IMAGE 的文件,跳过创建镜像。

build.sh 还需要一个名为config 的文件,其中包含一些启动时读取的规范。

实践

首先,我们将创建一个基本的Raspberry Pi OS Lite镜像。这个Raspberry Pi OS Lite镜像将作为我们自定义镜像的基础。创建一个名为_config_的空文件并添加以下两行。

IMG_NAME='Cockpit'
ENABLE_SSH=1

在目录stage3,stage4, 和stage5 中创建一个名为SKIP 的空文件。Stages 45 默认会发出一个图像,因此在stage4stage5 中添加一个名为SKIP_IMAGE 的空文件。

现在打开一个终端,通过输入su ,切换到根用户。导航到版本库的根目录,通过键入./build.sh 启动构建脚本。

构建过程将需要一些时间。

构建过程结束后,你会发现在版本库的根目录下还有两个目录:workdeploywork 文件夹包含一些中间的输出。在deploy 文件夹中,你应该找到压缩后的镜像文件,准备进行部署。

如果整个构建过程是成功的,我们现在可以修改这个过程,使其额外地安装Cockpit。

扩展构建过程

Raspberry Pi OS Lite镜像是我们安装Cockpit的基础。由于Raspberry Pi OS Lite镜像中包含了stage2 ,我们将创建我们自己的stage3 ,它将处理Cockpit的安装。

我们完全删除原来的stage3 ,并创建一个新的、空的stage3

rm -rf stage3 && mkdir stage3

stage3 ,我们创建一个子阶段来安装cockpit。

mkdir stage3/00-cockpit

要在图像上安装cockpit,我们只需要将其添加到软件包列表中。

echo "cockpit" >> stage3/00-cockpit/00-packages

我们还想配置我们的新stage3 ,以输出一个图像,因此我们只需在stage3 目录中添加这个文件。

touch stage3/EXPORT_IMAGE

由于之前的构建过程中已经有了中间图像,我们可以通过在相关目录中添加skip-files ,来防止这些阶段再次被构建。

跳过stage0stage1 的构建过程。

touch stage0/SKIP && touch stage1/SKIP

跳过stage2 的构建过程,也跳过图像的创建。

touch stage2/SKIP && touch stage2/SKIP_IMAGE

现在再次运行构建脚本。

./build.sh

deployment 文件夹中,你现在应该找到一个压缩的镜像-Cockpit-lite.zip ,它已经准备好进行部署。

疑难解答

如果你试图应用更复杂的修改,在用pi-gen构建你自己的Raspberry Pi镜像时,会有很多的尝试和错误。你肯定会遇到构建过程会因为某些原因而停止的情况。由于在构建过程中没有异常处理,我们确实有一些手动的清理工作,以防过程停止。

很可能在进程停止后,chroot 文件系统仍然被挂载。如果不解除挂载,你将无法启动一个新的构建进程。在它仍然被挂载的情况下,通过打字手动解除挂载。

umount work//tmpimage/

我确定的另一个问题是,当chroot 文件系统即将被卸载时,脚本停止了。在文件scripts/qcow2_handling 中,你可以看到,在试图卸载sync 之前,直接调用了Sync ,迫使系统刷新了写缓冲区。作为一个虚拟机运行构建系统,当unmount 被调用时,写入过程还没有准备好,所以脚本在这里停止了。

为了解决这个问题,我只是在syncunmount 之间插入了一个sleep ,这就解决了问题。

Sleep in between sync and unmount - core dump example

(我知道30秒的时间太长了,但由于整个构建过程需要20分钟以上,30秒只是沧海一粟而已)

修改现有图像

与使用pi-gen 构建镜像相比,你也可以直接在运行中的Raspberry Pi操作系统上应用修改。在我们的方案中,只需登录并使用以下命令安装Cockpit。

sudo apt install cockpit

现在关闭你的Raspberry Pi,取出SD卡,并将其连接到你的电脑上。通过输入lsblk -p ,检查你的系统是否已经自动安装了SD卡上的分区。

Using lsblk -p to check mounting partitions

在上面的截图中,SD卡的设备是/dev/sdcboot- 和rootfs- 分区被自动挂载在上述挂载点。在你继续进行之前,用.NET Framework卸载它们。

umount /dev/sdc1 && umount /dev/sdc2

现在我们把SD卡的内容复制到我们的文件系统。确保你有足够的磁盘空间,因为镜像的大小将与SD卡相同。用下面的命令开始复制过程。

dd if=/dev/sdc of=~/MyImage.img bs=32M

Copying image from the SD card

一旦复制过程完成,我们就可以用PiShrink缩小镜像了。按照软件库中提到的安装说明进行安装。

wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
chmod +x pishrink.sh
sudo mv pishrink.sh /usr/local/bin

现在通过输入以下命令来调用脚本。

sudo pishrink.sh ~/MyImage.img

Invoking the pishrink.sh script

PiShrink将镜像的大小减少了近10倍。从以前的30GB到3.5GB。你仍然可以在上传或分享之前通过压缩来优化大小。

就这样,你现在可以分享和闪现这张图片了。

闪存图像

如果你想用Linux把你自己定制的Raspberry Pi图像闪回SD卡,请按照下面的步骤进行。

将SD卡放入你的电脑。如果之前已经安装了SD卡,你的系统可能会自动加载SD卡上的文件系统。你可以通过打开一个命令行并输入lsblk -p 来检查。

Checking automatic mounting with lsblk -p

正如你在上面的截图中看到的,我的系统自动挂载了两个文件系统,bootrootfs ,因为这张SD卡已经包含了Raspberry Pi操作系统。在我们开始闪烁SD卡之前,我们必须先通过输入来卸载文件系统。

umount /dev/sdc1 && umount /dev/sdc2

lsblk -p 的输出应该是这样的,这样才能继续。

Output of lsblk -p

现在你可以将镜像闪存到SD卡上了。打开一个命令行并输入。

dd if=/path/to/image.img of=/dev/sdc bs=32M, conv=fsync

bs=32M ,你指定SD卡是以32兆字节的块写入的,conv=fsync 强制进程物理地写入每个块。

如果成功,你应该看到这个输出。

Successful output example

完成了!现在你可以把SD卡放回Raspberry Pi,并启动它。

总结

本文介绍的两种技术都有其优点和缺点。虽然使用pi-gen 来创建你自己的Raspberry Pi图像比简单地修改一个现有的图像更容易出错,但如果你打算建立一个CICD管道,它是首选的方法。我个人最喜欢的显然是修改现有的图像,因为你可以直接确保你应用的变化是有效的。