Building Flashable Images For Your Fedora IoT Remix
It’s nice to be able to pick an RPM-OSTree based system like Fedora IoT and build a remix based on it with tools like RPM-OSTree-Engine. But you know what? Maybe it’s not enough to just manage your Remix’ OSTree, host it somewhere publicly and to automate the whole build process. Maybe you want something more, the next step in OS distribution and management? Well I don’t know about you but hell did I want that feature!
With support and funds from my company I hired someone investigating and implementing this into RPM-OSTree-Engine. After some polishing and refactoring with 0.3.3 we can now start building flashable raw images of Fedora 33 IoT for your Raspberry Pi and Compute Modules.
Let me walk you through how that’s done manually with the RPM-OSTree-Engine container before showing you how it’s automated via Gitlab CI. At the end I’ll talk a bit on the implementation details, in case you wonder and consider implementing something similar looking for an inspiration.
The Art Of Creating OS Images
We’ll use so called loopback devices to mount a file as partitioned virtual SD Card where we then install the OSTree system. In order to do this the
loop kernel module has to be available, which most modern systems should provide. Another dependency is SELinux if we intend to build Fedora IoT. Overall it’s recommended to use fairly up to date systems to avoid problems with SELinux policies.
Assuming you have a RPM-OSTree repository laying around from previous play through now all you need to do is running the
sudo podman run --privileged --rm -it -v $(pwd):/mirror -w /mirror quay.io/os-forge/rpm-ostree-engine:latest rpm-ostree-engine-image --ref=OSTreeBeard/develop/x86_64 --mirror=/mirror/.deploy-repo
If you want to build an image from a remote RPM-OSTree repository you can additional specify the remote address using the
sudo podman run --privileged --rm -it -v $(pwd):/mirror -w /mirror quay.io/os-forge/rpm-ostree-engine:latest rpm-ostree-engine-image --ref=OSTreeBeard/develop/x86_64 --origin=https://ostree.example.com
Or even combine both options to get an image from your local repository with a public update channel already configured as an alternative to adding a OSTree remotes.d conf.
--output= option you can control how the output image is named. It’s default name is
os-iot.raw and it will be placed in the working directory. With something like
OSTreeBeard-$(date +%Y%m%d) you can get a filename like
The command requires
sudo because the virtualization requires access to the host system. For access to the
loopback devices and in order to use
os-iot.raw we also need
In order to compress the output raw-image you can use the xz command:
xz -0 -T0 ./os-iot.raw
This will give you a file named
os-iot.raw.xz which is compatible with fedora
I have yet to test if this works cross-architecture. I’m currently building the images on hosts of the respective architecture simply because I already have them set up.
Let Gitlab CI Do The Repetition
With the RPM-OSTree-Engine templates you can build a
.gitlab-ci.yml that automatically builds images from provided RPM-OSTree repositories. It’s intended use is as follow up job to building RPM-OSTree commits, but eventually this is up to you. You just need to get the sources at the designated places. The most basic example would be:
include: - project: 'os-forge/rpm-ostree-engine' ref: 'v0.3.4' file: 'gitlab-ci-template.yml' variables: CI_OSTREE_REF_NAME: OSTreeBeard build-ostree: stage: image extend: .build-ostree tags: - arm64 - selinux build-image: stage: image extend: .build-image tags: - arm64 - selinux
The template jobs assume that your repository is present under
/remote-storage/repo in both jobs. How the repo get’s there is up to you. I use SSHFS in production. In the rpm-ostree-engine-example repository you can find a more complete example
.gitlab-ci.yml that makes use of caches to transport the resulting RPM-OSTree repository over to the image build job.
By default the image build job will upload the resulting
os-iot.raw.xz as artifact you can then download in gitlab UI. Make sure that the artifacts file size quotas for your gitlab repository are high enough. A typical Fedora IoT 33 image will take up to 750 MB.
Take A Look At The Guts - Eww
The implementation can be found in
We begin with writing some zeros into a file which we’ll later mount as virtual device. Using parted we partition this file before setting up the loopback device and mounting the file. Next we can create filesystems, prepare the boot partition, etc. The interesting part is when we use
ostree to initialize a new OSTree based system and deploy our latest commit onto the root partition. After that we need to setup some common folders and SELinux permissions. Finally we need to place pre-compiled fstab, grub.cfg and bootloader for RPi3 and RPi4. Thanks to the coreos-assembler and osbuild projects for pointing that out.
Since we are aiming to run this in a container the container has to be executed with
CAP_MAC_ADMIN capability in order to have access on
loopback devices. Apparently
dd also needs permission to write to
os-iot.raw. For simplicity I’ve resorted to
--privileged for now. Apart from that
podman for that matter have to be run with
sudo so they are allowed to do their magic. That’s something I have to investigate more.
We found that it might be necessary to “probe” the
loopX devices with
mknod before trying to allocate them with
losetup. I have no idea why though. That’s why you will find that strange for-loop in the Gitlab-CI template probing those devices.
Any thoughts of your own?
Feel free to raise a discussion with me on Mastodon or drop me an email.