Files
hp-chell/CHROMEOS-FLEX.md
T

209 lines
9.5 KiB
Markdown

# 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.
## 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)
and `chromeos-install.sh`, `write_gpt.sh`, `cros_installer`,
`is_running_from_installer`, and coreutils `/bin/chroot` shim, so the
chroot wrapper applies.
- `cros_installer` has the same `No recognized cros_XXX bios option`
check, so the fake `cros_efi` is still needed. The success line is the
same `Installation to '${DST}' complete.`
- The target layout from `write_gpt.sh` is 2.66GB each for ROOT-A and
ROOT-B, with STATE filling the rest of the disk.
- **FydeOS's `chromeos-postinst` adds steps:** it mounts the
destination's ESP (`${install_dev%[0-9]}12`) to sync a `cros_debug`
(dev-mode) flag in its `grub.cfg`, then runs
`/usr/share/dualboot/update_initrd.sh` if `rootdev` returns a loop
device, which it does in our chroot. That's harmless:
`update_initrd.sh` exits unless `fydeos_dualboot` is 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's `crdyboot`. The
`40_custom` chainload 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 is `F35E-F13C`.
Caveats before trying it:
- It replaces Flex (same eMMC, full wipe).
- The script rewrites the UUID in the `40_custom` entry titled
"ChromeOS Flex", so FydeOS would boot under that title. Rename the
entry, or make the script take its title from the image's
`lsb-release`. The script's messages and this doc also say "Flex"
throughout.
- Hardware support is unverified: it's unclear whether the `iris` build
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`.