Skip to content

Commit 9f8ea1e

Browse files
committed
Add make-boot-image service
Signed-off-by: Richard Oliver <[email protected]>
1 parent 7afb239 commit 9f8ea1e

File tree

4 files changed

+276
-0
lines changed

4 files changed

+276
-0
lines changed

make-boot-image/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# make-boot-image
2+
A oneshot service to download the specified Raspberry Pi linux-image- and
3+
create a replacement boot-image- package. This replacement package contains a
4+
signed boot.img with a cryptroot-enabled initramfs. The kernel modules are
5+
retained in the replacement package. Necessary firmware file are inserted into
6+
the signed boot.img where appropriate (via raspi-firmware package).
7+
8+
> [!CAUTION]
9+
> Support only exists for 2712 kernels at this time.
10+
11+
## Configuration
12+
- VENDOR
13+
- OPENSSL
14+
- CUSTOMER\_KEY\_FILE\_PEM
15+
16+
## Usage
17+
To create a replacement boot-image- package for linux-image-6.6.31+rpt-rpi-2712
18+
```
19+
systemctl start make-boot-image@$(systemd-escape 6.6.31+rpt-rpi-2712).service
20+
```
21+
22+
To determine the latest 2712 linux image (in order to run the service as
23+
suggested above)
24+
```
25+
META_PKG=linux-image-rpi-2712
26+
SRV=rpi-package-download@$(systemd-escape $META_PKG).service
27+
systemctl start --wait $SRV \
28+
&& grep-dctrl -F Package -X $META_PKG -n -s Depends /var/cache/$SRV/latest/Packages \
29+
| grep -o '^[[:graph:]]*'
30+
```
31+
32+
The service makes use of systemd's CacheDirectory during execution. The boot-image- package created by the example given above would typically be found at:
33+
```
34+
/var/cache/[email protected]\x2brpt\x2drpi\x2d2712.service/boot-image-<vendor>-6.6.31+rpi-rpi-2712_6.6.31-1+rpt1_arm64.deb
35+
```
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
# deps:
6+
# - dpkg (dpkg-deb)
7+
# - openssl
8+
# - zstd
9+
# - cpio
10+
11+
# TODO: Currently, we just assume we're Pi5/2712.
12+
# There is currently no functionality to insert firmware for Pi4 and earlier.
13+
14+
TMPDIR="${TMPDIR:=/tmp}"
15+
16+
if [[ -z "${1}" ]]; then
17+
>&2 echo "No linux image specified"
18+
exit 1
19+
fi
20+
21+
if [[ -z "${VENDOR}" ]]; then
22+
>&2 echo "'VENDOR' not specified"
23+
exit 1
24+
fi
25+
26+
if [[ -z "${OPENSSL}" || ! -f "${OPENSSL}" ]]; then
27+
>&2 echo "'OPENSSL' not set or binary does not exist"
28+
exit 1
29+
fi
30+
31+
if [[ -z "${CUSTOMER_KEY_FILE_PEM}" || ! -f "${CUSTOMER_KEY_FILE_PEM}" ]]; then
32+
>&2 echo "'CUSTOMER_KEY_FILE_PEM' not set or file does not exist"
33+
exit 1
34+
fi
35+
36+
LINUX_IMAGE="${1}"
37+
38+
# Should be set by systemd
39+
SERVICE_NAME="make-boot-image@$(systemd-escape $LINUX_IMAGE).service"
40+
CACHE_DIRECTORY="${CACHE_DIRECTORY:=/var/cache/${SERVICE_NAME}}"
41+
RUNTUME_DIRECTORY="${RUNTIME_DIRECTORY:=/run/${SERVICE_NAME}}"
42+
43+
# TODO: Might be interesting to start rpi-package-download with --no-block to
44+
# allow multiple simultaneous downloads.
45+
function download_package() {
46+
systemctl start \
47+
--wait \
48+
rpi-package-download@$(systemd-escape ${1}).service
49+
}
50+
51+
KERNEL_2712="linux-image-${LINUX_IMAGE}"
52+
>&2 echo "Downloading ${KERNEL_2712}"
53+
download_package "$KERNEL_2712"
54+
55+
PACKAGE_NAME="boot-image-${VENDOR}-${LINUX_IMAGE}"
56+
57+
# Temp directory cleanup
58+
TEMP_DIRS=()
59+
function remove_temp_dirs() {
60+
>&2 echo "Removing temporary directories"
61+
for dir in "${TEMP_DIRS[@]}"
62+
do
63+
rm -rf "$dir"
64+
done
65+
}
66+
trap remove_temp_dirs EXIT
67+
68+
>&2 echo -n "Creating filesystem hierarchy for deb package: "
69+
DEB_HIER="$(mktemp --directory --tmpdir debhier.XXX)"
70+
TEMP_DIRS+=("${DEB_HIER}")
71+
>&2 echo "${DEB_HIER}"
72+
73+
>&2 echo -n "Create rootfs working directory: "
74+
WORK_DIR="$(mktemp --directory --tmpdir boot-image-rootfs.XXX)"
75+
TEMP_DIRS+=("${WORK_DIR}")
76+
>&2 echo "${WORK_DIR}"
77+
78+
function latest_pkg_dir() {
79+
echo "/var/cache/rpi-package-download@$(systemd-escape ${1}).service/latest"
80+
}
81+
82+
>&2 echo "Extracting package contents"
83+
dpkg-deb --raw-extract "$(latest_pkg_dir $KERNEL_2712)/package.deb" "${WORK_DIR}"
84+
85+
function get_dctrl_field() {
86+
grep-dctrl \
87+
--field=Package \
88+
--exact-match "${2}" \
89+
--no-field-names \
90+
--show-field="${3}" \
91+
"${1}"
92+
}
93+
94+
# Determine package version for later reuse
95+
KERNEL_2712_VERSION="$(get_dctrl_field ${WORK_DIR}/DEBIAN/control ${KERNEL_2712} Version)"
96+
>&2 echo "Extracted ${KERNEL_2712}, version ${KERNEL_2712_VERSION}"
97+
98+
# rootfs kernel modules
99+
>&2 echo "Copy kernel modules into deb package"
100+
mkdir -p "${DEB_HIER}/lib/modules"
101+
rsync -crt "${WORK_DIR}/lib/modules/"* "${DEB_HIER}/lib/modules"
102+
103+
>&2 echo -n "Create ramdisk working directory: "
104+
BFS_DIR="$(mktemp --directory --tmpdir boot-image-bootfs.XXX)"
105+
TEMP_DIRS+=("${BFS_DIR}")
106+
>&2 echo "${BFS_DIR}"
107+
108+
# Kernel Images
109+
>&2 echo "Copy kernel to ramdisk"
110+
cp "${WORK_DIR}/boot/vmlinuz-${LINUX_IMAGE}" "${BFS_DIR}/zImage"
111+
112+
# Overlays
113+
>&2 echo "Copy overlays to ramdisk"
114+
OVERLAY_PATH="${WORK_DIR}/usr/lib/${KERNEL_2712}/overlays"
115+
rsync -crt "${OVERLAY_PATH}"/*.dtb* "${OVERLAY_PATH}/README" "${BFS_DIR}/overlays"
116+
117+
# DTBs
118+
>&2 echo "Copy DTBs to ramdisk"
119+
DTB_PATH="${WORK_DIR}/usr/lib/${KERNEL_2712}/broadcom"
120+
rsync -crt "${DTB_PATH}"/bcm27*.dtb "${BFS_DIR}"
121+
122+
# Insert an initramfs
123+
>&2 echo "Add cryptoot initramfs to ramdisk (with necessary kernel modules)"
124+
INITRAMFS_EXTRACT="$(mktemp --directory --tmpdir initramfs-extract.XXX)"
125+
TEMP_DIRS+=("${INITRAMFS_EXTRACT}")
126+
zstd -q -d /usr/share/misc/cryptroot_initramfs -o "${INITRAMFS_EXTRACT}/initramfs.cpio"
127+
mkdir -p "${INITRAMFS_EXTRACT}/initramfs"
128+
pushd "${INITRAMFS_EXTRACT}/initramfs" > /dev/null
129+
cpio --quiet -id < ../initramfs.cpio > /dev/null
130+
rm ../initramfs.cpio
131+
pushd "${WORK_DIR}" > /dev/null
132+
find lib/modules \
133+
\( \
134+
-name 'dm-mod.*' \
135+
-o \
136+
-name 'dm-crypt.*' \
137+
-o \
138+
-name 'af_alg.*' \
139+
-o \
140+
-name 'algif_skcipher.*' \
141+
-o \
142+
-name 'libaes.*' \
143+
-o \
144+
-name 'aes_generic.*' \
145+
-o \
146+
-name 'aes-arm64.*' \
147+
\) \
148+
-exec cp -r --parents "{}" "${INITRAMFS_EXTRACT}/initramfs/usr/" \;
149+
popd > /dev/null
150+
find . -print0 | cpio --quiet --null -ov --format=newc > ../initramfs.cpio 2> /dev/null
151+
popd > /dev/null
152+
zstd -q -6 "${INITRAMFS_EXTRACT}/initramfs.cpio" -o "${BFS_DIR}/rootfs.cpio.zst"
153+
154+
# cmdline.txt
155+
>&2 echo "Add cmdline.txt to ramdisk"
156+
# TODO: Needs to be user-modifiable
157+
echo "rootwait console=tty0 console=serial0,115200 root=/dev/ram0" > "${BFS_DIR}/cmdline.txt"
158+
159+
# Inner config.txt
160+
>&2 echo "Add config.txt to ramdisk"
161+
# TODO: Needs to be user-modifiable
162+
echo \
163+
'[all]
164+
kernel=zImage
165+
initramfs rootfs.cpio.zst
166+
enable_uart=1
167+
uart_2ndstage=1
168+
disable_overscan=1
169+
cmdline=cmdline.txt
170+
171+
[cm4]
172+
dtoverlay=dwc2,dr_mode=host
173+
174+
[none]
175+
' > "${BFS_DIR}/config.txt"
176+
177+
# Invoke make-boot-image
178+
>&2 echo "Finalise ramdisk in deb package (boot.img)"
179+
mkdir -p "${DEB_HIER}/boot/firmware"
180+
# TODO: Assuming pi5 here
181+
make-boot-image \
182+
-b pi5 \
183+
-d "${BFS_DIR}" \
184+
-o "${DEB_HIER}/boot/firmware/boot.img" > /dev/null
185+
186+
# Outer config.txt
187+
>&2 echo "Add config.txt to deb package (ensure boot.img is used)"
188+
cp /usr/share/misc/boot_ramdisk_config.txt "${DEB_HIER}/boot/firmware/config.txt"
189+
190+
# boot.sig generation
191+
>&2 echo "Signing ramdisk image"
192+
sha256sum "${DEB_HIER}/boot/firmware/boot.img" | awk '{print $1}' > "${DEB_HIER}/boot/firmware/boot.sig"
193+
echo -n "rsa2048: " >> "${DEB_HIER}/boot/firmware/boot.sig"
194+
${OPENSSL} dgst \
195+
-sign "${CUSTOMER_KEY_FILE_PEM}" \
196+
-keyform PEM \
197+
-sha256 \
198+
"${DEB_HIER}/boot/firmware/boot.img" \
199+
| xxd -c 4096 -p >> "${DEB_HIER}/boot/firmware/boot.sig"
200+
201+
# Insert control file
202+
# TODO: Needs to be user modifiable
203+
# TODO: depends on kmod, linux-base, etc?
204+
# TODO: breaks for fwupdate, initramfs-tools, etc?
205+
mkdir "${DEB_HIER}/DEBIAN"
206+
echo \
207+
"Package: ${PACKAGE_NAME}
208+
Source: linux
209+
Version: ${KERNEL_2712_VERSION}
210+
Architecture: arm64
211+
Maintainer: John Smith <[email protected]>
212+
Section: kernel
213+
Priority: optional
214+
Homepage: https://github.com/raspberrypi/linux/
215+
Provides: ${KERNEL_2712}
216+
Conflicts: ${KERNEL_2712}
217+
Replaces: ${KERNEL_2712}
218+
Description: TODO: Provide a better description" \
219+
> "${DEB_HIER}/DEBIAN/control"
220+
221+
# Create Debian package
222+
dpkg-deb --build "${DEB_HIER}" "${CACHE_DIRECTORY}"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[Unit]
2+
Description=Creates a signed boot image using a Raspberry Pi OS kernel / bootloader
3+
4+
[Service]
5+
Type=oneshot
6+
EnvironmentFile=/etc/rpi-sb-provisioner/config
7+
ExecStart=/usr/local/bin/make-boot-image-from-kernel "%I"
8+
CacheDirectory=%n
9+
10+
[Install]
11+
WantedBy=multi-user.target

nfpm.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ depends:
143143
- dctrl-tools
144144
- diffutils
145145
- findutils
146+
- dpkg
147+
- zstd
146148

147149
# Recommended packages. (overridable)
148150
# This will expand any env var you set in the field, e.g. ${RECOMMENDS_BLA}
@@ -228,6 +230,12 @@ contents:
228230
- src: rpi-package-download/rpi-package-download
229231
dst: /usr/local/bin/rpi-package-download
230232

233+
- src: make-boot-image/make-boot-image.service
234+
dst: /usr/local/lib/systemd/system/[email protected]
235+
236+
- src: make-boot-image/make-boot-image-from-kernel
237+
dst: /usr/local/bin/make-boot-image-from-kernel
238+
231239
# This will add all files in some/directory or in subdirectories at the
232240
# same level under the directory /etc. This means the tree structure in
233241
# some/directory will not be replicated.

0 commit comments

Comments
 (0)