9.5 KiB
ChromeOS Flex on hp-chell's internal eMMC
hp-chell (HP Chromebook 13 G1, MrChromebox UEFI firmware) dual-boots:
| OS | Disk | Notes |
|---|---|---|
| KDE neon | sda, external USB/SD card (117GB) |
primary OS, owns GRUB |
| ChromeOS Flex | mmcblk0, internal eMMC (29GB) |
installed from neon, see below |
Because neon lives entirely on external media, the eMMC can be wiped and reinstalled from the running neon system with no risk to neon.
Installing / reinstalling
sudo ./install-flex-emmc.sh ~/Downloads/chromeos_<ver>_reven_recovery_stable-channel_<keys>.bin.zip
- Accepts the
.bin.zipstraight from Google's Flex download or an already-extracted.bin(zips are extracted to/var/tmp/flex-install). - Destination defaults to
/dev/mmcblk0; pass a second argument to override. The destination is wiped entirely. - Refuses to run if the destination holds the running root filesystem,
shows the target's layout, and asks you to type
yes. - Takes about 2.5 minutes. Full installer log:
/var/log/flex-install.log. UEFI boot entries from before the run are saved to/var/log/flex-install-efiboot-before.txtand the current ones are printed at the end. - Updates the GRUB chainload entry (below) with the new EFI partition's
UUID and runs
update-grub.
Last installed: 2026-09-27, Flex 16640.57.0 (stable, reven).
Why not just dd the image
The Flex recovery .bin is a full GPT disk image, but it's the
installer layout, not an installed system:
| Partition | Installer image (raw dd) |
Proper install |
|---|---|---|
| ROOT-A | 2.4GB | 4GB |
| ROOT-B | 2MB placeholder | 4GB (A/B updates) |
| STATE | fixed 6GB | rest of the disk (~20.6GB) |
| unallocated | ~20.7GB | none |
Flex decides whether it's running from installer media with
/usr/sbin/is_running_from_installer, which compares the ROOT-A and
ROOT-B sizes. A raw dd therefore boots as live media ("Install
ChromeOS Flex / Try it first"), can't auto-update, and never grows
STATE. There's no flag to set, and resizing in place isn't
practical (ROOT-B sits in a 2MB gap between the ESP and ROOT-A). This
was the state from 2026-09-04 (raw dd) until the 2026-09-27 reinstall.
The fix is to run the image's own chromeos-install, which is what the
"Install ChromeOS Flex" button runs, against the eMMC.
How the script works
- Adds a temporary udev rule (
/run/udev/rules.d/99-flex-install-ignore.rules,UDISKS_IGNORE=1) so the Plasma desktop doesn't auto-mount eMMC partitions mid-install, then unmounts anything already mounted there. - Attaches the
.binread-only as a partitioned loop device. - In a private mount namespace (
unshare -m; every mount is discarded on exit), mounts the image's ROOT-A read-only and builds a chroot: tmpfs/tmp/run/var/mnt/stateful_partition, bind/dev/sys, fresh/proc. - Masks every disk except the destination: each
/dev/sd*,/dev/nvme*, other/dev/mmcblk*, and/dev/vd*node gets/dev/nullbind-mounted over it, and/dev/diskand/dev/blockare hidden. Inside the chroot, reading/dev/sdareturns 0 bytes, so neon is unreachable. - Runs:
chromeos-install --dst /dev/mmcblk0 --payload_image /tmp/payload.bin \ --skip_dst_removable --yes --target_bios efi --debug - Checks the log for success, re-reads the partition table, and updates GRUB. Cleanup (loop device, udev rule) runs on exit, including on failure.
Gotchas when running chromeos-install from another Linux
Each of these broke an attempt during the 2026-09-27 install:
No recognized cros_XXX bios option on kernel command line:chromeos_postinstreads/proc/cmdlineforcros_efi(orcros_legacyetc.), even though it's passed--bios efi. The script bind-mounts a copy of the command line withcros_efiappended over/proc/cmdline.chromeos-postinstthen re-chroots into the freshly written root with a non-recursivemount --bind /proc, which drops that overmount. So the image'schroot(a coreutils shim at/bin/chroot) is also overmounted with a wrapper that re-applies the fake cmdline inside the target root before exec'ing the realcoreutils --coreutils-prog=chroot.sfdisk: This disk is currently in use: udisks auto-mounted the new STATE partition (/media/matt/H-STATE) from a previous attempt. The udev rule in step 1 above handles this.- Exit status can't be trusted:
chromeos-installexited 0 while its innerchromeos-install.shhad failed. It also logs harmlessERROR cros_installerlines on success (metrics/var/lib/metrics/uma-events, "Couldn't check the current permission"). The script treats the run as successful only if the log hasInstallation to '/dev/mmcblk0' complete.and no finalError:line. Touch(/mnt/stateful_partition/.install_completed) failed: postinst marks the installer's STATE as done. In the chroot that's the read-only image, so the script gives it a throwaway tmpfs.- Harmless noise:
initctl(no Upstart),Failed to find physical volume,BLKRRPART: Device or resource busy, and ChromeOS ACPIGetAcpiSysfsPatherrors (not stock ChromeOS firmware). nohup … &over SSH got killed when the session closed, so long runs go throughsystemd-run --unit=... -p StandardOutput=file:/var/log/.... The log must not be in/tmpif another user owns the file (root is denied byfs.protected_regular).
Resulting layout (2026-09-27)
mmcblk0 29.1G
├─mmcblk0p1 20.6G ext4 STATE
├─mmcblk0p2 64M KERN-A (priority=1 successful=1)
├─mmcblk0p3 4G ext4 ROOT-A
├─mmcblk0p4 64M KERN-B
├─mmcblk0p5 4G ext4 ROOT-B
├─mmcblk0p6-7 512B KERN-C / ROOT-C
├─mmcblk0p8 16M ext4 OEM
├─mmcblk0p9-11 512B reserved / reserved / RWFW
└─mmcblk0p12 256M vfat EFI-SYSTEM (UUID CBE4-703E)
The ESP is copied from the image, so its FAT UUID (CBE4-703E) survived
the reinstall unchanged. The UEFI boot entries and BootOrder were
untouched.
Booting Flex
Two ways:
- From GRUB (neon's menu, shown for 5s): the entry "ChromeOS Flex
(internal eMMC)" in
/etc/grub.d/40_customchainloads Flex's own EFI loader (crdyboot) from the eMMC's ESP:Runmenuentry "ChromeOS Flex (internal eMMC)" { insmod part_gpt insmod fat insmod chain search --no-floppy --fs-uuid --set=root CBE4-703E chainloader /EFI/BOOT/BOOTX64.EFI }sudo update-grubafter editing40_custom. The install script rewrites the--fs-uuidvalue automatically. - From the firmware boot picker (MrChromebox UEFI,
Escduring POST):efibootmgr -vshowsneon(Boot0005, on the USB card) andInternal eMMC(Boot0001), withBootOrder0005,0001,0000,0003,0002. With the USB card unplugged, the firmware falls through toInternal eMMCand boots Flex with no menu.
Other ChromiumOS-based images: FydeOS
Checked 2026-09-27, by reading the image only (not installed):
FydeOS_for_PC_iris_v23.0-SP1-io.bin.zip (board amd64-fydeos_iris-io,
version 16700.56.23.51), in matt's ~/Downloads (extracted copy in
~/fydeos-check). install-flex-emmc.sh should work with it
unchanged.
- Same installer image layout: ROOT-A is p3 (2.7GB), ROOT-B is a
2MB placeholder, the ESP is p12. The same
chromeos-install(Rust) andchromeos-install.sh,write_gpt.sh,cros_installer,is_running_from_installer, and coreutils/bin/chrootshim, so the chroot wrapper applies. cros_installerhas the sameNo recognized cros_XXX bios optioncheck, so the fakecros_efiis still needed. The success line is the sameInstallation to '${DST}' complete.- The target layout from
write_gpt.shis 2.66GB each for ROOT-A and ROOT-B, with STATE filling the rest of the disk. - FydeOS's
chromeos-postinstadds steps: it mounts the destination's ESP (${install_dev%[0-9]}12) to sync acros_debug(dev-mode) flag in itsgrub.cfg, then runs/usr/share/dualboot/update_initrd.shifrootdevreturns a loop device, which it does in our chroot. That's harmless:update_initrd.shexits unlessfydeos_dualbootis on/proc/cmdline, which isn't in our faked cmdline, and other disks are masked anyway. - The ESP uses GRUB (
/efi/boot/bootx64.efi+grub.cfg, kernels in/syslinux/vmlinuz.{A,B}) instead of Flex'scrdyboot. The40_customchainload path still works. It's unsigned, which is fine because Secure Boot is disabled on hp-chell (mokutil --sb-state). The image's ESP UUID isF35E-F13C.
Caveats before trying it:
- It replaces Flex (same eMMC, full wipe).
- The script rewrites the UUID in the
40_customentry titled "ChromeOS Flex", so FydeOS would boot under that title. Rename the entry, or make the script take its title from the image'slsb-release. The script's messages and this doc also say "Flex" throughout. - Hardware support is unverified: it's unclear whether the
irisbuild supports this 6th-gen Core m7-6Y75 / HD 515 (Skylake). Check FydeOS's hardware compatibility list first.
History
- 2026-09-04: first install by streaming the image with
unzip -p … | dd of=/dev/mmcblk0 bs=4M conv=fsync(~18 min; eMMC writes dropped from ~130MB/s to ~9MB/s as its SLC cache filled, which is normal for this class of eMMC). Booted as live/installer media, as explained above. The note at the time that Flex expands STATE on first boot was wrong. - 2026-09-27: reinstalled properly with
install-flex-emmc.sh.