Skip to content

Commit

Permalink
Add make-boot-image service
Browse files Browse the repository at this point in the history
Signed-off-by: Richard Oliver <[email protected]>
  • Loading branch information
roliver-rpi committed Aug 6, 2024
1 parent 7afb239 commit 9f8ea1e
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 0 deletions.
35 changes: 35 additions & 0 deletions make-boot-image/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# make-boot-image
A oneshot service to download the specified Raspberry Pi linux-image- and
create a replacement boot-image- package. This replacement package contains a
signed boot.img with a cryptroot-enabled initramfs. The kernel modules are
retained in the replacement package. Necessary firmware file are inserted into
the signed boot.img where appropriate (via raspi-firmware package).

> [!CAUTION]
> Support only exists for 2712 kernels at this time.
## Configuration
- VENDOR
- OPENSSL
- CUSTOMER\_KEY\_FILE\_PEM

## Usage
To create a replacement boot-image- package for linux-image-6.6.31+rpt-rpi-2712
```
systemctl start make-boot-image@$(systemd-escape 6.6.31+rpt-rpi-2712).service
```

To determine the latest 2712 linux image (in order to run the service as
suggested above)
```
META_PKG=linux-image-rpi-2712
SRV=rpi-package-download@$(systemd-escape $META_PKG).service
systemctl start --wait $SRV \
&& grep-dctrl -F Package -X $META_PKG -n -s Depends /var/cache/$SRV/latest/Packages \
| grep -o '^[[:graph:]]*'
```

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:
```
/var/cache/[email protected]\x2brpt\x2drpi\x2d2712.service/boot-image-<vendor>-6.6.31+rpi-rpi-2712_6.6.31-1+rpt1_arm64.deb
```
222 changes: 222 additions & 0 deletions make-boot-image/make-boot-image-from-kernel
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#!/usr/bin/env bash

set -e

# deps:
# - dpkg (dpkg-deb)
# - openssl
# - zstd
# - cpio

# TODO: Currently, we just assume we're Pi5/2712.
# There is currently no functionality to insert firmware for Pi4 and earlier.

TMPDIR="${TMPDIR:=/tmp}"

if [[ -z "${1}" ]]; then
>&2 echo "No linux image specified"
exit 1
fi

if [[ -z "${VENDOR}" ]]; then
>&2 echo "'VENDOR' not specified"
exit 1
fi

if [[ -z "${OPENSSL}" || ! -f "${OPENSSL}" ]]; then
>&2 echo "'OPENSSL' not set or binary does not exist"
exit 1
fi

if [[ -z "${CUSTOMER_KEY_FILE_PEM}" || ! -f "${CUSTOMER_KEY_FILE_PEM}" ]]; then
>&2 echo "'CUSTOMER_KEY_FILE_PEM' not set or file does not exist"
exit 1
fi

LINUX_IMAGE="${1}"

# Should be set by systemd
SERVICE_NAME="make-boot-image@$(systemd-escape $LINUX_IMAGE).service"
CACHE_DIRECTORY="${CACHE_DIRECTORY:=/var/cache/${SERVICE_NAME}}"
RUNTUME_DIRECTORY="${RUNTIME_DIRECTORY:=/run/${SERVICE_NAME}}"

# TODO: Might be interesting to start rpi-package-download with --no-block to
# allow multiple simultaneous downloads.
function download_package() {
systemctl start \
--wait \
rpi-package-download@$(systemd-escape ${1}).service
}

KERNEL_2712="linux-image-${LINUX_IMAGE}"
>&2 echo "Downloading ${KERNEL_2712}"
download_package "$KERNEL_2712"

PACKAGE_NAME="boot-image-${VENDOR}-${LINUX_IMAGE}"

# Temp directory cleanup
TEMP_DIRS=()
function remove_temp_dirs() {
>&2 echo "Removing temporary directories"
for dir in "${TEMP_DIRS[@]}"
do
rm -rf "$dir"
done
}
trap remove_temp_dirs EXIT

>&2 echo -n "Creating filesystem hierarchy for deb package: "
DEB_HIER="$(mktemp --directory --tmpdir debhier.XXX)"
TEMP_DIRS+=("${DEB_HIER}")
>&2 echo "${DEB_HIER}"

>&2 echo -n "Create rootfs working directory: "
WORK_DIR="$(mktemp --directory --tmpdir boot-image-rootfs.XXX)"
TEMP_DIRS+=("${WORK_DIR}")
>&2 echo "${WORK_DIR}"

function latest_pkg_dir() {
echo "/var/cache/rpi-package-download@$(systemd-escape ${1}).service/latest"
}

>&2 echo "Extracting package contents"
dpkg-deb --raw-extract "$(latest_pkg_dir $KERNEL_2712)/package.deb" "${WORK_DIR}"

function get_dctrl_field() {
grep-dctrl \
--field=Package \
--exact-match "${2}" \
--no-field-names \
--show-field="${3}" \
"${1}"
}

# Determine package version for later reuse
KERNEL_2712_VERSION="$(get_dctrl_field ${WORK_DIR}/DEBIAN/control ${KERNEL_2712} Version)"
>&2 echo "Extracted ${KERNEL_2712}, version ${KERNEL_2712_VERSION}"

# rootfs kernel modules
>&2 echo "Copy kernel modules into deb package"
mkdir -p "${DEB_HIER}/lib/modules"
rsync -crt "${WORK_DIR}/lib/modules/"* "${DEB_HIER}/lib/modules"

>&2 echo -n "Create ramdisk working directory: "
BFS_DIR="$(mktemp --directory --tmpdir boot-image-bootfs.XXX)"
TEMP_DIRS+=("${BFS_DIR}")
>&2 echo "${BFS_DIR}"

# Kernel Images
>&2 echo "Copy kernel to ramdisk"
cp "${WORK_DIR}/boot/vmlinuz-${LINUX_IMAGE}" "${BFS_DIR}/zImage"

# Overlays
>&2 echo "Copy overlays to ramdisk"
OVERLAY_PATH="${WORK_DIR}/usr/lib/${KERNEL_2712}/overlays"
rsync -crt "${OVERLAY_PATH}"/*.dtb* "${OVERLAY_PATH}/README" "${BFS_DIR}/overlays"

# DTBs
>&2 echo "Copy DTBs to ramdisk"
DTB_PATH="${WORK_DIR}/usr/lib/${KERNEL_2712}/broadcom"
rsync -crt "${DTB_PATH}"/bcm27*.dtb "${BFS_DIR}"

# Insert an initramfs
>&2 echo "Add cryptoot initramfs to ramdisk (with necessary kernel modules)"
INITRAMFS_EXTRACT="$(mktemp --directory --tmpdir initramfs-extract.XXX)"
TEMP_DIRS+=("${INITRAMFS_EXTRACT}")
zstd -q -d /usr/share/misc/cryptroot_initramfs -o "${INITRAMFS_EXTRACT}/initramfs.cpio"
mkdir -p "${INITRAMFS_EXTRACT}/initramfs"
pushd "${INITRAMFS_EXTRACT}/initramfs" > /dev/null
cpio --quiet -id < ../initramfs.cpio > /dev/null
rm ../initramfs.cpio
pushd "${WORK_DIR}" > /dev/null
find lib/modules \
\( \
-name 'dm-mod.*' \
-o \
-name 'dm-crypt.*' \
-o \
-name 'af_alg.*' \
-o \
-name 'algif_skcipher.*' \
-o \
-name 'libaes.*' \
-o \
-name 'aes_generic.*' \
-o \
-name 'aes-arm64.*' \
\) \
-exec cp -r --parents "{}" "${INITRAMFS_EXTRACT}/initramfs/usr/" \;
popd > /dev/null
find . -print0 | cpio --quiet --null -ov --format=newc > ../initramfs.cpio 2> /dev/null
popd > /dev/null
zstd -q -6 "${INITRAMFS_EXTRACT}/initramfs.cpio" -o "${BFS_DIR}/rootfs.cpio.zst"

# cmdline.txt
>&2 echo "Add cmdline.txt to ramdisk"
# TODO: Needs to be user-modifiable
echo "rootwait console=tty0 console=serial0,115200 root=/dev/ram0" > "${BFS_DIR}/cmdline.txt"

# Inner config.txt
>&2 echo "Add config.txt to ramdisk"
# TODO: Needs to be user-modifiable
echo \
'[all]
kernel=zImage
initramfs rootfs.cpio.zst
enable_uart=1
uart_2ndstage=1
disable_overscan=1
cmdline=cmdline.txt
[cm4]
dtoverlay=dwc2,dr_mode=host
[none]
' > "${BFS_DIR}/config.txt"

# Invoke make-boot-image
>&2 echo "Finalise ramdisk in deb package (boot.img)"
mkdir -p "${DEB_HIER}/boot/firmware"
# TODO: Assuming pi5 here
make-boot-image \
-b pi5 \
-d "${BFS_DIR}" \
-o "${DEB_HIER}/boot/firmware/boot.img" > /dev/null

# Outer config.txt
>&2 echo "Add config.txt to deb package (ensure boot.img is used)"
cp /usr/share/misc/boot_ramdisk_config.txt "${DEB_HIER}/boot/firmware/config.txt"

# boot.sig generation
>&2 echo "Signing ramdisk image"
sha256sum "${DEB_HIER}/boot/firmware/boot.img" | awk '{print $1}' > "${DEB_HIER}/boot/firmware/boot.sig"
echo -n "rsa2048: " >> "${DEB_HIER}/boot/firmware/boot.sig"
${OPENSSL} dgst \
-sign "${CUSTOMER_KEY_FILE_PEM}" \
-keyform PEM \
-sha256 \
"${DEB_HIER}/boot/firmware/boot.img" \
| xxd -c 4096 -p >> "${DEB_HIER}/boot/firmware/boot.sig"

# Insert control file
# TODO: Needs to be user modifiable
# TODO: depends on kmod, linux-base, etc?
# TODO: breaks for fwupdate, initramfs-tools, etc?
mkdir "${DEB_HIER}/DEBIAN"
echo \
"Package: ${PACKAGE_NAME}
Source: linux
Version: ${KERNEL_2712_VERSION}
Architecture: arm64
Maintainer: John Smith <[email protected]>
Section: kernel
Priority: optional
Homepage: https://github.com/raspberrypi/linux/
Provides: ${KERNEL_2712}
Conflicts: ${KERNEL_2712}
Replaces: ${KERNEL_2712}
Description: TODO: Provide a better description" \
> "${DEB_HIER}/DEBIAN/control"

# Create Debian package
dpkg-deb --build "${DEB_HIER}" "${CACHE_DIRECTORY}"
11 changes: 11 additions & 0 deletions make-boot-image/make-boot-image.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Unit]
Description=Creates a signed boot image using a Raspberry Pi OS kernel / bootloader

[Service]
Type=oneshot
EnvironmentFile=/etc/rpi-sb-provisioner/config
ExecStart=/usr/local/bin/make-boot-image-from-kernel "%I"
CacheDirectory=%n

[Install]
WantedBy=multi-user.target
8 changes: 8 additions & 0 deletions nfpm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ depends:
- dctrl-tools
- diffutils
- findutils
- dpkg
- zstd

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

- src: make-boot-image/make-boot-image.service
dst: /usr/local/lib/systemd/system/[email protected]

- src: make-boot-image/make-boot-image-from-kernel
dst: /usr/local/bin/make-boot-image-from-kernel

# This will add all files in some/directory or in subdirectories at the
# same level under the directory /etc. This means the tree structure in
# some/directory will not be replicated.
Expand Down

0 comments on commit 9f8ea1e

Please sign in to comment.