Files
hp-chell/CHROMEOS-FLEX.md
T
ergosteurandClaude Opus 5.5 08ae7257ca Move ChromeOS Flex notes into CHROMEOS-FLEX.md
Standalone doc covering install/reinstall via install-flex-emmc.sh, why a
raw dd boots as live media, chromeos-install-from-Linux gotchas, the
resulting layout, and boot selection. CLAUDE.md keeps a short quick
reference pointing to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 12:22:23 -04:00

7.3 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.zip straight 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.txt and 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

  1. 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.
  2. Attaches the .bin read-only as a partitioned loop device.
  3. 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.
  4. Masks every disk except the destination: each /dev/sd*, /dev/nvme*, other /dev/mmcblk*, and /dev/vd* node gets /dev/null bind-mounted over it, and /dev/disk and /dev/block are hidden. Inside the chroot, reading /dev/sda returns 0 bytes, so neon is unreachable.
  5. Runs:
    chromeos-install --dst /dev/mmcblk0 --payload_image /tmp/payload.bin \
        --skip_dst_removable --yes --target_bios efi --debug
    
  6. 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_postinst reads /proc/cmdline for cros_efi (or cros_legacy etc.), even though it's passed --bios efi. The script bind-mounts a copy of the command line with cros_efi appended over /proc/cmdline. chromeos-postinst then re-chroots into the freshly written root with a non-recursive mount --bind /proc, which drops that overmount. So the image's chroot (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 real coreutils --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-install exited 0 while its inner chromeos-install.sh had failed. It also logs harmless ERROR cros_installer lines 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 has Installation to '/dev/mmcblk0' complete. and no final Error: 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 ACPI GetAcpiSysfsPath errors (not stock ChromeOS firmware).
  • nohup … & over SSH got killed when the session closed, so long runs go through systemd-run --unit=... -p StandardOutput=file:/var/log/.... The log must not be in /tmp if another user owns the file (root is denied by fs.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:

  1. From GRUB (neon's menu, shown for 5s): the entry "ChromeOS Flex (internal eMMC)" in /etc/grub.d/40_custom chainloads Flex's own EFI loader (crdyboot) from the eMMC's ESP:
    menuentry "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
    }
    
    Run sudo update-grub after editing 40_custom. The install script rewrites the --fs-uuid value automatically.
  2. From the firmware boot picker (MrChromebox UEFI, Esc during POST): efibootmgr -v shows neon (Boot0005, on the USB card) and Internal eMMC (Boot0001), with BootOrder 0005,0001,0000,0003,0002. With the USB card unplugged, the firmware falls through to Internal eMMC and boots Flex with no menu.

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.