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>
This commit is contained in:
ergosteur
2026-09-27 12:22:23 -04:00
co-authored by Claude Opus 5.5
parent 93f4eca05c
commit 08ae7257ca
2 changed files with 179 additions and 78 deletions
+165
View File
@@ -0,0 +1,165 @@
# 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`.
+14 -78
View File
@@ -119,82 +119,18 @@ power-off + power-on before assuming anything is actually broken.
## Dual boot: KDE neon (USB) + ChromeOS Flex (internal eMMC)
2026-09-04: installed ChromeOS Flex onto the internal eMMC
(`/dev/mmcblk0`), which was blank (see storage correction above — the
KDE neon system boots from an external USB/SD card on `sda`, not from
anything internal, so writing to the eMMC was safe to do live from the
running system with zero risk to the neon install).
ChromeOS Flex is installed on the internal eMMC (`/dev/mmcblk0`); neon
stays on the external `sda`. **Full details are in `CHROMEOS-FLEX.md`**,
covering install/reinstall via `install-flex-emmc.sh`, why a raw `dd` of
the recovery image boots as live media, `chromeos-install`-from-Linux
gotchas, the partition layout, and boot selection.
**How it was installed**: the Flex recovery image
(`chromeos_16640.57.0_reven_recovery_stable-channel_RevenMPKeys-v10.bin.zip`,
in `matt`'s `~/Downloads`) is a full raw GPT disk image — exactly what
the normal "boot from USB, then Install CloudReady/Chrome OS Flex" flow
`dd`s onto the target disk. Since neon's root already lives on external
media, that install flow was skipped entirely and the image was
streamed straight onto the eMMC instead:
```
sudo sh -c 'unzip -p "<path to .zip>" <path to .bin inside the zip> \
| dd of=/dev/mmcblk0 bs=4M status=progress conv=fsync'
```
Unzipped image is ~8.9GB; eMMC is 29GB. (Original note here claimed
ChromeOS auto-expands the stateful partition on first boot — **wrong**,
see the 2026-09-27 reinstall below.) eMMC write speed degraded over the transfer (~130MB/s at the start down
to ~9MB/s by the end, total ~18 min for 8.9GB) — looks like the eMMC's
SLC write cache exhausting partway through; expected/normal for eMMC of
this class, not a fault.
**2026-09-27: reinstalled properly with `install-flex-emmc.sh`.** The
raw `dd` above left the *installer* image layout: ROOT-B a 2MB
placeholder, STATE a fixed 6GB, ~20.7GB unallocated. Flex's
`/usr/sbin/is_running_from_installer` compares ROOT-A/ROOT-B sizes, so
it booted as live media ("Install ChromeOS Flex / Try it first") and
couldn't do A/B updates. There is no flag to flip; the fix is running
the image's own `chromeos-install` (what the Install button runs)
against the eMMC. `install-flex-emmc.sh` (in this repo) does that from
neon: loop-attaches the `.bin`, chroots into its ROOT-A inside a private
mount namespace with every other disk (incl. neon's `sda`) masked by
`/dev/null`, and runs `chromeos-install --dst /dev/mmcblk0
--payload_image ... --target_bios efi`. Gotchas it handles:
- `chromeos_postinst` needs a `cros_efi` flag on the kernel cmdline;
faked via a bind over `/proc/cmdline`, plus a wrapper on the image's
`chroot` because postinst re-chroots with a non-recursive `/proc` bind.
- Plasma/udisks auto-mounts new eMMC partitions mid-install, making
`sfdisk` refuse a busy disk — a temporary `UDISKS_IGNORE` udev rule
in `/run/udev/rules.d` prevents it.
- `chromeos-install` can exit 0 on failure and logs harmless `ERROR`
lines on success — judge by its final `Installation to ... complete.`
line. Log: `/var/log/flex-install.log`.
- Whole run is only ~2.5 min (partitions copied at eMMC speed).
Result: ROOT-A/ROOT-B 4GB each, STATE 20.6GB (fills disk), KERN-A
`successful=1`. The ESP is copied from the image so its FS UUID stays
`CBE4-703E` (script still re-points `40_custom` in case it changes);
UEFI boot entries/order untouched.
**Boot selection** — both OSes are reachable two ways:
1. **From GRUB** (neon's own boot menu, visible for 5s per
`GRUB_TIMEOUT`): a custom entry "ChromeOS Flex (internal eMMC)" was
added to `/etc/grub.d/40_custom`, which chainloads the eMMC's own EFI
bootloader:
```
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
}
```
`CBE4-703E` is the FAT filesystem UUID of the eMMC's `EFI-SYSTEM`
partition (`mmcblk0p12`); `/EFI/BOOT/BOOTX64.EFI` is Chrome OS's
fallback UEFI loader (`crdyboot`) on that partition. Run
`sudo update-grub` after editing `40_custom` for changes to take
effect.
2. **From the firmware's own UEFI boot picker** (MrChromebox UEFI,
typically `Esc` during POST) — `efibootmgr -v` shows both `neon`
(Boot0005, on the USB stick) and `Internal eMMC` (Boot0001) as
registered entries, `BootOrder` currently `0005,0001,0000,0003,0002`
(neon first). If the USB stick is unplugged, firmware falls through
past the missing `neon` entry straight to `Internal eMMC` and boots
Flex automatically — no menu needed.
Quick reference:
- Reinstall: `sudo ./install-flex-emmc.sh <flex .bin.zip or .bin>` (wipes
the eMMC, masks every other disk, about 2.5 min, log in
`/var/log/flex-install.log`).
- Boot Flex from neon's GRUB menu with the "ChromeOS Flex (internal
eMMC)" entry (in `/etc/grub.d/40_custom`; chainloads the ESP with FS
UUID `CBE4-703E`), or from the firmware picker (`Esc` at POST, then
`Internal eMMC`). With the USB card unplugged, the firmware boots Flex
directly.