From 08ae7257ca1360ae0f785580206032609e2e7557 Mon Sep 17 00:00:00 2001 From: ergosteur <1992147+ergosteur@users.noreply.github.com> Date: Sun, 27 Sep 2026 12:22:23 -0400 Subject: [PATCH] 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 --- CHROMEOS-FLEX.md | 165 +++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 92 ++++---------------------- 2 files changed, 179 insertions(+), 78 deletions(-) create mode 100644 CHROMEOS-FLEX.md diff --git a/CHROMEOS-FLEX.md b/CHROMEOS-FLEX.md new file mode 100644 index 0000000..7c69baf --- /dev/null +++ b/CHROMEOS-FLEX.md @@ -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__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`. diff --git a/CLAUDE.md b/CLAUDE.md index da5c861..0b5a3f7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 "" \ - | 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 ` (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.