-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add make-boot-image service #11
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -182,6 +182,20 @@ Set to `1` to allow the service to run without actually writing keys or OS image | |
|
||
WARNING: Setting `DEMO_MODE_ONLY` will cause your seen-devices storage location to change to a subdirectory of the one specified by `RPI_DEVICE_SERIAL_STORE`, `demo/` | ||
|
||
=== BOOT_IMAGE_VENDOR | ||
*Mandatory* for make-boot-image | ||
|
||
Lower-case single-word representation of your organisation name. Used by | ||
make-boot-image service. e.g. `acme` would be appropriate for "Acme | ||
Corporation". | ||
|
||
=== BOOT_IMAGE_MAINTAINER | ||
*Mandatory* for make-boot-image | ||
|
||
A display name and email address in RFC 5322 mailbox format of the individual / | ||
team responsible for creating your boot-image packages. e.g. | ||
`Packaging Team <[email protected]>' | ||
|
||
== Using rpi-sb-provisioner | ||
`rpi-sb-provisioner` is composed of three `systemd` services that are triggered by the connection of a device in RPIBoot mode to a USB port. With `rpi-sb-provisioner` configured to your requirements, all that is therefore required is to connect your target Raspberry Pi device in RPIBoot mode. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
## Format of return will be [Happy: bool, error: str] | ||
from os import path | ||
from email.utils import parseaddr, formataddr | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this a core dep, or does it require an additional package? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's part of the Python standard library. It's provided by libpython3-stdlib in Debian, but this is a hard-dep of the python3 package. Even if we were to use python3-minimal, we would still get email.utils as part of libpython3.11-minimal |
||
import subprocess | ||
|
||
def validate_CUSTOMER_KEY_FILE_PEM(text) -> tuple[bool, str]: | ||
|
@@ -69,3 +70,26 @@ def validate_RPI_SB_WORKDIR(text) -> tuple[bool, str]: | |
else: | ||
return (False, "Please specify absolute path, beginning with /") | ||
return (True, "") | ||
|
||
def validate_BOOT_IMAGE_VENDOR(text) -> tuple[bool, str]: | ||
if len(text) > 0: | ||
if text.isalpha() and text.islower(): | ||
return (True, "") | ||
else: | ||
return (False, "BOOT_IMAGE_VENDOR must contain only lowercase letters") | ||
else: | ||
return (False, "Please specify a boot image vendor, e.g. \"acme\"") | ||
|
||
def validate_BOOT_IMAGE_MAINTAINER(text) -> tuple[bool, str]: | ||
# TODO: parseaddr/formataddr is now a legacy API. | ||
# Switch to python3-email-validator once v2.2.0 is available in Debian. | ||
# | ||
# parseaddr supports many formats but formataddr always uses RFC 5322 | ||
# mailbox. | ||
# Ensure that both display name and addr-spec address enclosed in angle | ||
# brackets are present. | ||
maint_addr = parseaddr(text) | ||
if all(maint_addr) and formataddr(maint_addr) == text: | ||
return (True, "") | ||
else: | ||
return (False, "BOOT_IMAGE_MAINTAINER must be an RFC 5322 mailbox, e.g. \"Able Maintainer <[email protected]>\"") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/bin/sh | ||
|
||
if [ "$1" = install ] | ||
then | ||
rm -f /boot/firmware/config.txt | ||
fi |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
rootwait console=tty0 console=serial0,115200 root=/dev/ram0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[all] | ||
kernel=zImage | ||
arm_64bit=1 | ||
initramfs rootfs.cpio.zst | ||
enable_uart=1 | ||
uart_2ndstage=1 | ||
disable_overscan=1 | ||
cmdline=cmdline.txt | ||
|
||
[cm4] | ||
dtoverlay=dwc2,dr_mode=host | ||
|
||
[none] |
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 v8 kernels at this time. | ||
|
||
## Configuration | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this need to be expanded to include the vendor fields? |
||
- VENDOR | ||
- OPENSSL | ||
- CUSTOMER\_KEY\_FILE\_PEM | ||
|
||
## Usage | ||
To create a replacement boot-image- package for linux-image-6.6.31+rpt-rpi-v8 | ||
``` | ||
systemctl start make-boot-image@$(systemd-escape 6.6.31+rpt-rpi-v8).service | ||
``` | ||
|
||
To determine the latest v8 linux image (in order to run the service as | ||
suggested above) | ||
``` | ||
META_PKG=linux-image-rpi-v8 | ||
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\x2dv8.service/boot-image-<vendor>-6.6.31+rpt-rpi-v8_6.6.31-1+rpt1_arm64.deb | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
#!/bin/sh | ||
|
||
set -e | ||
|
||
# deps: | ||
# - dpkg (dpkg-deb) | ||
# - openssl | ||
# - zstd | ||
# - cpio | ||
# - findutils (xargs) | ||
|
||
. /usr/local/bin/terminal-functions.sh | ||
|
||
read_config | ||
|
||
TMPDIR="${TMPDIR:=/tmp}" | ||
|
||
if [ -z "${1}" ]; then | ||
>&2 echo "No linux image specified" | ||
exit 1 | ||
fi | ||
|
||
if [ -z "${RPI_DEVICE_FAMILY}" ]; then | ||
>&2 echo "'RPI_DEVICE_FAMILY' not specified" | ||
exit 1 | ||
fi | ||
|
||
if [ -z "${BOOT_IMAGE_VENDOR}" ]; then | ||
>&2 echo "'BOOT_IMAGE_VENDOR' not specified" | ||
exit 1 | ||
fi | ||
|
||
if [ -z "${BOOT_IMAGE_MAINTAINER}" ]; then | ||
>&2 echo "'BOOT_IMAGE_MAINTAINER' not specified" | ||
exit 1 | ||
fi | ||
|
||
if [ -z "${OPENSSL}" ] || [ ! -f "${OPENSSL}" ]; then | ||
>&2 echo "'OPENSSL' not set or binary 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}}" | ||
|
||
# TODO: Might be interesting to start rpi-package-download with --no-block to | ||
# allow multiple simultaneous downloads. | ||
download_package() { | ||
systemctl start \ | ||
--wait \ | ||
rpi-package-download@"$(systemd-escape "${1}")".service | ||
} | ||
|
||
KERNEL_2711="linux-image-${LINUX_IMAGE}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apologies for the run-around, but I would prefer this was named 'kernel_v8' or similar. Not a blocker. |
||
>&2 echo "Downloading ${KERNEL_2711}" | ||
download_package "$KERNEL_2711" | ||
|
||
PACKAGE_NAME="boot-image-${BOOT_IMAGE_VENDOR}-${LINUX_IMAGE}" | ||
|
||
# Temp directory cleanup | ||
TEMP_DIRS_LIST=$(mktemp make_boot_image_temp_dirs_list.XXX) | ||
:> "${TEMP_DIRS_LIST}" | ||
remove_temp_dirs() { | ||
>&2 echo "Removing temporary directories" | ||
xargs --null rm -rf < "${TEMP_DIRS_LIST}" | ||
rm -f "${TEMP_DIRS_LIST}" | ||
} | ||
trap remove_temp_dirs EXIT | ||
|
||
>&2 printf "Creating filesystem hierarchy for deb package: " | ||
DEB_HIER="$(mktemp --directory --tmpdir debhier.XXX)" | ||
printf "%s\0" "${DEB_HIER}" >> "${TEMP_DIRS_LIST}" | ||
>&2 echo "${DEB_HIER}" | ||
|
||
>&2 printf "Create rootfs working directory: " | ||
WORK_DIR="$(mktemp --directory --tmpdir boot-image-rootfs.XXX)" | ||
printf "%s\0" "${WORK_DIR}" >> "${TEMP_DIRS_LIST}" | ||
>&2 echo "${WORK_DIR}" | ||
|
||
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_2711")/package.deb" "${WORK_DIR}" | ||
|
||
get_dctrl_field() { | ||
grep-dctrl \ | ||
--field=Package \ | ||
--exact-match "${2}" \ | ||
--no-field-names \ | ||
--show-field="${3}" \ | ||
"${1}" | ||
} | ||
|
||
# Determine package version for later reuse | ||
KERNEL_2711_VERSION="$(get_dctrl_field "${WORK_DIR}/DEBIAN/control" "${KERNEL_2711}" Version)" | ||
>&2 echo "Extracted ${KERNEL_2711}, version ${KERNEL_2711_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 printf "Create ramdisk working directory: " | ||
BFS_DIR="$(mktemp --directory --tmpdir boot-image-bootfs.XXX)" | ||
printf "%s\0" "${BFS_DIR}" >> "${TEMP_DIRS_LIST}" | ||
>&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_2711}/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_2711}/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)" | ||
printf "%s\0" "${INITRAMFS_EXTRACT}" >> "${TEMP_DIRS_LIST}" | ||
zstd -q -d "$(get_cryptroot)" -o "${INITRAMFS_EXTRACT}/initramfs.cpio" | ||
mkdir -p "${INITRAMFS_EXTRACT}/initramfs" | ||
cd "${INITRAMFS_EXTRACT}/initramfs" | ||
RETURN_DIR="${OLDPWD}" | ||
cpio --quiet -id < ../initramfs.cpio > /dev/null | ||
rm ../initramfs.cpio | ||
cd "${WORK_DIR}" | ||
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/" \; | ||
cd - | ||
find . -print0 | cpio --quiet --null -ov --format=newc > ../initramfs.cpio 2> /dev/null | ||
cd "${RETURN_DIR}" | ||
unset RETURN_DIR | ||
zstd -q -6 "${INITRAMFS_EXTRACT}/initramfs.cpio" -o "${BFS_DIR}/rootfs.cpio.zst" | ||
|
||
# raspi-firmware | ||
>&2 echo "Downloading raspi-firmware" | ||
download_package raspi-firmware | ||
|
||
>&2 printf "Create temp directory to extract firmware: " | ||
FW_EXTRACT_DIR="$(mktemp --directory --tmpdir boot-image-firmware.XXX)" | ||
printf "%s\0" "${FW_EXTRACT_DIR}" >> "${TEMP_DIRS_LIST}" | ||
>&2 echo "${FW_EXTRACT_DIR}" | ||
|
||
>&2 echo "Extracting firmware package contents" | ||
dpkg-deb --raw-extract "$(latest_pkg_dir raspi-firmware)/package.deb" "${FW_EXTRACT_DIR}" | ||
|
||
>&2 echo "Add firmware to ramdisk" | ||
rsync -v -crt "${FW_EXTRACT_DIR}/usr/lib/raspi-firmware/" "${BFS_DIR}" | ||
|
||
# cmdline.txt | ||
>&2 echo "Add cmdline.txt to ramdisk" | ||
cp "$(get_ramdisk_cmdline_file)" "${BFS_DIR}/cmdline.txt" | ||
|
||
# Inner config.txt | ||
>&2 echo "Add config.txt to ramdisk" | ||
cp "$(get_internal_config_file)" "${BFS_DIR}/config.txt" | ||
|
||
# Invoke make-boot-image | ||
>&2 echo "Finalise ramdisk in deb package (boot.img)" | ||
mkdir -p "${DEB_HIER}/boot/firmware" | ||
make-boot-image \ | ||
-b "pi${RPI_DEVICE_FAMILY}" \ | ||
-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 "$(get_fastboot_config_file)" "${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" | ||
printf "rsa2048: " >> "${DEB_HIER}/boot/firmware/boot.sig" | ||
# shellcheck disable=SC2046 | ||
${OPENSSL} dgst \ | ||
-sign $(get_signing_directives) \ | ||
-keyform PEM \ | ||
-sha256 \ | ||
"${DEB_HIER}/boot/firmware/boot.img" \ | ||
| xxd -c 4096 -p >> "${DEB_HIER}/boot/firmware/boot.sig" | ||
|
||
# Insert control file | ||
mkdir "${DEB_HIER}/DEBIAN" | ||
echo \ | ||
"Package: ${PACKAGE_NAME} | ||
Source: linux | ||
Version: ${KERNEL_2711_VERSION} | ||
Architecture: arm64 | ||
Maintainer: ${BOOT_IMAGE_MAINTAINER} | ||
Section: kernel | ||
Priority: optional | ||
Homepage: https://github.com/raspberrypi/linux/ | ||
Provides: ${KERNEL_2711} | ||
Conflicts: ${KERNEL_2711} | ||
Replaces: ${KERNEL_2711} | ||
Description: Repackaged ${KERNEL_2711} for signed/cryptroot boot" \ | ||
> "${DEB_HIER}/DEBIAN/control" | ||
|
||
# Insert preinst script to remove /boot/firmware/config.txt (otherwise dpkg | ||
# attempt to create a ".dpkg-tmp" hardlink. | ||
cp "$(get_bootimg_preinst_file)" "${DEB_HIER}/DEBIAN/preinst" | ||
|
||
# Create Debian package | ||
dpkg-deb --build "${DEB_HIER}" "${CACHE_DIRECTORY}" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[Unit] | ||
Description=Creates a signed boot image using a Raspberry Pi OS kernel / bootloader | ||
|
||
[Service] | ||
Type=oneshot | ||
ExecStart=/usr/local/bin/make-boot-image-from-kernel "%I" | ||
CacheDirectory=%n | ||
|
||
[Install] | ||
WantedBy=multi-user.target |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent formatting - maybe get this through a asciidoc preview tool?