As someone who has worked embedded project on occasion, I needed to a consistent way to automatically prepare embedded images, pre-loaded with code (especially in the world of cheap SD-cards). The challenge is ofcourse, that embedded images use a different CPU architecture. That's why
packer-builder-arm-image was created.
packer-builder-arm-image brings the benefits for
packer, to the world embedded linux arm images, and allows you do modify them directly from your laptop or cloud VM. This means that you can now modify ARM images using native ARM tools on your x86 machine. Let's see how!
packer-build-arm-image performs the following:
- Download and verifies an existing OS Image (raspbian for example)
- Extract it and mount it
- Modify it via shell scripts and file uploads (using arm binaries!)
The packer builder arm leverages existing technologies and integrates them together. Let's see how in detail.
Downloads an existing OS Image
The plugin leverages existing packer code for downloading ISOs (hence all the iso terminology in the config). this lets us use images from a local or a remote location, and have their checksum validated. This feature makes easy to distribute packer configuration and have it Just Work™.
Extract & Mount
SD card images are usually compressed. If that's the case, they are automatically extracted so they can be mounted. When extracting
xz images, we try to use the native
xz binary for maximum performance, falling back to a go library if not available.
In the end of the process, we leave the result uncompressed so it is ready for flashing. Note that the original image file is never modified - a copy is made.
Before mounting, we allow the user to optionally increase the size of the last partition (if the free space in the original image is not sufficient). We do that by reading and modifying the partition table for the image.
Once we have the final image files with the correct partition sizes, time to mount them.
In order to mount the image locally (so we can edit its file system), we use
kpartx reads the partition table from the image and maps each partition to device files using the kernel's device mapper. we then mount these devices.
Where do we mount them too? Well, if its a known image type (BBB or raspbian) we have presets for the mount points. If not, the list of mount points can be provided by the user in the configuation.
Once everything is mounted, we can
chroot in and start modifying the image. We even have most of the code for handling chroot in packer already (in the aws provisioner. the code was copied to prevent pulling in aws dependencies). The one problem remains that all the images inside the chroot are arm binaries. Let's see how we solve that.
To allow users to run the arm utilities in the image (e.g.
apt-get install ...), We use the kernel's binfmt and
qemu-static-arm. the kernel binfmt allows us to have arm binaries automatically executed through
qemu-static-arm is copied inside the image, so it's available when chrooting, and removed once all customizations are done.
If the user wants to provide custom arguments for
qemu, we compile a static C qemu wrapper, that
execs qemu with the arguments the user provided.
All the steps above now enable using packer's shell and file provisioners to prepare you custom image automcatically. Checkout the examples in the repo. with the set of tools above you can create and modify embedded \ IoT images automatically. I personally used this to create images with default passwords disabled for extra network security. During the provisioning phase I added my ssh public key to the image, so I can still login remotely.