# 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__reven_recovery_stable-channel_.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`.