Convert a Debian or Raspberry Pi OS image
otapulse-convert takes a disk image of a standard Linux distribution and turns it into an OTA-Pulse image for bootloader compatibility mode.
You do not need a Yocto build.
The converted image has:
- a boot partition,
- two root file system partitions, A and B,
- a data partition,
- the bootloader (GRUB or U-Boot), set up to count the boots of a fresh update and to revert it on its own,
- the OTA-Pulse agent (
soc-ota-agent) and the monitoring daemon (socmond), - a
.otapulseartifact of its root file system, which you can sign with your key.
Supported inputs
Section titled “Supported inputs”| Input image | Configuration | Status |
|---|---|---|
| Debian 12 arm64 “genericcloud” or “nocloud” (UEFI, GRUB) | configs/debian_arm64_uefi_config | Converted and booted under QEMU. Virtio disk (/dev/vda) only. |
| Raspberry Pi OS (Debian 12) arm64, U-Boot | configs/raspberrypi/uboot/debian/raspberrypi4_bookworm_64bit_config | Not run. |
| 32-bit armhf or amd64 images | Untested. Other boards, Ubuntu, 32-bit images and Raspberry Pi UEFI are not included. | |
| An image with no GRUB of its own (standalone GRUB) | Untested. The conversion stops with a message that the bootloader assets are not listed. | |
| EFI secure boot, BIOS boot | Untested. The GRUB environment scripts and configuration are not signed. |
The generic arm64 configuration assumes a virtio disk.
For a real UEFI board, copy it and set OTAPULSE_STORAGE_DEVICE_BASE to the disk of the board, for example /dev/nvme0n1p.
Or set OTAPULSE_ENABLE_PARTUUID=y together with the four partition options.
Change the console arguments too.
What you need
Section titled “What you need”- A Linux host with Docker. The container runs with
--privilegedbecause the conversion uses loop devices and mounts. - For an image of another architecture than the host (arm64 on an x86-64 host),
qemu-userregistered in the kernel throughbinfmt_misc. The tool does not change this. - Network access during the conversion. The tool installs the dependencies of the two packages, and
partedandgdisk, from the package repositories of the input image. - The tool tarball
otapulse-convert-<version>.tar.gz. It holds the tool and the artifact tool. It holds thesoc-ota-agentandsocmondpackages only if it was built with them; otherwise copy the.debfiles for your architecture intoinput/packages.
Convert an image
Section titled “Convert an image”-
Unpack the tool and check it
Terminal window tar -xzf otapulse-convert-<version>.tar.gzcd otapulse-convert./otapulse-convert --version./otapulse-convert --help--helpprints the command line. It shows the options--disk-image,--config,--overlay,--versionand--help. -
Build the container image once
Terminal window ./docker-build -
Put the input image in place
Terminal window cp debian-12-genericcloud-arm64.raw input/The packages for the right architecture are read from
input/packages. -
Write a configuration with your values
Create
input/my_config. This is a plain shell file. All options and their defaults are inconfigs/otapulse_convert_config.Terminal window OTAPULSE_SERVER_URL="https://<your-server>"OTAPULSE_DEVICE_TYPE="my-device"OTAPULSE_ARTIFACT_SIGNING_KEY="/otapulse-convert/input/release-private.pem"OTAPULSE_PANIC_TIMEOUT_S="10"The signing key path is the path inside the container. Put the key in
input/. -
Convert
Terminal window OTAPULSE_ARTIFACT_NAME="release-1" ./docker-otapulse-convert \--disk-image input/debian-12-genericcloud-arm64.raw \--config configs/debian_arm64_uefi_config \--config input/my_configOTAPULSE_ARTIFACT_NAMEis required. The device reports it as its running software version, and different updates need different names. The conversion took about 2 minutes in our runs.
The result is in deploy/:
<name>.img: the disk image to flash or boot,<name>.ext4: the root file system,<name>.otapulse: the artifact of the root file system.
The file name is built from the input image name and the device type. The tool also prints where its debug log is. That log holds every command the tool ran.
The converted layout
Section titled “The converted layout”On the Debian 12 arm64 image the result was a GPT disk of 8 GiB:
| Partition | Size | Use |
|---|---|---|
| 1 | 128 MiB | EFI system partition, mounted at /boot/efi |
| 2 and 3 | about 3.9 GiB each | Root file system A and B |
| 4 | 128 MiB, grown on first boot | /data |
The data partition grows to the end of the disk on the first boot.
In our run it went from 128 MiB to 152 MiB, which was all the free space.
The image’s own GRUB is kept and gets the A/B boot scripts.
Every kernel line carries panic=<seconds>.
Configuration options
Section titled “Configuration options”Every option has the prefix OTAPULSE_.
Set your own values in a file and pass it with --config.
The defaults file is always read first.
| Option | Default | Meaning |
|---|---|---|
OTAPULSE_ARTIFACT_NAME | none | Required. Pass it in the environment. The name of the image and update. |
OTAPULSE_DEVICE_TYPE | the hostname of the input image | The device type. The agent installs an artifact only if its device type list contains this value. |
OTAPULSE_SERVER_URL | empty | Written into the agent and socmond configuration at the first install of the packages. Plain URL characters only. If it is empty, edit /etc/otapulse/otapulse.conf and /etc/socmond/config.json afterwards. |
OTAPULSE_ARTIFACT_SIGNING_KEY | empty | RSA private key that signs the .otapulse artifact. With none, the artifact is unsigned and the tool warns. A production agent installs only signed artifacts. |
OTAPULSE_PANIC_TIMEOUT_S | 10 | panic=<n> on every kernel line. The conversion fails if the value is not positive. |
OTAPULSE_GRUB_KERNEL_BOOT_ARGS | empty | Extra kernel arguments for the image’s own GRUB, such as the console. |
OTAPULSE_MACHINE_ID_PERSIST | y | Keeps the machine id on the data partition across updates. Without it every update would register the device again under a new identity. |
OTAPULSE_SOCMOND_WATCHDOG | n | y enables socmond-watchdog. See below. |
OTAPULSE_INSTALL_INPUT_PACKAGES, OTAPULSE_INSTALL_INPUT_PACKAGES_PATH | y, input/packages | Install the .deb files in that directory (the right architecture only). The conversion fails if soc-ota-agent or socmond is not installed afterwards, or if /etc/otapulse/boot_mode is not bootloader. |
OTAPULSE_STORAGE_TOTAL_SIZE_MB | 8192 | Size of the medium the image is written to. |
OTAPULSE_BOOT_PART_SIZE_MB, OTAPULSE_DATA_PART_SIZE_MB | 40, 128 | Boot and data partition sizes in MiB. |
OTAPULSE_STORAGE_DEVICE_BASE | /dev/mmcblk0p | The device file the partition numbers are appended to, in the bootloader, the fstab and the agent configuration. The Debian arm64 configuration sets /dev/vda. |
OTAPULSE_PARTITION_SCHEME | the scheme of the input | gpt or dos. |
OTAPULSE_ENABLE_PARTUUID | n | Use partition UUIDs instead of device names. Needs OTAPULSE_BOOT_PART, OTAPULSE_ROOTFS_PART_A, OTAPULSE_ROOTFS_PART_B and OTAPULSE_DATA_PART. |
OTAPULSE_ARTIFACT_COMPRESSION | gzip | lzma, gzip or none. LZMA is 2 to 3 times smaller and about 10 times slower to write. |
OTAPULSE_ASSETS_BASE_URL | empty | Where missing bootloader files (for the Raspberry Pi U-Boot) may be downloaded from. Every file is checked against configs/assets.sha256 either way. A missing or changed file stops the conversion. |
Why the watchdog is off
Section titled “Why the watchdog is off”socmond-watchdog reboots the device when the monitoring daemon dies.
It also forces a reboot when free memory or free space on the root file system drops below 5 percent.
That suits an appliance.
On a general-purpose system it can reboot a machine that is only full.
Converted images therefore ship it disabled.
Set OTAPULSE_SOCMOND_WATCHDOG="y" to enable it.
The packages in the image
Section titled “The packages in the image”The two packages are ordinary Debian packages. You can also install them on a system you prepared yourself.
| Package | Architecture | Content |
|---|---|---|
soc-ota-agent | arm64, armhf, amd64 | The agent, the otapulse alias, soc-ota-provision, the systemd units, the boot guard, the update state scripts, /etc/otapulse/otapulse.conf and /etc/otapulse/boot_mode. |
socmond | all | The monitoring daemon (shell), its watchdog, the core dump handler, soc-ctl, soc-deregister, units and /etc/socmond/config.json. |
Defaults you should know:
/etc/otapulse/otapulse.confships withBootEnvModeset tobootloaderandUseFileBasedBootEnvset tofalse. The agent then reads the bootloader environment and never falls back to the file-based mode. If it finds no environment tool it stops with an error.- Artifact verification is required. The agent reads public keys from
/etc/soc-monitoring/signing-keys/active/production-rsa-public.pem. Your image must put your key there. /etc/otapulse/boot_modeholdsbootloader. An absent file would mean native mode and would let the update scripts rewrite boot files that the bootloader owns.- The packages hold no credential.
/datamust be a mount point. The data seed refuses to run onto the root file system, and the agent needs it. Without/datathe agent does not start.- The data seed copies
/etc/otapulse/device_typeto/data/otapulse/device_typeon the first start. If the data partition carries another device type, the unit fails and the agent does not start. - An upgrade of the
soc-ota-agentpackage does not stop or restart the running agent. The new binary runs from the next start of the unit. An upgrade therefore cannot interrupt an install. - On purge,
/etc/otapulse/device_type,/etc/socmond/provisioning_tokenand everything on/datastay. - The armhf package is built for ARMv7. It does not run on ARMv6 boards such as the Pi Zero or Pi 1.
socmond sets the core_pattern sysctl to collect crash dumps.
A system with systemd-coredump installed will see that setting overridden.
Register the devices
Section titled “Register the devices”A converted image does not include the first-boot registration service of the Yocto images.
That service needs an administrator login in the image, which a customer image must not carry.
So the agent and socmond register separately, and they show up as two server records for one device.
- Agent:
soc-ota-provision --server <url> --token <provisioning token>writes the per-device key to/data/otapulse/otapulse.conf(mode 0600) and restarts the agent. The data partition keeps it across updates. - Monitoring: put the token in
/etc/socmond/provisioning_token(mode 0600) and setserver.base_urlin/etc/socmond/config.json.socmondprovisions itself at its next start.
These commands registered a device on a plain Debian 12 arm64 VM. They have not been run on a converted image.
Names you may see
Section titled “Names you may see”The tool prints no upstream product name in its command line, its messages, its option names or its file names. The debug log is the exception. It records every command, including the shell trace of the bootloader scripts. In that log, and in the message of a failed bootloader step, you can see file, directory and variable names of the bootloader integration. They are the real names of files on the image, so we do not rewrite them. See Names you may see in build logs and on devices.
Known limits
Section titled “Known limits”- Not OTA qualified. No update install, commit or bad-artifact revert through the agent has been run on a converted image. The slot switch and revert were a manual smoke test. We set the flags by hand, slot B booted with
upgrade_available=1, we cut the power while it was uncommitted, and GRUB went back to slot A. - Slot B behavior of the agent was not observed. The machine id restore on a slot that an update has just written is not proven. Only a reboot on slot A was seen.
- Registration. Not proven on a converted image. See above.
- Kernel upgrades. An
aptkernel upgrade rerunsupdate-grubagainst the EFI partition that both slots share. We did not check that the generated GRUB configuration keeps the A/B scripts andpanic=<n>. - The 20 minute boot deadline was not exercised on a converted image. See Boot deadline.
- Raspberry Pi OS and U-Boot. Not run. The prebuilt U-Boot for the Raspberry Pi is pinned by its checksum only. Where to host it is open. Set
OTAPULSE_ASSETS_BASE_URLto your own host. - The container is not reproducible. The base image is pinned by digest, but its
aptpackages are installed from the live repositories. Only the tool itself is reproducible. A snapshot mirror pin is a planned follow-up. - Console. The input image decides the serial console. Set
OTAPULSE_GRUB_KERNEL_BOOT_ARGS. - Warning at boot.
socmond.service: Unknown key 'StartLimitIntervalSec' in section [Service]is harmless on Debian 12.