创建您自己的自定义 Raspberry Pi 镜像

从零开始构建 Raspberry Pi 镜像,或将您正在运行的、已修改的 Raspberry Pi OS 转换回其他人可以使用的镜像。
62 位读者喜欢这篇文章。
Vector, generic Raspberry Pi board

最近当我阅读了 Alan Formy-Duval 的文章 使用 Cockpit 管理您的 Raspberry Pi 后,我认为创建一个预装 Cockpit 的镜像会是个好主意。幸运的是,至少有两种方法可以完成这项任务:

  • 调整 Raspberry Pi OS 镜像构建工具链 pi-gen 的源代码,这使您能够从零开始构建 Raspberry Pi 镜像
  • 将您正在运行的、已修改的 Raspberry Pi OS 转换回其他人可以使用的镜像

本文涵盖了这两种方法。我将重点介绍每种技术的优缺点。

Pi-gen

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

先决条件

为了成功运行构建过程,建议使用 32 位版本的 Debian Buster 或 Ubuntu Xenial。它也可能在其他系统上工作,但为了避免不必要的复杂性,我建议使用推荐的系统之一设置虚拟机。如果您不熟悉虚拟机,请查看我的文章 在任何操作系统上使用 VirtualBox 试用 Linux。当您完成所有设置并运行后,还要安装 仓库描述 中提到的依赖项。还要考虑到您需要在虚拟机中访问互联网,并有足够的可用磁盘空间。我将我的虚拟机设置为 40GB 硬盘,这似乎足够了。

为了遵循本文中的说明,请克隆 pi-gen 仓库,或者如果您想开始开发自己的镜像,请 fork 它。

仓库概览

整个构建过程分为多个阶段。每个阶段都表示为一个普通文件夹,并代表关于完整 Raspberry Pi OS 镜像的逻辑中间步骤。

  • 阶段 0:引导程序 — 创建一个可用的文件系统
  • 阶段 1:最小系统 — 创建一个绝对最小的系统
  • 阶段 2:精简系统 — 对应于 Raspberry Pi OS Lite
  • 阶段 3:桌面系统 — 安装 X11、LXDE、Web 浏览器等等
  • 阶段 4:对应于普通的 Raspberry Pi OS
  • 阶段 5:对应于 Raspberry Pi OS Full

各个阶段是相互构建的:不构建较低阶段就无法构建较高阶段。您也不能遗漏中间的任何阶段。例如,要构建 Raspberry Pi OS Lite,您必须构建阶段 0、1 和 2。要构建带有桌面的 Raspberry Pi OS,您必须构建阶段 0、1、2、3、4 和 5。

构建过程

构建过程由 build.sh 控制,可以在根仓库中找到它。如果您已经知道如何读写 bash 脚本,那么理解那里定义的过程不会有障碍。如果不是,阅读 build.sh 并尝试理解正在发生的事情是一个非常好的实践。但是即使没有 bash 脚本编写技能,您也能够创建自己的预装 Cockpit 的镜像。

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

  • stage-loop: 循环遍历所有阶段目录,按升序排列
    • 如果找到名为 SKIP 的文件,则跳过进一步处理
    • 运行脚本 prerun.sh
    • sub-loop: 循环遍历每个子目录,按升序排列,并处理以下文件(如果存在)
        • 00-run-sh:预先运行的任意指令
        • 00-run-chroot.sh:在镜像的 chroot 目录中运行此脚本
        • 00-debconfsdebconf-set-selection 的变量
        • 00-packages:要安装的软件包列表
        • 00-packages-nr:类似于 00-packages,不同之处在于这将导致使用 --no-install-recommends -y 参数进行 apt-get 安装
      • 00-patches:一个包含要应用的补丁文件的目录,使用 quilt
      • 回到 stage-loop,如果找到名为 EXPORT_IMAGE 的文件,则为此阶段生成镜像
      • 如果找到名为 SKIP_IMAGE 的文件,则跳过创建镜像

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

实践操作

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

IMG_NAME='Cockpit'
ENABLE_SSH=1

在目录 stage3stage4stage5 中创建一个名为 SKIP 的空文件。阶段 45 默认会生成镜像,因此在 stage4stage5 中添加一个名为 SKIP_IMAGE 的空文件。

现在打开终端,通过输入 su 切换到 root 用户。导航到仓库的根目录,并通过输入 ./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 文件夹中,您现在应该找到一个压缩的镜像 <date>-Cockpit-lite.zip,它已准备好部署。

故障排除

如果您尝试应用更复杂的修改,那么使用 pi-gen 构建自己的 Raspberry Pi 镜像会涉及大量的尝试和错误。您肯定会遇到构建过程由于某种原因在中间停止的情况。由于构建过程中没有异常处理,因此如果过程停止,我们需要手动进行一些清理。

在过程停止后,chroot 文件系统很可能仍处于挂载状态。如果不卸载它,您将无法启动新的构建过程。如果它仍然挂载,请通过输入以下命令手动卸载它

umount work/<Build-date-&-image-name>/tmpimage/

我确定的另一个问题是,当 chroot 文件系统即将被卸载时,脚本停止了。在文件 scripts/qcow2_handling 中,您可以看到在尝试卸载之前直接调用了 syncSync 强制系统刷新写入缓冲区。当以虚拟机方式运行构建系统时,当调用 unmount 时,写入过程尚未准备就绪,因此脚本在此处停止。

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

Sleep in between sync and unmount - core dump example

(我知道 30 秒有点过分了,但由于整个构建过程需要 20 多分钟,30 秒只是沧海一粟)

修改现有镜像

与使用 pi-gen 构建镜像相反,您也可以直接在正在运行的 Raspberry Pi OS 上应用修改。在我们的场景中,只需登录并使用以下命令安装 Cockpit

sudo apt install cockpit

现在关闭您的 Raspberry Pi,取出 SD 卡,并将其连接到您的 PC。通过输入 lsblk -p 检查您的系统是否已自动挂载 SD 卡上的分区

Using lsblk -p to check mounting partitions

在上面的屏幕截图中,SD 卡是设备 /dev/sdc,并且 bootrootfs 分区已自动挂载到提到的挂载点。在继续之前,请使用以下命令卸载它们

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 将镜像大小缩小了近十倍:从之前的 30GB 缩小到 3.5GB。您仍然可以通过在上传或共享之前对其进行压缩来优化大小。

就是这样,您现在可以共享和刷写此镜像了。

刷写镜像

如果您想使用 Linux 将您自己的自定义 Raspberry Pi 镜像刷写回 SD 卡,请按照以下步骤操作。

将 SD 卡插入您的 PC。如果之前已经安装过系统,您的系统很可能会自动挂载 SD 卡上的文件系统。您可以通过打开命令行并输入 lsblk -p 来检查这一点

Checking automatic mounting with lsblk -p

正如您在上面的屏幕截图中看到的,我的系统自动挂载了两个文件系统 bootrootfs,因为此 SD 卡已包含 Raspberry Pi OS。在我们开始刷写 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 管道,这是首选方法。我个人最喜欢的显然是修改现有镜像,因为您可以直接确保您应用的更改正在工作。

接下来阅读
标签
User profile image.
Stephan 是一位技术爱好者,他欣赏开源,因为它能深入了解事物的工作原理。Stephan 在工业自动化软件这个主要为专有领域的行业担任全职支持工程师。如果可能,他会致力于他基于 Python 的开源项目、撰写文章或驾驶摩托车。

评论已关闭。

Creative Commons License本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.