最近当我阅读了 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-debconfs:debconf-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在目录 stage3、stage4 和 stage5 中创建一个名为 SKIP 的空文件。阶段 4 和 5 默认会生成镜像,因此在 stage4 和 stage5 中添加一个名为 SKIP_IMAGE 的空文件。
现在打开终端,通过输入 su 切换到 root 用户。导航到仓库的根目录,并通过输入 ./build.sh 启动构建脚本。
构建过程将需要一些时间。
构建过程完成后,您将在仓库的根目录中找到另外两个目录:work 和 deploy。work 文件夹包含一些中间输出。在 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 来防止再次构建这些阶段
跳过 stage0 和 stage1 的构建过程
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 中,您可以看到在尝试卸载之前直接调用了 sync。Sync 强制系统刷新写入缓冲区。当以虚拟机方式运行构建系统时,当调用 unmount 时,写入过程尚未准备就绪,因此脚本在此处停止。
为了解决这个问题,我只是在 sync 和 unmount 之间插入了一个 sleep,这解决了这个问题
(我知道 30 秒有点过分了,但由于整个构建过程需要 20 多分钟,30 秒只是沧海一粟)
修改现有镜像
与使用 pi-gen 构建镜像相反,您也可以直接在正在运行的 Raspberry Pi OS 上应用修改。在我们的场景中,只需登录并使用以下命令安装 Cockpit
sudo apt install cockpit现在关闭您的 Raspberry Pi,取出 SD 卡,并将其连接到您的 PC。通过输入 lsblk -p 检查您的系统是否已自动挂载 SD 卡上的分区
在上面的屏幕截图中,SD 卡是设备 /dev/sdc,并且 boot 和 rootfs 分区已自动挂载到提到的挂载点。在继续之前,请使用以下命令卸载它们
umount /dev/sdc1 && umount /dev/sdc2现在我们将 SD 卡的内容复制到我们的文件系统中。确保您有足够的可用磁盘空间,因为镜像的大小将与 SD 卡相同。使用以下命令启动复制过程
dd if=/dev/sdc of=~/MyImage.img bs=32M
复制过程完成后,我们可以使用 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
PiShrink 将镜像大小缩小了近十倍:从之前的 30GB 缩小到 3.5GB。您仍然可以通过在上传或共享之前对其进行压缩来优化大小。
就是这样,您现在可以共享和刷写此镜像了。
刷写镜像
如果您想使用 Linux 将您自己的自定义 Raspberry Pi 镜像刷写回 SD 卡,请按照以下步骤操作。
将 SD 卡插入您的 PC。如果之前已经安装过系统,您的系统很可能会自动挂载 SD 卡上的文件系统。您可以通过打开命令行并输入 lsblk -p 来检查这一点
正如您在上面的屏幕截图中看到的,我的系统自动挂载了两个文件系统 boot 和 rootfs,因为此 SD 卡已包含 Raspberry Pi OS。在我们开始刷写 SD 卡之前,我们必须首先卸载文件系统,方法是输入
umount /dev/sdc1 && umount /dev/sdc2lsblk -p 的输出应如下所示才能继续
现在您可以将镜像刷写到 SD 卡:打开命令行并输入
dd if=/path/to/image.img of=/dev/sdc bs=32M, conv=fsync使用 bs=32M,您指定 SD 卡以 32 兆字节的块写入,conv=fsync 强制该过程物理写入每个块。
如果成功,您应该看到此输出
完成!您现在可以将 SD 卡放回 Raspberry Pi 并启动它。
总结
本文介绍的两种技术都有其优点和缺点。虽然使用 pi-gen 创建自己的自定义 Raspberry Pi 镜像比简单地修改现有镜像更容易出错,但如果您计划设置 CICD 管道,这是首选方法。我个人最喜欢的显然是修改现有镜像,因为您可以直接确保您应用的更改正在工作。

评论已关闭。