Skip to content

feat(initramfs): replace the scripted initramfs with omnect-os-init - #697

Open
JanZachmann wants to merge 114 commits into
omnect:mainfrom
JanZachmann:initramfs-integration
Open

JanZachmann wants to merge 114 commits into
omnect:mainfrom
JanZachmann:initramfs-integration

Conversation

@JanZachmann

@JanZachmann JanZachmann commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

omnect-os moves from the scripted initramfs to omnect-os-init, an init written in Rust. This PR is the main integration step: it builds the init, installs it as /init and removes the scripted initramfs (init.d/* from omnect-os-initramfs-scripts).

  • omnect-os-init: Cargo features follow the machine and distro (grub or uboot, the partition table from OMNECT_PART_TABLE, factory-reset, resize-data, persistent-var-log, release-image, flash-mode-2, flash-mode-2-direct when OMNECT_FLASH_MODE_2_DIRECT_FLASHING is 1, flash-mode-3, rtc from the machine feature, and early-modules when OMNECT_EARLY_MODULES is set). The partition and bootloader constants that flash mode 1 needs are exported into the build; for flash mode 2 also the boot partition offset and size and OMNECT_USER_ID.
  • OMNECT_PART_TABLE (gpt or dos, default gpt) selects the partition table the init is built for; a machine whose wks file uses --ptable msdos sets dos.
  • OMNECT_USER_ID (uid and gid of the omnect user) is a distro variable, so the init recipe and omnect_user.bbclass share one value.
  • omnect-os-initramfs: installs the tools the init calls (bash for the debug shell on non-release images, e2fsprogs, dosfstools, util-linux-sfdisk, util-linux-blkid on GRUB machines, efibootmgr on EFI machines, dhcpcd and base-passwd for flash modes 2 and 3, dropbear and the omnect user for flash mode 2, curl and ca-certificates for flash mode 3). It drops the legacy script packages, the initramfs-framework modules, os-release, jq, kmod, util-linux-uuidgen, bmaptool and xz. dhcpcd is built without udev: it runs only in the initramfs, where udevd does not run, so it no longer pulls udev into the initramfs. The fsck.fat links in /sbin are created only when /sbin is not merged into /usr/sbin; on a merged image they replaced fsck.fat with a link to itself, so the boot partition was never checked.
  • GRUB passes bootpart_fsuuid instead of rootblk, so the init finds its disk by the filesystem UUID of the boot partition (partition 1, read with probe). A flash mode 1 clone gets a new boot volume ID (omnect-os-init#38); the rootfs UUID would be the same on the clone and its source.
  • GRUB boots from the disk it was loaded from (taken from $cmdpath, which needs the regexp module), not from hd0. hd0 is the first disk of the firmware: with a flash mode 1 clone and its source both attached, GRUB read the grubenv of the clone but loaded kernel and rootfs from the source.
  • U-Boot declares every environment variable the OS writes at runtime as writable (omnect_first_boot_done, omnect_fsck_*, omnect_factory_reset_last_error, flash-mode-url, flash-mode-url-sha256), so a saveenv in omnect_update_flow keeps them. data-mount-options and resized-data are no longer listed, because the init reads neither.
  • u-boot-scr: the boot script no longer appends the literal text None to bootargs when OMNECT_APPEND_1ST_BOOT is unset, as on developer images.
  • OMNECT_EARLY_MODULES lists the kernel modules the init loads before switch_root (imx_sdma on phytec-imx8mm). It selects the early-modules feature, and the new package omnect-os-init-early-modules installs the list into the rootfs as /etc/omnect/early-modules.d/omnect.conf. The omnect-os-init recipe is built per machine, because its features and build.rs read machine settings. rpi kernels build compressed modules, so they get CONFIG_MODULE_DECOMPRESS when the variable is set.
  • omnect-base-files adds the mount point /mnt/rootCurrentPrivate, where the init bind-mounts the root filesystem. Its tmpfiles config creates /mnt/cert/ca and /mnt/cert/priv (d instead of z), because the init does not create them.
  • The busybox poweroff config is removed; the init powers off itself.
  • Removed with the scripted initramfs:
    • The recipe omnect-os-initramfs-scripts. A layer with a .bbappend for it no longer parses, and own init.d scripts are no longer possible.
    • Factory reset wipe mode 4, a custom wipe added by such a .bbappend. A request with mode 4 now gets status 1.
    • The bootloader variable data-mount-options; the data partition is always mounted with the default options.
    • An optional preserve: the factory-reset request must now contain it, else it gets status 3.
  • omnect-device-service reads the factory-reset result the init writes (omnect-device-service#207).
  • The expected bootloader checksums of genericx86-64, rpi and phytec-imx8mm follow the changed bootloader inputs.
  • README and doc/: keep the build settings and link to the omnect-os-init docs for the behaviour of flash modes, factory reset, filesystem check and the status file.

Reason

The initramfs moves from shell scripts to one Rust binary, for typed error handling, a defined result contract towards omnect-device-service, and unit tests for the boot, factory-reset and flash paths.

  • The omnect-os-init recipe builds the released version 0.1.0. It contains omnect-os-init#40, which keeps what the scripted initramfs did: /run without suid and device files, the links of create-fs-links.txt, the persistent logs in /mnt/data/log, and the restored directories with their owner and mode.
  • The omnect-device-service recipe builds from the branch of omnect-device-service#207. It moves to a released version before this merges.
  • omnect-ui#132 reads the new factory-reset result and no longer offers mode 4. The three PRs have to reach a release together.
  • The bootloader version changes on every machine, because omnect_env.h and the GRUB config are bootloader inputs (on phytec-imx8mm the boot script is one too); devices get a bootloader update with this image. A layer whose bootloader checksum covers recipes-bsp/u-boot/u-boot/* needs a new expected value at the same time.

JoergZeidler and others added 25 commits June 30, 2026 10:55
Refactors GRUB's root partition identification by transitioning from a static `rootblk` parameter to a dynamic filesystem UUID. This enhances boot reliability by eliminating dependency on potentially unstable block device names. The GRUB `probe` command is leveraged to detect the boot partition's UUID and pass it to the kernel as `bootpart_fsuuid`.
Adds `util-linux-blkid` to the initramfs, providing essential tools for robust identification of block devices, which is crucial for locating and mounting the root filesystem.

Introduces conditional Cargo features for the Rust-based `omnect-os-init` based on the detected bootloader (`grub` for EFI, `uboot` otherwise). This allows the init process to be compiled with bootloader-specific logic, improving its adaptability and ensuring correct behavior across different boot environments.
Refreshes the recipe to align with the latest upstream changes in the omnect-os-init repository.

Bumps the SRCREV for the primary source.
Significantly reduces the number of Rust crate dependencies and their checksums, resulting in a leaner dependency footprint for the project.
Ensures the `resize-data` feature is appended with a comma as a prefix, aligning with the expected comma-separated format for Cargo features. This consistency prevents incorrect parsing of build features.
Updates the `omnect-os-init` Rust component to a newer commit.
Includes `e2fsprogs-e2fsck` in the initramfs image to enable
filesystem checks, likely required by the updated init for enhanced
system integrity and recovery.
…features

Enable GPT support by default for all targets and conditionally enable features for release images and persistent logging based on build configuration.
omnect_first_boot_done, omnect_factory_reset_last_error and omnect_fsck_<part>
were missing from the writeable env flags list. Unlisted variables are not
guaranteed to survive a u-boot saveenv, and omnect_update_flow calls saveenv on
both update paths, so the variables are lost across an update boot while
omnect_extra_bootargs and the other listed ones survive.

Observed effect: after an A/B update the device reports first_boot true again
and repeats the first-boot work (resize preflight, extra-bootargs sync). The
same applies to a stored fsck record, which is exactly the case the env is
meant to carry across a reboot.

The list now matches BootEnvKey::as_str() in omnect-os-init. The fsck entries
name the partitions the initramfs runs fsck on; the flags syntax takes exact
names, so they are enumerated.

No change needed for grub: it persists named variables via save_env and keeps
the rest of the block, so it has no equivalent drop.

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
omnect_first_boot_done, omnect_factory_reset_last_error and omnect_fsck_<part>
were missing from the writeable env flags list. Unlisted variables are not
guaranteed to survive a u-boot saveenv, and omnect_update_flow calls saveenv on
both update paths, so the variables are lost across an update boot while
omnect_extra_bootargs and the other listed ones survive.

Observed effect: after an A/B update the device reports first_boot true again
and repeats the first-boot work (resize preflight, extra-bootargs sync). The
same applies to a stored fsck record, which is exactly the case the env is
meant to carry across a reboot.

The list now matches BootEnvKey::as_str() in omnect-os-init. The fsck entries
name the partitions the initramfs runs fsck on; the flags syntax takes exact
names, so they are enumerated.

No change needed for grub: it persists named variables via save_env and keeps
the rest of the block, so it has no equivalent drop.

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
Keep the legacy shell initramfs scripts deleted - the rust init replaces
them and nothing on this branch references them any more.

Take the phytec bootloader checksum from upstream. Both sides changed the
u-boot env, so the merged tree needs a freshly built value; the checksum
check reports it on the next tauril2 build.

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
The recipe is generated by cargo-bitbake and sets S = "${WORKDIR}/git",
which do_qa_unpack in insane.bbclass rejects with a fatal error.
bitbake.conf sets S = "${UNPACKDIR}/${BP}", so assign that in the .inc,
which is included after the generated assignment.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
do_install moves the binary to /init and removes /usr/bin, which leaves an
empty /usr in the initramfs.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The recorded checksum no longer matches the built bootloader, so the build
fails.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
@JanZachmann
JanZachmann marked this pull request as draft September 18, 2026 13:34
…de 4

The mode table named the tools the legacy shell script used (dd, rm plus
fstrim). The Rust init overwrites the whole partition from /dev/urandom and
discards it with an ioctl, so the table now states what a mode does, not how.

Mode 4 ran a customer-supplied wipe hook. It is not ported: it handed the
wipe, the one step the caller asked for, to code that is not built or tested
here, and modes 2 and 3 cover rotating disks and flash. A mode-4 trigger is
rejected as invalid, so its row and the custom-wipe instructions are gone.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
# Conflicts:
#	conf/machine/include/rpi.inc
The recipe fetched the init from a personal fork on a branch that stops
before the factory-reset wipe modes. Point it at the branch under review so
an image carries them, and update the AUTOINC suffix with the revision.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The examples and the field list still described the shell implementation:
the object was named factory-reset, error carried an exit code and was
always present, and status 1 meant an unsupported wipe mode.

The init reports the object as factory_reset, leaves error and context out
unless something went wrong, adds data_wiped, and uses status 4 for a reset
that needed a second format attempt. A trigger it cannot use at all never
reaches the reset, so that case is described separately.

Also drops the link to the removed initramfs script directory.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
JanZachmann added a commit to omnect/omnect-os-init that referenced this pull request Oct 26, 2026
…ne (#38)

## Summary

fsck: for vfat, an exit code with bit 2 now counts as a failed check,
not as "reboot required", and fsck.fat codes are logged as numbers
instead of fsck(8) bit names. ext4 keeps the reboot. The boot partition
record is now always written to grubenv, because a vfat check can no
longer ask for a reboot.

A failed fsck logs the last lines of its output, so the reason (e.g.
`open: Input/output error`) reaches kmsg. Without a bootloader
environment, `persist_fsck_results` warns that the fsck status is not
saved to it. `output_tail` moved to `logging` for this and returns `(no
output)` when nothing is left; this also changes the curl and e2image
error reasons of flash modes 1 and 3.

flash mode 1 (GRUB): after the boot partition is copied, `fatlabel -i
-r` gives the clone a new boot volume ID.

`docs/usage.md` describes both changes.

## Reason

fsck.fat has no reboot exit code. It exits with 2 on a usage error and
with 6 when it cannot open the device (dosfstools 4.2). The bitmask test
handled both as "reboot required", so such a device rebooted on every
boot. Now the boot continues as for any other failed boot partition
check, and the mount decides.

GRUB passes the filesystem UUID of the boot partition as
`bootpart_fsuuid=` (with omnect/meta-omnect#697, which probes partition
1), and the init finds its disk with `blkid --uuid`. The clone copies
the boot partition byte for byte, so it had the same volume ID as the
source. With the source disk still attached, blkid could return the
source, and the init mounted the source partitions under the kernel of
the clone. The unit tests pin the `fatlabel` arguments, the step order,
and that a failed `fatlabel` stops the clone before the EFI entry is
created.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The recipe now builds the head of the factory reset contract change, which also has the firmware update order fix for bootloader updates.

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
The initramfs writes flash-mode-url and flash-mode-url-sha256 for flash mode 3. The checksums of rpi and phytec-imx8mm change with omnect_env.h.

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
The initramfs no longer creates /mnt/cert/ca and /mnt/cert/priv, so tmpfiles creates them when they are missing.

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
Both recipes are generated with cargo bitbake. The init now keeps the behaviour of the scripted initramfs: /run hardening, create-fs-links.txt, the move of the persistent logs, and freeing the initramfs memory.

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
cargo bitbake writes an AUTOINC version, and every git crate adds its revision to SRCREV_FORMAT. The ipk file name then gets too long and do_package_write_ipk fails. Only the revision of omnect-device-service goes into the version now.

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
The recipe is generated with cargo bitbake. The init now writes the fsck logs and the flash mode logs readable only by root.

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
JanZachmann added a commit to omnect/omnect-os-init that referenced this pull request Oct 27, 2026
## Summary

A boot with `omnect_validate_update`, `omnect_validate_update_failed` or
`omnect_bootloader_updated` set is no first boot any more, and an
unreadable flag counts the same. On such a boot
`handle_update_validation` writes `omnect_first_boot_done`, and it
clears `omnect_validate_update_failed` and `omnect_bootloader_updated`
only after that write worked; if the write fails, the flag stays set,
the next boot tries again, and the init logs an error.
`omnect_validate_update` is cleared by ODS, so on a validation boot a
failed write is not retried: the boot after a successful validation is a
first boot. A flash-mode boot does not write the marker. Both marker
writers use one helper on `BootEnv`. The extra-bootargs sync gates on
`first_boot` alone again; its own `update_pending` check and the
`update_pending` field of `InitSetupCtx` are removed. `docs/usage.md`
and the affected specs describe the new `first_boot` value.

## Reason

A device updated from the scripted initramfs has no
`omnect_first_boot_done`. The init took the first boot after the update
for the first boot after flashing: it reported `first_boot: true` and
ran the data resize again on a device in use. The extra-bootargs sync
skipped only validation boots, but an update that also updates the
bootloader is not validated: the update handlers set
`omnect_bootloader_updated` instead of `omnect_validate_update_part`.
The integration in omnect/meta-omnect#697 changes the bootloader on
every machine, so it takes exactly this path.

An update ends the first-boot phase: a device whose first-boot resize
never succeeded no longer retries it after an update. The unit tests pin
each update flag and a read error on each, that the marker is written
before a flag is cleared and that no flag is cleared when the write
fails, a failed write on a validation boot that still creates the
trigger file, and over two boots on one mock that a fresh flash and each
update flag end the first-boot phase after one boot.

---------

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The recipe is generated with cargo bitbake. The init now deletes the local update validation config on boots without update validation.

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
The recipes are generated with cargo bitbake from the current heads of the init and omnect-device-service branches.

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
@JanZachmann
JanZachmann marked this pull request as ready for review October 28, 2026 09:18
JanZachmann added a commit to omnect/omnect-os-init that referenced this pull request Oct 28, 2026
…ate validation config (#40)

## Summary

Keeps behaviours of the scripted initramfs that the init did not keep,
and removes a stale update validation config:

- `/run` is mounted with `nosuid`, `nodev`, `strictatime` and mode 0755.
- The links in `/etc/omnect/create-fs-links.txt` (one `<target> <link>`
pair per line) are created once, then the file is deleted.
- With `persistent-var-log`, `/var/log` is `/mnt/data/log`, the
directory of the scripted initramfs. The fsck logs are written to
`/mnt/data/log/fsck`.
- After `switch_root` the initramfs files are deleted, so their RAM is
freed. The delete does not cross into mounted filesystems, and the
number of deleted entries is logged. When `/` is not a ramfs or tmpfs,
the files are kept and the reason is logged.
- A factory reset restore creates missing parent directories with the
owner and mode from the backup.
- The fsck logs and the flash mode logs are readable only by root: files
0600, the fsck log directory 0700. The scripted initramfs kept fsck
output only in the status file, which has mode 0600.
- `parted` runs in script mode, and a failed `parted print` is an error.
- An empty `bootpart_fsuuid=` fails at once with a clear error instead
of waiting for the timeout.
- An fsck record in the env that does not unpack or has no exit code
line (the format of the scripted initramfs) is deleted instead of
staying there on every later boot. The text of a record without exit
code is logged first.
- On a boot without `omnect_validate_update`,
`/mnt/data/var/lib/omnect-device-service/update_validation_conf.json` is
deleted. A missing file is not an error, a failed delete is logged as a
warning. On a validation boot the file is kept.
- `tests/contract` holds the status file for every factory reset result,
and a test checks that the init writes exactly these files.
- `docs/usage.md` describes the symlinks, the removal of the update
validation config, and that a failed EFI boot entry ends a flash mode
with a fatal error.
- `docs/usage.md` and the README warn that flash mode 3 does not check
TLS certificates on a machine without `rtc`.
- Two wrong comments are fixed: the init does not create `/mnt/cert/ca`
and `/mnt/cert/priv`, and GRUB probes the boot partition of its own
disk.

## Reason

These are behaviours of the scripted initramfs that the init did not
keep, or kept only partly. A device updated from the scripted initramfs
would lose its log history and its one-time links, and run with a `/run`
that allows suid binaries and device files. omnect/meta-omnect#697
builds the init into omnect-os and needs these fixes.

omnect-device-service writes the update validation config before the
reboot into the validation boot of an update it runs itself
(`set_boot_partition`), and removes it after a successful validation. A
failed validation reboots without removing it. An ADU update does not go
through omnect-device-service: the swupdate handler only sets
`omnect_validate_update_part` and does not write the file, but the
validation reads it. So after a failed local validation, the next ADU
update is validated as a local one: no IoT Hub connection check, and
deviceupdate-agent is not started by omnect-device-service. A config
written by hand is also deleted on every boot without validation;
omnect/omnect-device-service#207 adds this to the update validation
docs.

The contract files close a gap that no device test can see: the status
file is the interface to omnect-device-service.
omnect/omnect-device-service#207 adds a copy of these files and a test
that parses each one. The error-after-wipe and warning cases are built
by the factory reset status code, so they hold values the init really
writes.

---------

Signed-off-by: Jan Zachmann 50990105+JanZachmann@users.noreply.github.com
The recipe is generated with cargo bitbake from the omnect-os-init main
branch at tag 0.1.0.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
@JanZachmann JanZachmann changed the title Initramfs integration feat(initramfs): replace the scripted initramfs with omnect-os-init Oct 28, 2026

@JoergZeidler JoergZeidler left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review pass on head 7df8c9c, reviewed against omnect-os-init@0.1.0 and the sibling-repo contracts. Findings are inline, most severe first. The two High items (rtc feature resolution, A/B rollback safety) need a build to settle — see the inline notes. Verified OK and not commented: SRCREV = tag 0.1.0, OMNECT_USER_ID default 15581, and the added U-Boot env keys match the init's BootEnvKey strings (but the fsck set is incomplete — see omnect_env.h).

Comment thread recipes-bsp/grub/grub-efi/omnect.inc.in Resolved
SRC_URI += "git://github.com/omnect/omnect-device-service.git;protocol=https;nobranch=1;branch=main"
SRCREV = "9b62e97f07e4dbbed66d6cc4b8ceb155411b763d"
# SRC_URI += "crate://crates.io/omnect-device-service/0.46.1"
SRC_URI += "git://github.com/JanZachmann/omnect-device-service.git;protocol=https;nobranch=1;branch=jz-2026-07-28-factory-reset-json-contract"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High (acknowledged blocker) — builds from a personal fork on a WIP branch: JanZachmann/omnect-device-service, branch jz-2026-07-28-factory-reset-json-contract, and PV still carries .AUTOINC+e9488c7e74. The PR text already says this must move to a released version before merge. Point it back to omnect/omnect-device-service on a released SRCREV (as omnect-os-init already does) and drop the AUTOINC suffix.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this stays open until the ods PR is merged and released. Then the recipe is generated again with cargo bitbake from the omnect repo, without the AUTOINC suffix.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. Leaving this thread open as the merge gate until the ODS PR is released and the recipe is regenerated from the omnect repo.

Comment thread recipes-bsp/u-boot/u-boot/omnect_env.h Resolved
Comment thread recipes-bsp/grub/grub-efi/omnect.inc.in Resolved
Comment thread README.md Resolved
@JoergZeidler
JoergZeidler self-requested a review October 29, 2026 14:01
GRUB falls back to hd0 when cmdpath names no disk, and boots with an
empty bootpart_fsuuid when probe fails. Both cases now print a message,
so a failed boot has a hint on the console.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants